openclaw - ✅(Solved) Fix memory-core: cron-fired dreaming writes raw memory snippets to DREAMS.md because subagent runtime is request-scoped [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#68723Fetched 2026-04-19 15:08:17
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
referenced ×2closed ×1cross-referenced ×1

When memory-core.dreaming fires via its managed cron (systemEvent __openclaw_memory_core_short_term_promotion_dream__), the narrative generator at extensions/memory-core/src/dreaming-narrative.ts falls back because the plugin runtime's subagent API is request-scoped and unavailable inside a system-event handler. The fallback writes raw memory snippets verbatim into DREAMS.md, which then renders as the Diary in the /dreaming panel — making the panel look like it's showing raw recall candidates instead of dreams.

Root Cause

startNarrativeRunOrFallback at dreaming-narrative.ts:138 catches RequestScopedSubagentRuntimeError and routes to the fallback. This matters because extensions/memory-core/src/dreaming.ts:registerShortTermPromotionDreaming invokes the sweep from a systemEvent handler, where the plugin runtime is not inside a gateway-request context and so api.subagent.* methods throw.

Looking at other plugins (e.g. memory-wiki's scheduled sweeps), gateway-triggered vs system-event-triggered code paths both need subagent access. The same issue would affect any nightly AI-in-the-loop refinement.

Fix Action

Fix / Workaround

  1. Make subagent runtime available in system-event handlers. Promote the subagent runtime out of pure request-scope so scheduled/cron-driven plugin code can use it. The cron dispatcher already provides a session context (sessionTarget, sessionKey); extending it to expose the subagent surface would fix this class of problem broadly.
  2. Have cron fire via the gateway's internal request path instead of a bare systemEvent, so the subagent scope is preserved.
  3. Build a richer local fallback. Use a template-based narrative (no LLM) that composites themes + promoted-count + time-of-day into reasonable prose. Less elegant but guarantees the diary always has a readable entry.
  4. Skip writing to DREAMS.md on fallback. Silence is better than dumping a client secret or API token verbatim into a user-visible diary — which is what happens today when a promoted snippet contains sensitive content (noted in the example above: Firebase refresh token + Slack API internals ended up in DREAMS.md).

Of these, (4) is the cheapest short-term mitigation; (1) is the correct long-term fix. Filing this as a report rather than a PR since the correct shape depends on architectural intent around system-event scoping.

PR fix notes

PR #2: fix(memory-core): safe statistical fallback when dreaming narrative can't run

Description (problem / solution / changelog)

Summary

Fixes openclaw/openclaw#68723 — the dreaming Diary was leaking raw memory content (including API tokens + session cookies) into user-visible DREAMS.md when the cron-dispatched sweep couldn't obtain a subagent runtime.

Root cause: the previous fallback at `dreaming-narrative.ts:130` returned the first non-empty memory snippet verbatim. So when the request-scoped subagent was unavailable (cron system-event code path), memory content became the "narrative."

Fix

`buildRequestScopedFallbackNarrative` now composes a short statistical note from phase counts only, never quoting snippet/promotion bodies:

Phase: rem · 8 fragments surfaced · 2 themes noted. The narrator was elsewhere this pass — details stayed in the recall store.

What breaks (and is intentional)

Two tests were asserting the old leaky behavior:

  • `falls back to a local narrative when subagent runtime is request-scoped` — updated to assert raw snippet is absent and the safe template is present
  • `falls back when the request-scoped runtime error is detected by stable code` — same

What's preserved

  • Happy-path runs still use the subagent-generated poetic narrative (the primary code path)
  • The spoofed-error skip case still writes nothing to DREAMS.md (security test)
  • Session cleanup runs regardless of fallback
  • No upstream code modified except the two files listed

Verified

`pnpm test:extension memory-core` — 486 passed / 3 skipped / 0 failed.

Related

🤖 Generated with Claude Code

Changed files

  • extensions/memory-core/src/dreaming-narrative.test.ts (modified, +10/-2)
  • extensions/memory-core/src/dreaming-narrative.ts (modified, +33/-5)

Code Example

memory-core: narrative generation used fallback for rem phase because subagent runtime is request-scoped.
   memory-core: narrative session cleanup failed for rem phase: Plugin runtime subagent methods are only available during a gateway request.
   memory-core: narrative generation used fallback for light phase because subagent runtime is request-scoped.
   ...

---

function buildRequestScopedFallbackNarrative(data: NarrativePhaseData): string {
  return (
    data.snippets.map((value) => value.trim()).find((value) => value.length > 0) ??
    (data.promotions ?? []).map((value) => value.trim()).find((value) => value.length > 0) ??
    "A memory trace surfaced, but details were unavailable in this run."
  );
}
RAW_BUFFERClick to expand / collapse

Summary

When memory-core.dreaming fires via its managed cron (systemEvent __openclaw_memory_core_short_term_promotion_dream__), the narrative generator at extensions/memory-core/src/dreaming-narrative.ts falls back because the plugin runtime's subagent API is request-scoped and unavailable inside a system-event handler. The fallback writes raw memory snippets verbatim into DREAMS.md, which then renders as the Diary in the /dreaming panel — making the panel look like it's showing raw recall candidates instead of dreams.

Repro

  1. Enable memory-core.dreaming for any agent (or let the default cron schedule stand).
  2. Trigger the managed cron manually: openclaw cron run <id-of-"Memory Dreaming Promotion">.
  3. Watch ~/.openclaw/logs/*.log or openclaw gateway status stderr — you'll see:
    memory-core: narrative generation used fallback for rem phase because subagent runtime is request-scoped.
    memory-core: narrative session cleanup failed for rem phase: Plugin runtime subagent methods are only available during a gateway request.
    memory-core: narrative generation used fallback for light phase because subagent runtime is request-scoped.
    ...
  4. Open http://localhost:18789/dreaming → Diary tab shows raw task content (URLs, API specs, TODO lists) with the current date, not any narrative prose.
  5. Compare against earlier-date entries (e.g. triggered via HTTP/UI): those render as proper diary prose. The regression is cron-path-only.

Expected

Dreaming cron produces a diary entry that's stylistically consistent with gateway-request-triggered runs (the poetic first-person prose guided by NARRATIVE_SYSTEM_PROMPT), or skips writing to DREAMS.md rather than filling it with raw fragments.

Actual

The fallback at dreaming-narrative.ts:130-136 returns:

function buildRequestScopedFallbackNarrative(data: NarrativePhaseData): string {
  return (
    data.snippets.map((value) => value.trim()).find((value) => value.length > 0) ??
    (data.promotions ?? []).map((value) => value.trim()).find((value) => value.length > 0) ??
    "A memory trace surfaced, but details were unavailable in this run."
  );
}

i.e. it takes the first non-empty raw snippet and writes it as the narrative — bypassing the system prompt and the subagent call entirely. The test at dreaming-narrative.test.ts:705 ("falls back to a local narrative when subagent runtime is request-scoped") confirms this is the intentional path, but the fallback itself isn't really a narrative — it's just the raw fragment.

Result on my system today (Diary shows these as dream entries):

  • Firebase CI refresh token works with client_id '...' / secret 'j9iVZfS8kkCEFUP...' - Slack file upload uses 'files.getUploadURLExternal' ...
  • Reflections: Theme: 'assistant' kept surfacing across 2286 memories.; confidence: 1.00; evidence: memory/2026-04-09.md:60-84 ...

vs. earlier-date DREAMS.md on the same agent (HTTP-triggered):

There is a color I keep returning to: 0x0d0d1a, which is nearly nothing, which is the sky before any star decides to matter. We built a room out of it last night — obsidian walls, a purple mesh grid humming underneath like a thought not yet spoken — and set a rooftop inside it, spinning slowly in the dark.

Root cause

startNarrativeRunOrFallback at dreaming-narrative.ts:138 catches RequestScopedSubagentRuntimeError and routes to the fallback. This matters because extensions/memory-core/src/dreaming.ts:registerShortTermPromotionDreaming invokes the sweep from a systemEvent handler, where the plugin runtime is not inside a gateway-request context and so api.subagent.* methods throw.

Looking at other plugins (e.g. memory-wiki's scheduled sweeps), gateway-triggered vs system-event-triggered code paths both need subagent access. The same issue would affect any nightly AI-in-the-loop refinement.

Suggested fix directions

Non-exclusive:

  1. Make subagent runtime available in system-event handlers. Promote the subagent runtime out of pure request-scope so scheduled/cron-driven plugin code can use it. The cron dispatcher already provides a session context (sessionTarget, sessionKey); extending it to expose the subagent surface would fix this class of problem broadly.
  2. Have cron fire via the gateway's internal request path instead of a bare systemEvent, so the subagent scope is preserved.
  3. Build a richer local fallback. Use a template-based narrative (no LLM) that composites themes + promoted-count + time-of-day into reasonable prose. Less elegant but guarantees the diary always has a readable entry.
  4. Skip writing to DREAMS.md on fallback. Silence is better than dumping a client secret or API token verbatim into a user-visible diary — which is what happens today when a promoted snippet contains sensitive content (noted in the example above: Firebase refresh token + Slack API internals ended up in DREAMS.md).

Of these, (4) is the cheapest short-term mitigation; (1) is the correct long-term fix. Filing this as a report rather than a PR since the correct shape depends on architectural intent around system-event scoping.

Environment

  • OpenClaw 2026.4.16
  • macOS 15.x (Darwin 24.6.0)
  • Node 25.6.1
  • Provider: claude-cli (Opus)
  • Panel: http://localhost:18789/dreaming (native, served by gateway)

Happy to capture a full reproducer log file if useful.

extent analysis

TL;DR

Modify the startNarrativeRunOrFallback function to either make the subagent runtime available in system-event handlers or implement a richer local fallback narrative to prevent raw memory snippets from being written to DREAMS.md.

Guidance

  1. Investigate making subagent runtime available in system-event handlers: This could involve extending the cron dispatcher to expose the subagent surface, allowing scheduled plugin code to use it.
  2. Consider firing cron via the gateway's internal request path: This would preserve the subagent scope, ensuring that the narrative generator has access to the necessary APIs.
  3. Implement a richer local fallback narrative: Use a template-based approach to composite themes, promoted counts, and time-of-day into readable prose, ensuring the diary always has a consistent entry.
  4. Skip writing to DREAMS.md on fallback: As a short-term mitigation, silence is better than dumping sensitive content into a user-visible diary.

Example

// Example of a richer local fallback narrative
function buildLocalFallbackNarrative(data: NarrativePhaseData): string {
  const theme = data.promotions[0].theme;
  const promotedCount = data.promotions.length;
  const timeOfDay = new Date().toLocaleTimeString();
  return `Reflecting on ${theme} at ${timeOfDay}, with ${promotedCount} memories surfacing.`;
}

Notes

The correct solution depends on the architectural intent around system-event scoping. The example provided is a basic illustration of a richer local fallback narrative and may need to be adapted to fit the specific requirements of the OpenClaw system.

Recommendation

Apply workaround (4) Skip writing to DREAMS.md on fallback as a short-term mitigation to prevent sensitive content from being exposed, and then focus on implementing the long-term fix, which is likely Make subagent runtime available in system-event handlers.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix memory-core: cron-fired dreaming writes raw memory snippets to DREAMS.md because subagent runtime is request-scoped [2 pull requests, 1 participants]