openclaw - ✅(Solved) Fix Dreaming narrative sessions never reuse — session key includes timestamp causing unbounded growth [1 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#68354Fetched 2026-04-18 05:53:07
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×1referenced ×1

Fix Action

Fixed

PR fix notes

PR #68364: fix: prevent unbounded narrative session growth by stabilizing session key (#68354)

Description (problem / solution / changelog)

Problem

buildNarrativeSessionKey includes a millisecond timestamp (nowMs from Date.now()) in the session key, making it unique per dream run:

return `dreaming-narrative-${params.phase}-${workspaceHash}-${params.nowMs}`;

When deleteSession fails in the finally block, these uniquely-keyed sessions accumulate indefinitely. As reported in #68354: 251 orphan sessions in 4 days, consuming ~4.8M tokens.

Fix

1. Stabilize session key (remove nowMs)

return `dreaming-narrative-${params.phase}-${workspaceHash}`;

Now each workspace+phase combination reuses the same session key. If deleteSession fails, the next run reuses the existing session instead of creating a new orphan. Maximum accumulation: 2-3 sessions (one per phase) instead of unbounded.

2. Keep nowMs in idempotency key

idempotencyKey: `${params.sessionKey}-${params.nowMs}`,

Prevents deduplication across dream runs — each run is still treated as unique by the subagent runtime.

3. Pre-run defensive cleanup

Added a deleteSession call before subagent.run() to clear any leftover session from a previous failed run. This prevents stale conversation context from polluting the new narrative.

// Defensive cleanup: delete any leftover session from a previous failed run
try {
  await params.subagent.deleteSession({ sessionKey });
} catch {
  // Ignore — session may not exist yet.
}

Trade-offs considered

  • Stale context: If deleteSession fails in finally AND the pre-run cleanup also fails, the next run could see stale messages. This is a minor cosmetic risk (dream diary entries slightly influenced by previous context) vs. the current unbounded resource leak.
  • Concurrent runs: Dream runs are cron-scheduled, so concurrent same-workspace/phase runs are unlikely. The stable key makes them share a session if they do overlap, which is safer than creating parallel orphans.

Tests updated

  • Updated deleteSession call count expectations (now 2 per run: pre-run cleanup + finally)
  • Updated workspace isolation test to verify delete keys per workspace
  • Updated idempotency key expectations

Closes #68354

Changed files

  • extensions/memory-core/src/dreaming-narrative.test.ts (modified, +10/-6)
  • extensions/memory-core/src/dreaming-narrative.ts (modified, +10/-4)

Code Example

// dreaming-narrative.ts
function buildNarrativeSessionKey(params) {
    const workspaceHash = createHash("sha1").update(params.workspaceDir).digest("hex").slice(0, 12);
    return `dreaming-narrative-${params.phase}-${workspaceHash}-${params.nowMs}`;
    //                                                 ^^^^^^ unique per run
}

---

function buildNarrativeSessionKey(params) {
    const workspaceHash = createHash("sha1").update(params.workspaceDir).digest("hex").slice(0, 12);
    return `dreaming-narrative-${params.phase}-${workspaceHash}`;
}
RAW_BUFFERClick to expand / collapse

Bug Description

When memory-core dreaming is enabled, the Dream Diary narrative subagent creates a new session on every run instead of reusing an existing one. The session key includes a millisecond timestamp, making it unique each time:

// dreaming-narrative.ts
function buildNarrativeSessionKey(params) {
    const workspaceHash = createHash("sha1").update(params.workspaceDir).digest("hex").slice(0, 12);
    return `dreaming-narrative-${params.phase}-${workspaceHash}-${params.nowMs}`;
    //                                                 ^^^^^^ unique per run
}

This causes unbounded session accumulation in the session store.

Impact

  • Over 4 days (Apr 12–16), 251 orphan dreaming sessions accumulated on the main agent
  • Each session consumed ~19K tokens, totaling ~4.8M tokens wasted
  • The session store index grew from ~4 entries to 255, with no automatic cleanup of the index entries
  • While orphan transcript files get cleaned up after 5 minutes (DREAMING_ORPHAN_MIN_AGE_MS), the session store entries persist indefinitely

Reproduction

  1. Enable dreaming: plugins.entries.memory-core.config.dreaming.enabled = true
  2. Let the dreaming cron run (default: 0 3 * * *) or trigger dreaming manually
  3. Observe session count growing by 2 per run (light + rem phases)

Expected Behavior

The narrative subagent should reuse a single session (or at most one per phase: dreaming-narrative-light and dreaming-narrative-rem) rather than creating a new session each time.

Environment

  • OpenClaw version: 2026.4.14 (323493f)
  • OS: macOS 26.3.1 (arm64)
  • Node: v24.14.0

Suggested Fix

Remove nowMs from the session key, or use a fixed key per agent+phase combination:

function buildNarrativeSessionKey(params) {
    const workspaceHash = createHash("sha1").update(params.workspaceDir).digest("hex").slice(0, 12);
    return `dreaming-narrative-${params.phase}-${workspaceHash}`;
}

The existing orphan cleanup logic already handles transcript files — but the session store entries also need cleanup if unique keys are intentional.

extent analysis

TL;DR

Remove the nowMs parameter from the buildNarrativeSessionKey function to prevent creating a new session on every run.

Guidance

  • Identify the buildNarrativeSessionKey function in dreaming-narrative.ts and modify it to exclude the nowMs parameter.
  • Verify the fix by enabling dreaming, running the dreaming cron, and checking if the session count remains constant.
  • Consider implementing a cleanup mechanism for existing orphan session store entries to prevent index growth.
  • Review the DREAMING_ORPHAN_MIN_AGE_MS configuration to ensure it aligns with the desired session store cleanup behavior.

Example

function buildNarrativeSessionKey(params) {
    const workspaceHash = createHash("sha1").update(params.workspaceDir).digest("hex").slice(0, 12);
    return `dreaming-narrative-${params.phase}-${workspaceHash}`;
}

Notes

The suggested fix assumes that reusing a single session per phase is the desired behavior. If unique session keys are intentional, additional cleanup logic may be necessary to prevent unbounded session accumulation.

Recommendation

Apply the workaround by modifying the buildNarrativeSessionKey function to exclude the nowMs parameter, as this will prevent new session creation on every run and allow for existing session reuse.

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