Files
openclaw/docs/concepts/dreaming.md
Patrick Erichsen 529577e045 fix(memory/dreaming): surface blocked status when heartbeat is disabled for main (#69875)
* fix(memory/dreaming): surface blocked status in memory status when heartbeat disabled for main

Replace the hand-rolled heartbeat-rules logic in resolveDreamingBlockedReason
with the shared resolveHeartbeatSummaryForAgent helper, promoted from core to
the plugin-sdk via infra-runtime. Collapses the two disabled-reason branches
into a single message that points at a new Troubleshooting section in the
dreaming docs, so the silent-failure mode described in openclaw/openclaw#69843
becomes legible without the extension re-encoding heartbeat-enablement rules.

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* refactor(memory/dreaming): share resolveDreamingBlockedReason across cli and /dreaming surfaces

- Move resolveDreamingBlockedReason from cli.runtime.ts into dreaming.ts as an exported helper and pin its heartbeat check to DEFAULT_AGENT_ID (now exported from plugin-sdk/routing) so the status-line check agrees with the cron's hardcoded sessionTarget even when the configured default agent is not main.
- Render the blocked reason from formatStatus in dreaming-command.ts directly under the enabled line, so /dreaming status, /dreaming on, /dreaming off, and bare /dreaming all flag that the cron is blocked instead of implying dreaming is healthy.
- Tighten the blocked-reason text to lead with user impact ('dreaming is enabled but will not run because heartbeat is disabled for main'), so operators immediately understand the config is toggled on but nothing is actually running.
- Tighten the dreaming Troubleshooting copy to name main explicitly and mention both surfaces.
- Add tests locking the new behavior across cli.test.ts (default-agent=ops still reports blocked for main) and dreaming-command.test.ts (/dreaming status ordering, /dreaming on surfacing, healthy-heartbeat omission).

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* fix(memory/dreaming): check heartbeat for the resolved default agent, not the literal 'main'

sessionTarget: 'main' is a cron session-type enum variant meaning 'the default agent's main session', not an agent id (see src/cron/service/jobs.ts). buildManagedDreamingCronJob does not set agentId, and cron runtime resolves the missing agentId through resolveDefaultAgentId(cfg) before enqueuing or waking. The previous pin to DEFAULT_AGENT_ID could produce a false 'blocked' reading when a configured default agent is not 'main' and its heartbeat is fine, and could miss a real block when the default agent is not 'main' and that agent's heartbeat is actually off.

Switch resolveDreamingBlockedReason to resolveDefaultAgentId(cfg) and interpolate the resolved agent id into the message so the blocked line names the agent whose heartbeat is the blocker. Introduce a narrow local CRON_SESSION_TARGET_MAIN constant for the cron session-type enum variant (used by the sessionTarget type and value) so the remaining 'main' literal is semantically distinct from any agent id. Revert the DEFAULT_AGENT_ID export addition on plugin-sdk/routing; memory-core no longer needs it. Update the Troubleshooting doc wording and the cli test that was locking the wrong behaviour.

Refs openclaw/openclaw#69843, openclaw/openclaw#46046.

* fix(memory/dreaming): align blocked check with server-cron wake's defaults-only heartbeat

resolveDreamingBlockedReason was using resolveHeartbeatSummaryForAgent, which merges agents.defaults.heartbeat with agents.list[].heartbeat. The managed dreaming cron leaves job.agentId and job.sessionKey unset, so server-cron's wake wrapper cannot look up a per-agent entry and calls runHeartbeatOnce with agents.defaults.heartbeat only. Using the summary helper would disagree with the actual wake when the default agent overrides heartbeat.every differently from the defaults (either direction — false blocked when the override would run, or false healthy when defaults block).

Mirror the wake path explicitly: rule-1 enablement via isHeartbeatEnabledForAgent against the default agent, rule-3 interval via resolveHeartbeatIntervalMs with defaults-only heartbeat config. Comment points at server-cron so a future cleanup of that latent override-propagation gap sees the coupling.

Refs openclaw/openclaw#69843.
2026-04-21 18:51:43 -07:00

8.7 KiB

title, summary, read_when
title summary read_when
Dreaming Background memory consolidation with light, deep, and REM phases plus a Dream Diary
You want memory promotion to run automatically
You want to understand what each dreaming phase does
You want to tune consolidation without polluting MEMORY.md

Dreaming

Dreaming is the background memory consolidation system in memory-core. It helps OpenClaw move strong short-term signals into durable memory while keeping the process explainable and reviewable.

Dreaming is opt-in and disabled by default.

What dreaming writes

Dreaming keeps two kinds of output:

  • Machine state in memory/.dreams/ (recall store, phase signals, ingestion checkpoints, locks).
  • Human-readable output in DREAMS.md (or existing dreams.md) and optional phase report files under memory/dreaming/<phase>/YYYY-MM-DD.md.

Long-term promotion still writes only to MEMORY.md.

Phase model

Dreaming uses three cooperative phases:

Phase Purpose Durable write
Light Sort and stage recent short-term material No
Deep Score and promote durable candidates Yes (MEMORY.md)
REM Reflect on themes and recurring ideas No

These phases are internal implementation details, not separate user-configured "modes."

Light phase

Light phase ingests recent daily memory signals and recall traces, dedupes them, and stages candidate lines.

  • Reads from short-term recall state, recent daily memory files, and redacted session transcripts when available.
  • Writes a managed ## Light Sleep block when storage includes inline output.
  • Records reinforcement signals for later deep ranking.
  • Never writes to MEMORY.md.

Deep phase

Deep phase decides what becomes long-term memory.

  • Ranks candidates using weighted scoring and threshold gates.
  • Requires minScore, minRecallCount, and minUniqueQueries to pass.
  • Rehydrates snippets from live daily files before writing, so stale/deleted snippets are skipped.
  • Appends promoted entries to MEMORY.md.
  • Writes a ## Deep Sleep summary into DREAMS.md and optionally writes memory/dreaming/deep/YYYY-MM-DD.md.

REM phase

REM phase extracts patterns and reflective signals.

  • Builds theme and reflection summaries from recent short-term traces.
  • Writes a managed ## REM Sleep block when storage includes inline output.
  • Records REM reinforcement signals used by deep ranking.
  • Never writes to MEMORY.md.

Session transcript ingestion

Dreaming can ingest redacted session transcripts into the dreaming corpus. When transcripts are available, they are fed into the light phase alongside daily memory signals and recall traces. Personal and sensitive content is redacted before ingestion.

Dream Diary

Dreaming also keeps a narrative Dream Diary in DREAMS.md. After each phase has enough material, memory-core runs a best-effort background subagent turn (using the default runtime model) and appends a short diary entry.

This diary is for human reading in the Dreams UI, not a promotion source. Dreaming-generated diary/report artifacts are excluded from short-term promotion. Only grounded memory snippets are eligible to promote into MEMORY.md.

There is also a grounded historical backfill lane for review and recovery work:

  • memory rem-harness --path ... --grounded previews grounded diary output from historical YYYY-MM-DD.md notes.
  • memory rem-backfill --path ... writes reversible grounded diary entries into DREAMS.md.
  • memory rem-backfill --path ... --stage-short-term stages grounded durable candidates into the same short-term evidence store the normal deep phase already uses.
  • memory rem-backfill --rollback and --rollback-short-term remove those staged backfill artifacts without touching ordinary diary entries or live short-term recall.

The Control UI exposes the same diary backfill/reset flow so you can inspect results in the Dreams scene before deciding whether the grounded candidates deserve promotion. The Scene also shows a distinct grounded lane so you can see which staged short-term entries came from historical replay, which promoted items were grounded-led, and clear only grounded-only staged entries without touching ordinary live short-term state.

Deep ranking signals

Deep ranking uses six weighted base signals plus phase reinforcement:

Signal Weight Description
Frequency 0.24 How many short-term signals the entry accumulated
Relevance 0.30 Average retrieval quality for the entry
Query diversity 0.15 Distinct query/day contexts that surfaced it
Recency 0.15 Time-decayed freshness score
Consolidation 0.10 Multi-day recurrence strength
Conceptual richness 0.06 Concept-tag density from snippet/path

Light and REM phase hits add a small recency-decayed boost from memory/.dreams/phase-signals.json.

Scheduling

When enabled, memory-core auto-manages one cron job for a full dreaming sweep. Each sweep runs phases in order: light -> REM -> deep.

Default cadence behavior:

Setting Default
dreaming.frequency 0 3 * * *

Quick start

Enable dreaming:

{
  "plugins": {
    "entries": {
      "memory-core": {
        "config": {
          "dreaming": {
            "enabled": true
          }
        }
      }
    }
  }
}

Enable dreaming with a custom sweep cadence:

{
  "plugins": {
    "entries": {
      "memory-core": {
        "config": {
          "dreaming": {
            "enabled": true,
            "timezone": "America/Los_Angeles",
            "frequency": "0 */6 * * *"
          }
        }
      }
    }
  }
}

Slash command

/dreaming status
/dreaming on
/dreaming off
/dreaming help

CLI workflow

Use CLI promotion for preview or manual apply:

openclaw memory promote
openclaw memory promote --apply
openclaw memory promote --limit 5
openclaw memory status --deep

Manual memory promote uses deep-phase thresholds by default unless overridden with CLI flags.

Explain why a specific candidate would or would not promote:

openclaw memory promote-explain "router vlan"
openclaw memory promote-explain "router vlan" --json

Preview REM reflections, candidate truths, and deep promotion output without writing anything:

openclaw memory rem-harness
openclaw memory rem-harness --json

Key defaults

All settings live under plugins.entries.memory-core.config.dreaming.

Key Default
enabled false
frequency 0 3 * * *

Phase policy, thresholds, and storage behavior are internal implementation details (not user-facing config).

See Memory configuration reference for the full key list.

Dreams UI

When enabled, the Gateway Dreams tab shows:

  • current dreaming enabled state
  • phase-level status and managed-sweep presence
  • short-term, grounded, signal, and promoted-today counts
  • next scheduled run timing
  • a distinct grounded Scene lane for staged historical replay entries
  • an expandable Dream Diary reader backed by doctor.memory.dreamDiary

Troubleshooting

Dreaming never runs (status shows blocked)

The managed dreaming cron rides the default agent's heartbeat. If heartbeat is not firing for that agent, the cron enqueues a system event that nobody consumes and dreaming silently does not run. Both openclaw memory status and /dreaming status will report blocked in that case and name the agent whose heartbeat is the blocker.

Two common causes:

  • Another agent declares an explicit heartbeat: block. When any entry in agents.list has its own heartbeat block, only those agents heartbeat — the defaults stop applying to everyone else, so the default agent can go silent. Move the heartbeat settings to agents.defaults.heartbeat, or add an explicit heartbeat block on the default agent. See Scope and precedence.
  • heartbeat.every is 0, empty, or unparseable. The cron has no interval to schedule against, so the heartbeat is effectively disabled. Set every to a positive duration such as 30m. See Defaults.