From 8a4a63ca076ea052a3c3939b503820dca0cc5dc6 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 12 Apr 2026 18:30:35 +0100 Subject: [PATCH] fix(memory-core): use all dreaming signals for light confidence --- CHANGELOG.md | 1 + .../memory-core/src/dreaming-phases.test.ts | 23 +++++++++++++++++++ extensions/memory-core/src/dreaming-phases.ts | 8 ++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d1d837e83c..437fe00f663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai - Gateway/cron: preserve requested isolated-agent config across runtime reloads so subagent jobs and heartbeat overrides keep the right workspace and heartbeat settings when the hot-loaded snapshot is stale. Thanks @l0cka and @vincentkoc. - Gateway/plugins: always send a non-empty `idempotencyKey` for plugin subagent runs, so dreaming narrative jobs stop failing gateway schema validation. (#65354) Thanks @CodeForgeNet and @vincentkoc. - Dreaming/promotion: raise phase reinforcement enough for repeated dreaming-only revisits to clear the default durable-memory gate after multiple days, instead of stalling just below the score threshold. Thanks @vincentkoc. +- Dreaming/light-sleep: compute staged candidate confidence from all recorded short-term signals instead of recall-only counts, so dreaming-only entries stop rendering as `confidence: 0.00`. Thanks @vincentkoc. - CLI/plugins: honor `memory-wiki` when `plugins.allow` is set for `openclaw wiki`, and register `wiki` as the plugin-owned command alias so doctor/config stop treating it as stale. (#64779) Thanks @feiskyer and @vincentkoc. - Cron/isolated sessions: persist the right transcript path for each isolated run, including fresh session rollovers, so cron runs stop appending to stale session files. Thanks @samrusani and @vincentkoc. - CLI/memory-wiki: pass the active app config into the metadata registrar so built `openclaw wiki` commands resolve the live wiki plugin config instead of silently falling back to defaults. (#65012) Thanks @leonardsellem and @vincentkoc. diff --git a/extensions/memory-core/src/dreaming-phases.test.ts b/extensions/memory-core/src/dreaming-phases.test.ts index c73cbad1d30..b5944430285 100644 --- a/extensions/memory-core/src/dreaming-phases.test.ts +++ b/extensions/memory-core/src/dreaming-phases.test.ts @@ -406,6 +406,29 @@ describe("memory-core dreaming phases", () => { expect(after[0]?.snippet).toContain("Keep retention at 365 days."); }); + it("renders non-zero light-sleep confidence for dreaming-ingested candidates", async () => { + const workspaceDir = await createDreamingWorkspace(); + await withDreamingTestClock(async () => { + await writeDailyNote(workspaceDir, [ + `# ${DREAMING_TEST_DAY}`, + "", + "- Move backups to S3 Glacier.", + "- Keep retention at 365 days.", + ]); + + const { beforeAgentReply } = createLightDreamingHarness(workspaceDir); + await triggerLightDreaming(beforeAgentReply, workspaceDir, 5); + + const dailyContent = await fs.readFile( + path.join(workspaceDir, "memory", `${DREAMING_TEST_DAY}.md`), + "utf-8", + ); + expect(dailyContent).toContain("## Light Sleep"); + expect(dailyContent).toContain("confidence: 0.62"); + expect(dailyContent).not.toContain("confidence: 0.00"); + }); + }); + it("checkpoints session transcript ingestion and skips unchanged transcripts", async () => { const workspaceDir = await createDreamingWorkspace(); vi.stubEnv("OPENCLAW_TEST_FAST", "1"); diff --git a/extensions/memory-core/src/dreaming-phases.ts b/extensions/memory-core/src/dreaming-phases.ts index b4f6a73ec69..f20f262df54 100644 --- a/extensions/memory-core/src/dreaming-phases.ts +++ b/extensions/memory-core/src/dreaming-phases.ts @@ -1241,7 +1241,13 @@ export async function seedHistoricalDailyMemorySignals(params: { } function entryAverageScore(entry: ShortTermRecallEntry): number { - return entry.recallCount > 0 ? Math.max(0, Math.min(1, entry.totalScore / entry.recallCount)) : 0; + const signalCount = Math.max( + 0, + Math.floor(entry.recallCount ?? 0) + + Math.floor(entry.dailyCount ?? 0) + + Math.floor(entry.groundedCount ?? 0), + ); + return signalCount > 0 ? Math.max(0, Math.min(1, entry.totalScore / signalCount)) : 0; } function tokenizeSnippet(snippet: string): Set {