mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: re-ingest daily memory during dreaming (#76359) (thanks @neeravmakwana)
This commit is contained in:
@@ -326,6 +326,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/auth-profiles: do not record request-shape (`format`) rejections as auth-profile health failures, so a single per-session transcript-shape error (such as a prefill-strict 400 "conversation must end with a user message") no longer triggers a profile-wide cooldown that blocks every other healthy session sharing the same auth profile. Refs #77228. (#77280) Thanks @openperf.
|
||||
- CLI/update: stop dev-channel source updates immediately when `git fetch` fails, so tag conflicts cannot keep preflight, rebase, or build steps running against stale refs while the Gateway is still on the old runtime. (#77845) Thanks @obviyus.
|
||||
- Config/recovery: chmod restored `openclaw.json` back to owner-only (`0600`) after suspicious-read backup recovery on POSIX hosts, so a previously world-readable config mode cannot persist into a freshly restored credential-bearing config. (#77488) Thanks @drobison00.
|
||||
- Memory/dreaming: persist last dreaming-ingestion calendar day per daily note in `daily-ingestion.json` so unchanged notes are still re-ingested once per dreaming day for promotion signals toward deep thresholds. Fixes #76225. (#76359) Thanks @neeravmakwana.
|
||||
|
||||
## 2026.5.3-1
|
||||
|
||||
|
||||
@@ -2586,17 +2586,6 @@ describe("memory-core dreaming phases", () => {
|
||||
expect(after1).toHaveLength(1);
|
||||
expect(after1[0]?.dailyCount).toBe(1);
|
||||
|
||||
// Clear the daily ingestion checkpoint so the file is re-read on the second
|
||||
// sweep (simulating a new day where the same lookback window still covers
|
||||
// this file).
|
||||
const dailyStatePath = path.join(workspaceDir, "memory", ".dreams", "daily-ingestion.json");
|
||||
try {
|
||||
await fs.unlink(dailyStatePath);
|
||||
} catch {
|
||||
// ignore if not created
|
||||
}
|
||||
|
||||
// Second ingestion on 2026-04-06 (next day).
|
||||
const day2Ms = Date.parse("2026-04-06T10:00:00.000Z");
|
||||
const { beforeAgentReply: reply2 } = createHarness(configForTest, workspaceDir);
|
||||
await withDreamingTestClock(async () => {
|
||||
@@ -2615,8 +2604,6 @@ describe("memory-core dreaming phases", () => {
|
||||
nowMs: day2Ms,
|
||||
});
|
||||
expect(after2).toHaveLength(1);
|
||||
// With the fix, dailyCount should be 2 because the ingestion date changed.
|
||||
// Before the fix, it stayed at 1 because dayBucket was the file date.
|
||||
expect(after2[0]?.dailyCount).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,6 +72,7 @@ type RunPhaseIfTriggeredParams = {
|
||||
);
|
||||
const LIGHT_SLEEP_EVENT_TEXT = "__openclaw_memory_core_light_sleep__";
|
||||
const REM_SLEEP_EVENT_TEXT = "__openclaw_memory_core_rem_sleep__";
|
||||
const MEMORY_DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
||||
const DAILY_MEMORY_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})\.md$/;
|
||||
const DAILY_INGESTION_STATE_RELATIVE_PATH = path.join("memory", ".dreams", "daily-ingestion.json");
|
||||
const DAILY_INGESTION_SCORE = 0.62;
|
||||
@@ -386,6 +387,7 @@ type DailyIngestionBatch = {
|
||||
type DailyIngestionFileState = {
|
||||
mtimeMs: number;
|
||||
size: number;
|
||||
lastDreamingDayIngested?: string;
|
||||
};
|
||||
|
||||
type DailyIngestionState = {
|
||||
@@ -417,9 +419,11 @@ function normalizeDailyIngestionState(raw: unknown): DailyIngestionState {
|
||||
if (!Number.isFinite(mtimeMs) || mtimeMs < 0 || !Number.isFinite(size) || size < 0) {
|
||||
continue;
|
||||
}
|
||||
const lastDreamingDayIngested = normalizeMemoryDay(file.lastDreamingDayIngested);
|
||||
files[key] = {
|
||||
mtimeMs: Math.floor(mtimeMs),
|
||||
size: Math.floor(size),
|
||||
...(lastDreamingDayIngested ? { lastDreamingDayIngested } : {}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -428,6 +432,14 @@ function normalizeDailyIngestionState(raw: unknown): DailyIngestionState {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMemoryDay(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const day = value.trim();
|
||||
return MEMORY_DAY_RE.test(day) ? day : undefined;
|
||||
}
|
||||
|
||||
async function readDailyIngestionState(workspaceDir: string): Promise<DailyIngestionState> {
|
||||
const statePath = resolveDailyIngestionStatePath(workspaceDir);
|
||||
try {
|
||||
@@ -1065,6 +1077,7 @@ async function collectDailyIngestionBatches(params: {
|
||||
lookbackDays: number;
|
||||
limit: number;
|
||||
nowMs: number;
|
||||
ingestionDreamingDay: string;
|
||||
state: DailyIngestionState;
|
||||
}): Promise<DailyIngestionCollectionResult> {
|
||||
const memoryDir = path.join(params.workspaceDir, "memory");
|
||||
@@ -1119,11 +1132,15 @@ async function collectDailyIngestionBatches(params: {
|
||||
previous !== undefined &&
|
||||
previous.mtimeMs === fingerprint.mtimeMs &&
|
||||
previous.size === fingerprint.size;
|
||||
if (!unchanged) {
|
||||
changed = true;
|
||||
} else {
|
||||
const previousDreamingDay = normalizeMemoryDay(previous?.lastDreamingDayIngested);
|
||||
if (unchanged && previousDreamingDay === params.ingestionDreamingDay) {
|
||||
nextFiles[relativePath] = {
|
||||
...fingerprint,
|
||||
lastDreamingDayIngested: previousDreamingDay,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
changed = true;
|
||||
|
||||
const raw = await fs.readFile(filePath, "utf-8").catch((err: unknown) => {
|
||||
if ((err as NodeJS.ErrnoException)?.code === "ENOENT") {
|
||||
@@ -1155,6 +1172,10 @@ async function collectDailyIngestionBatches(params: {
|
||||
}
|
||||
batches.push({ day: file.day, results });
|
||||
total += results.length;
|
||||
nextFiles[relativePath] = {
|
||||
...fingerprint,
|
||||
lastDreamingDayIngested: params.ingestionDreamingDay,
|
||||
};
|
||||
if (total >= totalCap) {
|
||||
break;
|
||||
}
|
||||
@@ -1189,14 +1210,15 @@ async function ingestDailyMemorySignals(params: {
|
||||
timezone?: string;
|
||||
}): Promise<void> {
|
||||
const state = await readDailyIngestionState(params.workspaceDir);
|
||||
const ingestionDayBucket = formatMemoryDreamingDay(params.nowMs, params.timezone);
|
||||
const collected = await collectDailyIngestionBatches({
|
||||
workspaceDir: params.workspaceDir,
|
||||
lookbackDays: params.lookbackDays,
|
||||
limit: params.limit,
|
||||
nowMs: params.nowMs,
|
||||
ingestionDreamingDay: ingestionDayBucket,
|
||||
state,
|
||||
});
|
||||
const ingestionDayBucket = formatMemoryDreamingDay(params.nowMs, params.timezone);
|
||||
for (const batch of collected.batches) {
|
||||
await recordShortTermRecalls({
|
||||
workspaceDir: params.workspaceDir,
|
||||
|
||||
Reference in New Issue
Block a user