Skip to content

memory-core: cron-fired dreaming writes raw memory snippets to DREAMS.md because subagent runtime is request-scoped #68723

Description

@LightDriverCS

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions