{"id":"12b6f010-6391-432c-90c9-9426295598b3","entityType":"agent","slug":"clawhub-lzyling-signal-dreaming","name":"signal-dreaming","canonicalUrl":"https://www.xpersona.co/agent/clawhub-lzyling-signal-dreaming","canonicalPath":"/agent/clawhub-lzyling-signal-dreaming","generatedAt":"2026-10-10T07:18:36.618Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":null},"description":"Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.8K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17b20pjwm3e6gpzdhjbtfr3es84rtrw:signal-dreaming","sourceUrl":"https://clawhub.ai/lzyling/signal-dreaming","homepage":"https://clawhub.ai/lzyling/skills/signal-dreaming","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/lzyling/signal-dreaming","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/lzyling/skills/signal-dreaming","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":40,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"signal-dreaming technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":null},"stars":null,"forks":null,"downloads":1845,"packageName":null,"latestVersion":"5.0.3","tractionLabel":"1.8K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T00:16:41.823Z","lastCrawledAt":"2026-10-10T00:16:41.823Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T00:16:41.823Z","lastVerifiedAt":null,"highlights":[{"version":"5.0.3","createdAt":"2026-09-25T20:38:17.484Z","changelog":"5.0.3: the batch cap now says what it counts (daily logs only; L2 files touched by a run are reported separately); a forgotten topic file gets one way back into view; backup growth is reported without being touched. No change to what a run produces.","fileCount":7,"zipByteSize":40363},{"version":"5.0.2","createdAt":"2026-09-11T15:36:26.810Z","changelog":"Aligns the cron template's timeoutSeconds with the batch cap it has to finish inside, and defines what a run does when the selection comes back empty. No change to what a run produces.","fileCount":6,"zipByteSize":34542},{"version":"5.0.1","createdAt":"2026-09-11T14:52:15.472Z","changelog":"Stops a large watermark date from blocking every later date forever, and makes two silent write failures visible. No change to what a run produces.","fileCount":6,"zipByteSize":33402},{"version":"5.0.0","createdAt":"2026-09-11T10:49:18.770Z","changelog":"Makes the index a domain-level summary rather than a mirror of the L2 topic list, and curates on every run rather than only when the index is over budget. Hardens the audit helper against arithmetic-expansion command injection via its numeric overrides, and invokes it through bash so a fresh install without the executable bit still works. Breaking: the first run after upgrading restructures an existing MEMORY.md, so run it manually and supervised the first time.","fileCount":6,"zipByteSize":30193},{"version":"4.0.3","createdAt":"2026-09-06T11:38:37.863Z","changelog":"Restate the secret guard's scope as a limit on the guard rather than a list of exclusions. The three constraints are unchanged: the credential list is exhaustive, it is not to be extended, and nothing outside it raises the manual-review alert. 4.0.2 stated this by enumerating categories of ordinary content as out of scope; that enumeration is removed. The guard now describes itself as covering authentication material and explicitly not acting as a privacy classifier, with anything else following the workspace's own conventions. Behaviour is identical to 4.0.2; references/dream-audit.sh is unchanged.","fileCount":5,"zipByteSize":22311},{"version":"4.0.2","createdAt":"2026-09-06T10:16:15.927Z","changelog":"Scope the secret guard to credentials only. The secret list is now stated as exhaustive, with personal and transactional detail (addresses, phone numbers, order/tracking/refund numbers, pickup codes) named explicitly out of scope, and the manual-review alert forbidden for anything outside the list. The previous 'treat these as sensitive by default' wording read as a floor rather than a boundary, so a run could withhold ordinary personal detail from L2 and request manual review of a daily log — a request that cannot resolve, since the protocol never edits daily logs. references/dream-audit.sh is unchanged.","fileCount":5,"zipByteSize":22147},{"version":"4.0.1","createdAt":"2026-09-02T18:27:14.758Z","changelog":"Fix a guardian that skipped same-day appends: it tested for logs dated strictly after the watermark while Phase 1 selects >=, so work waiting inside the debounce window was reported as no-op. Session-reset hooks writing YYYY-MM-DD-HHMM.md files make same-day additions routine, so this was hitting regularly. The two log conditions are now one, reusing Phase 1's >= and testing whether any selected log changed since the last entry's heading timestamp. Also resizes the batch byte cap from 512 KB to 192 KiB, derived from an 1800s task timeout at a measured 0.12-0.21 KiB/s -- recompute it if your timeout differs.","fileCount":5,"zipByteSize":21110},{"version":"3.0.0-rc.3","createdAt":"2026-07-30T08:41:22.856Z","changelog":"Candidate-staged V3 memory consolidation with autonomous bounded index compaction, live-scope concurrency rejection, deterministic commit/state advancement, and V2/V3 transaction inventory compatibility.","fileCount":20,"zipByteSize":54251}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17b20pjwm3e6gpzdhjbtfr3es84rtrw:signal-dreaming","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17b20pjwm3e6gpzdhjbtfr3es84rtrw:signal-dreaming` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/lzyling/signal-dreaming before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T07:18:36.613Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lzyling-signal-dreaming/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":null},"readme":"Skill: signal-dreaming\n\nOwner: lzyling\n\nSummary: Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.\n\nTags: automation:1.0.0, consolidation:1.0.0, dreaming:1.0.0, latest:5.0.3, maintenance:1.0.0, memory:1.0.0, rc:3.0.0-rc.3\n\nVersion history:\n\nv5.0.3 | 2026-09-25T20:38:17.484Z | user\n\n5.0.3: the batch cap now says what it counts (daily logs only; L2 files touched by a run are reported separately); a forgotten topic file gets one way back into view; backup growth is reported without being touched. No change to what a run produces.\n\nv5.0.2 | 2026-09-11T15:36:26.810Z | user\n\nAligns the cron template's timeoutSeconds with the batch cap it has to finish inside, and defines what a run does when the selection comes back empty. No change to what a run produces.\n\nv5.0.1 | 2026-09-11T14:52:15.472Z | user\n\nStops a large watermark date from blocking every later date forever, and makes two silent write failures visible. No change to what a run produces.\n\nv5.0.0 | 2026-09-11T10:49:18.770Z | user\n\nMakes the index a domain-level summary rather than a mirror of the L2 topic list, and curates on every run rather than only when the index is over budget. Hardens the audit helper against arithmetic-expansion command injection via its numeric overrides, and invokes it through bash so a fresh install without the executable bit still works. Breaking: the first run after upgrading restructures an existing MEMORY.md, so run it manually and supervised the first time.\n\nv4.0.3 | 2026-09-06T11:38:37.863Z | user\n\nRestate the secret guard's scope as a limit on the guard rather than a list of exclusions. The three constraints are unchanged: the credential list is exhaustive, it is not to be extended, and nothing outside it raises the manual-review alert. 4.0.2 stated this by enumerating categories of ordinary content as out of scope; that enumeration is removed. The guard now describes itself as covering authentication material and explicitly not acting as a privacy classifier, with anything else following the workspace's own conventions. Behaviour is identical to 4.0.2; references/dream-audit.sh is unchanged.\n\nv4.0.2 | 2026-09-06T10:16:15.927Z | user\n\nScope the secret guard to credentials only. The secret list is now stated as exhaustive, with personal and transactional detail (addresses, phone numbers, order/tracking/refund numbers, pickup codes) named explicitly out of scope, and the manual-review alert forbidden for anything outside the list. The previous 'treat these as sensitive by default' wording read as a floor rather than a boundary, so a run could withhold ordinary personal detail from L2 and request manual review of a daily log — a request that cannot resolve, since the protocol never edits daily logs. references/dream-audit.sh is unchanged.\n\nv4.0.1 | 2026-09-02T18:27:14.758Z | user\n\nFix a guardian that skipped same-day appends: it tested for logs dated strictly after the watermark while Phase 1 selects >=, so work waiting inside the debounce window was reported as no-op. Session-reset hooks writing YYYY-MM-DD-HHMM.md files make same-day additions routine, so this was hitting regularly. The two log conditions are now one, reusing Phase 1's >= and testing whether any selected log changed since the last entry's heading timestamp. Also resizes the batch byte cap from 512 KB to 192 KiB, derived from an 1800s task timeout at a measured 0.12-0.21 KiB/s -- recompute it if your timeout differs.\n\nv3.0.0-rc.3 | 2026-07-30T08:41:22.856Z | user\n\nCandidate-staged V3 memory consolidation with autonomous bounded index compaction, live-scope concurrency rejection, deterministic commit/state advancement, and V2/V3 transaction inventory compatibility.\n\nv3.0.0-rc.1 | 2026-07-23T15:04:45.960Z | user\n\nRebuild from the proven v1.3.1 single-owner workflow with deterministic daily-log hashing, bounded bootstrap, true no-op behavior, guarded backups, recovery state, and deterministic audits.\n\nv2.0.1 | 2026-07-19T16:48:03.107Z | user\n\nUse explicit Bash invocation for bundled shell wrappers so ClawHub archive installs work without executable bits.\n\nv2.0.0 | 2026-07-19T16:03:58.210Z | user\n\nStable native-first v2: fail-closed OpenClaw 2026.7.1+ preflight, canonical path containment, staged transactions and recovery, reversible v1.3.1 migration, gate-driven scheduling, and 61-assertion macOS/Linux acceptance.\n\nv2.0.0-rc.1 | 2026-07-19T15:14:10.459Z | user\n\nNative-first v2 release candidate: fail-closed OpenClaw 2026.7.1+ preflight, canonical path containment, staged transactions and recovery, reversible v1.3.1 migration, gate-driven scheduling, and a 61-assertion isolated self-test validated on macOS and Linux.\n\nv1.3.1 | 2026-04-26T03:01:40.069Z | user\n\nMove dream backups outside memory indexing to .backup/memory-dreams with .bak suffixes, clarify audit is a lightweight sanity check rather than full DLP, expand common credential patterns, align SKILL frontmatter name with signal-dreaming, and remove auxiliary CHANGELOG from the skill package.\n\nv1.3.0 | 2026-04-26T02:56:30.752Z | user\n\nAdd Phase 1.5 quality gates, L2 write-before-backup, post-write audit, and dream-audit helper to prevent legacy/current project mixing, closed TODO resurfacing, and secret propagation.\n\nv1.2.0 | 2026-04-19T00:01:18.760Z | user\n\n2026.4.15 compatibility: fix cron template skill directory name (memory-dreaming→signal-dreaming); exclude memory/dreaming/** from daily log scan; skip built-in Dreaming phase blocks (Light Sleep/REM Sleep) in inline-mode logs; add Compatibility section explaining difference from memory-core built-in Dreaming\n\nv1.1.0 | 2026-04-18T23:55:15.762Z | user\n\nFix: use exec heredoc for dream-log append instead of edit tool; add absolute path guidance throughout; isolated sessions must not use ~ paths\n\nv1.0.0 | 2026-04-13T05:05:44.385Z | user\n\nInitial release: signal-driven three-phase memory consolidation. Uses OpenClaw recall signals (short-term-recall.json) to prioritize what gets consolidated into long-term memory.\n\nArchive index:\n\nArchive v5.0.3: 7 files, 40363 bytes\n\nFiles: references/changelog.md (21821b), references/dream-audit.sh (6893b), references/dream-protocol.md (52026b), references/memory-stack.md (2415b), skill-card.md (1810b), SKILL.md (9609b), _meta.json (134b)\n\nFile v5.0.3:SKILL.md\n\n---\nname: \"signal-dreaming\"\ndescription: \"Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.\"\n---\n\n# Signal Dreaming\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\nDaily session logs accumulate raw detail. This skill reads the ones written since the last run, promotes what matters into durable topic files, and keeps the top-level index worth reading at session start.\n\n## Memory Architecture Assumed\n\nA two-layer memory layout:\n\n- **Daily logs** (`memory/YYYY-MM-DD*.md`) — raw session notes, read-only, never moved or deleted\n- **L2 topic files** (`memory/<topic>.md`) — curated durable knowledge per subject (e.g. `memory/clash-verge.md`)\n- **Index** (`MEMORY.md`) — high-level status with pointers down to L2\n\nIf you are starting fresh, create `MEMORY.md` and `memory/dream-log.md` before the first run. L2 files are created on demand.\n\n`memory/dream-log.md` doubles as the only state this skill keeps: entries record a **`Consolidated through:`** date — the newest daily log already folded into memory. The next run scans back for the most recent entry carrying that field to know where to resume. There is no state file, no lock file, and nothing to migrate or repair.\n\n## Quick Start\n\n### Manual dream\n\nTell your agent:\n\n> \"Run a memory dream consolidation. Follow the protocol in `<SKILL_PATH>/references/dream-protocol.md`. Workspace root: `<YOUR_WORKSPACE_PATH>`.\"\n\nOr just *\"run a dream consolidation\"* — if this skill is loaded, the agent will know what to do.\n\n### Automated daily dream (cron)\n\n```json\n{\n  \"name\": \"daily-dream\",\n  \"schedule\": { \"kind\": \"cron\", \"expr\": \"0 7 * * *\", \"tz\": \"<YOUR_TIMEZONE>\" },\n  \"sessionTarget\": \"isolated\",\n  \"payload\": {\n    \"kind\": \"agentTurn\",\n    \"timeoutSeconds\": 1800,\n    \"message\": \"Run a memory dream consolidation. Read <SKILL_PATH>/SKILL.md and <SKILL_PATH>/references/dream-protocol.md in full, then follow the protocol phases in order. Workspace root: <YOUR_WORKSPACE_PATH>. Do not modify cron jobs, agent config, the Gateway, any daily log, or memory/dreaming/**. End your final response with a one-line dream summary — the cron delivery mechanism will auto-announce it.\"\n  },\n  \"delivery\": { \"mode\": \"announce\", \"channel\": \"<CHANNEL_TYPE>\", \"to\": \"<CHANNEL_ID>\" }\n}\n```\n\nSet `expr` and `tz` to when your human is asleep. `timeoutSeconds` and the batch cap are a pair — lower one without the other and a batch is admitted that cannot finish.\n\n## Three-Phase Safety Model\n\n| Phase | Writes | Purpose |\n|-------|--------|---------|\n| **Sense** | ❌ None | Select logs since the watermark, plan the work |\n| **Consolidate** | ✅ L2 files only | Promote content into topic files |\n| **Settle** | ✅ MEMORY.md + dream-log.md | Update index, write diary entry |\n\nPhase 1 is always read-only. An error in Sense never corrupts files.\n\n## Quality Gates\n\nA read-only planning checkpoint runs before any write: **topic identity** (do not merge legacy and current projects on name similarity), **lifecycle** (closed work must not reappear as an active TODO), **secret propagation** (never promote credentials into curated memory; the credential list is exhaustive and the guard is not a privacy classifier), **backup** (existing L2 files and `MEMORY.md` are copied to `<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/` with a `.bak` suffix first), and a **post-write audit** reporting size, structure, lifecycle separation, and credential patterns without gating the run. `references/dream-audit.sh` covers the common checks; it is not full DLP.\n\n## Failure Philosophy\n\nThis protocol is written for an agent to follow, not for a program to enforce. It fails **soft**:\n\n- Do the work you can do, then report what you skipped and why.\n- Diagnostics inform the final summary. They never block consolidation.\n- An oversized `MEMORY.md` is the reason to run, not a reason to abort.\n- Limits are derived from the runtime, never hard-coded into the protocol.\n- Never wait on an answer that cannot arrive. A scheduled run has nobody to ask, so out-of-bounds work is dropped and reported, not blocked on.\n\nExactly two conditions cancel writes: a **failed backup**, or a **write plan reaching outside the allowed paths** — and the second cancels only those targets, not the run.\n\nThe secret guard is the one hard block on content — and it blocks the offending value, not the run.\n\n## Where this sits in OpenClaw's memory stack\n\nThis skill works on the Markdown layer only — `MEMORY.md` plus `memory/*.md`. It **never reads or writes `memory/dreaming/**` or `memory/.dreams/**`**, which belong to memory-core's own built-in Dreaming, and it skips `## Light Sleep` / `## REM Sleep` blocks found inside a daily log. The two systems are independent: run this one with built-in Dreaming on or off.\n\nHow they differ, which adjacent components (`memory-wiki`, alternate backends, database-first state, automatic memory flush) are deliberately untouched, and what the version note covers: `references/memory-stack.md`.\n\nTool names in the protocol (`exec`, `edit`) are OpenClaw's; on another harness use its equivalents.\n\n## Key Rules\n\n- **Never move or delete daily logs** — archiving breaks `memory_search` indexing\n- **dream-log.md is Markdown** — append text directly, never write JSON\n- **Never copy credentials into curated memory** — omit/redact and alert instead. The secret list is **exhaustive**: authentication material only. This guard is not a privacy classifier; anything outside the list follows the workspace's own conventions and never raises a secret alert\n- **Keep lifecycle state sticky** — closed/archived/snowed lines stay non-active\n- **Back up before rewriting** `MEMORY.md` and any existing L2 file\n- **MEMORY.md budget is derived, never hard-coded** — headroom = `min(bootstrapMaxChars, bootstrapTotalMaxChars − other bootstrap files)`; target = **80%** of it (`SD_INDEX_TARGET_PCT`, a knob), the remaining 20% being growth slack. Count characters, not bytes. Crossing the target means sink detail into L2 — it never blocks and never justifies deleting facts\n- **Curate on every run, not only when over budget** — being under the target is not an answer to \"does this still belong at index level\". A target consulted only when exceeded becomes a floor the index grows to meet\n- **The index is not a list of topic files** — group entries by the question a reader is asking; the topic identity guard governs L2 files, not index structure. Closed and archived work keeps one line plus its L2 pointer\n- **Place every entry in a named section** — appending to the end of the file lets whatever heading happens to be last redefine what the entry means\n- **Batch limit**: 32 logs or 192 KiB of **daily-log bytes** per run, cut on date boundaries. Sized so one batch fits inside the task timeout; a single date that exceeds it on its own is still processed whole. When resuming after a gap it takes the oldest first so the watermark advances contiguously. A run also reads and rewrites the L2 files it promotes into and this does not count them — leave margin, and do not gate on it, since those files are only known once the logs are read. Record log bytes, L2 bytes and wall-clock every run\n- **Check reachability both ways** — every rule here walks from an index entry down to its topic file, so a file no entry names leaves this protocol's view for good. Report unreachable topic files *that sessions recently modified*; never auto-promote them\n- **On the watermark date, re-read only the files that changed** — `>=` exists to catch same-day appends, not to re-admit the whole date at full size every run. Compare mtimes against the heading of the newest entry carrying `Consolidated through:`, never a later backfill heading\n- **Write L2 files one at a time, and read back what you claim to have written** — batching content through one command invocation loses whole batches to encoding failures, and a step reporting success is not evidence its file changed\n\n## Full Protocol\n\nSee `references/dream-protocol.md` for the complete three-phase workflow, quality gates, dream-log format, and safety rules.\n\n## Version Note\n\n**5.0.3** names what the batch cap actually counts, gives a forgotten topic file one way back into view, and reports backup growth without touching it. No change to what a run produces.\n\n**5.0.2** aligns the cron template's `timeoutSeconds` with the batch cap it has to finish inside, and defines what a run does when 5.0.1's selection rule returns nothing at all.\n\n**5.0.1** stops a large watermark date from blocking every later date forever, and makes two silent write failures visible. The deadlock needed all three of `>=` selection, oldest-first resumption and the unsplittable date boundary — each correct alone. Only changed files on the watermark date are re-read now.\n\n**5.0.0** makes the index a domain-level summary rather than a mirror of the L2 topic list, and curates on every run rather than only when the index is over budget.\n\nBoth halves change what this skill produces, so an existing workspace will see its `MEMORY.md` restructured on the first run after upgrading. That rewrite is backed up like any other (Phase 3.1), but it is not something the human asked for on that day — hence the major version. **Run it manually the first time, with someone watching**, then hand it back to the schedule; later runs only touch the increment.\n\nFull release history, with the reasoning behind each change: `references/changelog.md`.\n\nFile v5.0.3:_meta.json\n\n{\n  \"ownerId\": \"kn73gt88jsjv66v89gzkwxcfrn84swtx\",\n  \"slug\": \"signal-dreaming\",\n  \"version\": \"5.0.3\",\n  \"publishedAt\": 1790368697484\n}\n\nFile v5.0.3:references/changelog.md\n\n# Signal Dreaming — Version History\n\nEach entry states what changed and why. Where a release fixed something that had\nbeen running wrong, the entry says what the wrong behaviour cost, because that is\nthe part that stops it being reintroduced.\n\n---\n\n## Known issues (open, not yet fixed)\n\n**The batch-encoding rule is scoped to writes, so read-only checks walk past it.**\n`dream-protocol.md` tells a run not to batch several **L2 writes** through one shell\ninvocation carrying the content as an argument or a here-document, because that path\nlost whole batches to a non-UTF-8 failure twice. The 5.0.3 first run (2026-09-13) hit\nthe same failure a fourth time — but on an *optional read-only verification* command\n(full-file diff reconstruction plus a source-hash final check), which the rule's\nwording does not cover. The fault is in constructing the command; it has nothing to\ndo with whether the command reads or writes.\n\nWhat the failure looks like, recorded so the next run identifies it in one step\nrather than going to inspect the locale: `SyntaxError: Non-UTF-8 code starting with\n'\\xXX'`. Diagnosis on 2026-09-13 established that this is the signature of a\ntruncation landing *inside* a multi-byte character — removing one or two trailing\nbytes of a three-byte CJK character reproduces it exactly, while removing all three\nyields a different error (`EOL while scanning string literal`). A bare locale is\n**not** the cause: with `LANG` unset and every `LC_*` at `C`, PEP 540 still enables\nUTF-8 mode automatically, and defeating that produces a `UnicodeEncodeError` on\noutput, not a `SyntaxError` on input.\n\nThe truncation threshold is not known. A single line of 4,491 bytes of pure CJK\nsurvived intact on the harness used for the diagnosis, but that is a different\ntransport from the `exec` tool where every failure occurred, and the failing command\ntext was not persisted.\n\nIntended fix, deferred to the next iteration by the operator's decision on\n2026-09-13: widen the rule from \"L2 writes\" to any command construction carrying a\nlong run of non-Latin content, read-only checks included, and record the error\nsignature in the protocol. Until then the proven workaround still stands and is\nalready written down — write one file at a time with the editing tool; six L2\ntargets went through first try that way after two batched runs lost everything.\n\n---\n\n## 5.0.3\n\nThree fixes to things that were never written down, rather than written wrong.\n\n**The batch cap now says what it counts.** It read `192 KiB of total input`, which\nis not what it measures: only daily logs are counted, while a run also reads and\nrewrites every L2 file it promotes into. The gap surfaced as throughput that\nlooked like it had degraded — one run spent 508 s on 50.6 KiB of logs, `0.10 KiB/s`,\nbelow the floor of the band 4.0.1 measured. Nothing had degraded. Adding the L2\nfiles the same run touched gives 77.0 KiB and `0.15 KiB/s`, back inside the band.\nThe risk was never the inaccuracy itself but what anyone recomputing the cap would\nconclude from it, since 4.0.1 ties the cap to the timeout and explicitly invites\nthat recomputation.\n\nFolding L2 bytes into the cap as a gate is the obvious fix and is not available:\nPhase 1.4 decides which L2 files a run will touch from the content of logs Phase\n1.3 has not read yet, so at the moment the cap is applied the number does not\nexist. Reserving a fixed allowance instead would be the hard-coded constant the\nDesign Stance was written against. So the cap is named honestly, the margin it\nneeds is stated as something to derive later, and Phase 1.4 and Phase 3.4 record\nlog bytes, L2 bytes and wall-clock on **every** run rather than only on an\noverrun — a measurement taken only when something goes wrong is sampled exactly\nwhere it is least representative of an ordinary night.\n\n**A forgotten topic file can be noticed again.** Phase 1.6 walks from each index\nentry to the file it points at, and nothing walked back. A topic file that no\nentry names was therefore examined by no phase of this protocol — not retired,\nnot referenced, not judged stale, simply out of view and with nothing able to\nreturn it. In one workspace 34 of 98 topic files were unreachable from the index,\namong them a proxy-configuration handbook updated four days earlier and still used\nby a skill, while the index held no mention of the subject. Nothing was lost:\n`memory_search` still reaches those files. What was lost is the session that\nstarts without knowing to look.\n\nA new Phase 1.7 subtracts the index's pointers from the topic directory and\nreports only the difference that sessions have modified since the frontier,\nmeasured before any write — so a recent mtime means ordinary session work rather\nthan this run's own Phase 2. Two limits are stated in the protocol because both are easy to\nlose later: appearing on the list is not a reason to add a pointer, or the index\ngrows back into the topic-directory mirror 5.0.0 removed, one entry per night\nwhile looking like maintenance; and files orphaned before this rule existed carry\nold mtimes and never appear, because draining a backlog is a one-off\nreconciliation a human asks for with the list in front of them, not a report\nre-read every night until it is ignored.\n\n**Backup growth is reported, and still never touched.** `.backup/memory-dreams/`\nhas no retention rule and does not get one. It had reached 99 directories and\n32 MiB in one workspace — the kind of number that invites an automatic prune. It\nshould not: these backups are the recovery path for every write this protocol\nmakes, including the hard stop that cancels a run; a pass whose job is rewriting\nmemory is the wrong process to hold the eraser for its own safety net; and no\nruntime value exists to derive a retention period from, so any cut-off would be\ninvented rather than measured. The audit helper prints `backup_dirs` and\n`backup_kib` with no threshold and no NOTE, and a human decides.\n\n**Orientation moved out of `SKILL.md`.** Adding two rules took the always-loaded\nfile from 9,957 characters to 10,930, over the 10,000 the authoring standard\nsets — and 9,957 was already only 43 under, so the budget had no room for any\nrule at all. The rules were folded into the entries they belong to, and the\nstack-orientation section moved to `references/memory-stack.md`: a comparison\nwith built-in Dreaming and a list of components this protocol does not touch is\nreference, not procedure, and it carries a version number that dates. The one\noperational line in it — never touch `memory/dreaming/**` or `memory/.dreams/**`\n— stayed behind. `SKILL.md` is now 9,547.\n\nNo change to what a run produces. The index keeps the shape 5.0.0 gave it.\n\n---\n\n## 5.0.2\n\nTwo fixes, both to things that were inconsistent rather than wrong in themselves.\n\n**The cron template's timeout now matches the batch cap.** 4.0.1 sized the\n`192 KiB` cap against an `1800 s` timeout and said so, but left the template in\n`SKILL.md` at `900 s`. At the slower end of the measured throughput that admits\nroughly `108 KiB` — so a batch this protocol considers legal gets the run killed\npartway, most likely inside Phase 2, leaving L2 files written, the index and the\ndiary untouched, and the watermark not advanced. Nothing records that it happened.\nThree releases carried the mismatch because the workspace it was developed on runs\n`1800 s`, so the template was never the thing being exercised. Both numbers are\nstated together now, with a line saying they move as a pair.\n\n**An empty selection now has a rule.** 5.0.1 made the state reachable and said\nnothing about it: once the watermark date contributes only its changed files, a run\nwhose only candidate is an unchanged watermark date selects nothing at all. Before\nthat, `>=` guaranteed there was always something. Undefined, the likely improvisation\nis the damaging one — carry the empty batch into Phase 3 and write a diary entry\nwhose `Consolidated through:` names logs the run never read.\n\nSo: an empty selection ends the run at Phase 1, writes nothing, and leaves the\nwatermark where it is. It does **not** append a `SKIP` comment. The guardian may,\nbecause the diary is the only channel it has; a run that reached Phase 1 has its\nfinal summary, and nothing in this protocol reads those comments — Phase 1.1\nanchors on Dream headings. Phase 1 is declared read-only in three places and stays\nthat way.\n\n---\n\n## 5.0.1\n\nStops a large watermark date from blocking every later date forever, and makes two\nsilent write failures visible.\n\n**The watermark date is no longer re-admitted whole.** Phase 1.2 selects `>=` so\nthat same-day appends are not missed, and justifies the cost as re-reading one\nalready-seen file being cheap. Applied to a whole date it is not cheap: every file\nof that date returned to the batch at full size on every run, changed or not. With\noldest-first resumption and the unsplittable date boundary, a date large enough to\nfill the batch on its own then filled it permanently — three consecutive runs each\nre-read the same 221 KiB day, and the following day's four logs were deferred every\ntime. Each of the three rules is right; the combination is what deadlocks, and it\nneeds a workspace that writes more in one day than a batch can hold, which is why\nit took this long to surface.\n\nThe fix asks the modification question per file instead of per date, with the\n`find -newermt` test the guardian already uses. Unchanged files on the watermark\ndate contribute nothing to the batch; changed ones are still selected, so the `>=`\ndefence is intact. Skipping the whole date on the same evidence was the obvious\nalternative and the wrong one: a false negative from mtime costs the guardian one\nno-op run, but costs a date-level skip everything appended to that day,\npermanently. Per-file keeps the consequence at the guardian's level.\n\nOne detail that is easy to get backwards: the comparison timestamp must come from\nthe newest entry carrying `Consolidated through:`, not the newest entry. A backfill\nhas a later heading and deliberately left the frontier alone, so comparing against\nit under-reports which files changed — and under-reporting here drops them as\nalready-read. The guardian escapes the same trap by refusing to skip after a\nbackfill; this rule has no do-nothing option, so it has to name the right entry.\n\n**L2 files are written one at a time.** Batching several promotions through a\nsingle command invocation that carries the content corrupted non-UTF-8 text and\nlost the entire batch, twice. The second occurrence skipped six targets, so detail\nthe run had correctly decided to sink stayed in the index and inflated it by\nroughly 3,000 characters. Per-file edits of the same content succeeded first try.\nThe fault is in building the command, so it stays invisible until the content is\nlong and non-Latin — exactly when a run has the most to lose.\n\n**Writes are verified by reading them back.** A diary trim reported success against\na file it had not changed, leaving 31 entries under a cap of 30. The result is a\nvalid file that is merely one entry too long, so nothing downstream notices. The\naudit helper already printed the entry count and nobody compared it; it now warns\nwhen the count exceeds `SD_DREAM_LOG_MAX` (default 30, same integer-only handling\nas the other overrides). Phase 3.6 states the general form: a step reporting\nsuccess is not evidence its file changed, so check the artifact rather than the\naction.\n\nNo change to what a run produces — the index keeps the shape 5.0.0 gave it.\n\n---\n\n## 5.0.0\n\nMakes the index a domain-level summary rather than a mirror of the L2 topic list,\nand curates on every run rather than only when the index is over budget.\n\nBoth halves change what the protocol produces, so an existing workspace will see\nits `MEMORY.md` restructured on the first run after upgrading. That restructuring\nis backed up like any other write (Phase 3.1), but it is not something the human\nasked for on that particular day — hence the major version.\n\n**Curation stopped being conditional on size.** The protocol said that below its\ntarget, size was \"not a consideration at all\", then asked — correctly — whether\neach section still belonged at index level. In practice the first clause won: the\nrun reached the target, stopped, and never got to the question. Measured across\neight consecutive runs on a CJK workspace, the index finished between 92% and 100%\nof a 16,000-character target every time, while five archived projects kept full\nentries throughout. A target that is only ever consulted when exceeded is not a\nceiling; it is a floor, and the index grows until it finds it.\n\n**The index stopped mirroring the topic directory.** Phase 1.5's topic identity\nguard forbids merging L2 files that describe materially different things. That\nguard is right, and it was being applied one layer too high: every new topic file\nearned its own index entry, nothing ever merged, and eleven of thirty-one entries\nin one workspace turned out to be facets of a single line of work. The guard now\nsays explicitly that it governs L2 files and not index structure, and Phase 3.2\ngroups index entries by the question a reader is asking.\n\n**Phase 1 now reads L2 modification times.** Deciding whether an entry still\ndescribes something live was unanswerable from the entry itself — an index says a\nproject is active in the same words the day the work stops. The modification time\nof the file an entry points at is a fact produced by Phase 2 rather than a field\nsomeone has to remember to maintain. A date stamped into the entry was the\nobvious alternative and the weaker one: it survives only until the first run that\nrefreshes every line at once. mtime informs the judgement; it never retires\nanything by itself.\n\n**Entries are placed deliberately.** New entries were appended to the end of the\nfile, which is not a neutral position — it sits under whatever heading happens to\nbe last. An active project entry landed under a standing-rules heading in\nproduction, where later runs would have read it as a permanent rule and never\nconsidered it for consolidation again. Phase 3.2 now assigns a section before\nwriting, and Phase 3.6 checks that every entry sits under a heading matching what\nit is.\n\n**Individual entries are sized.** A total budget says nothing about one entry, so\na section can reach several times the length of its neighbours unnoticed. The\nanchor is `maxPromotedSnippetTokens` — the cap `memory-core` puts on a single\npromoted snippet — read from config rather than copied into this document. It is\nused to find outliers only: entries already at or below it are left alone,\nbecause a per-entry cap used as a target rebuilds the same attractor one level\ndown.\n\n**Finished work keeps one line.** Closed and archived projects are reduced to a\nline plus an L2 pointer. Nothing in the protocol asks about them again, so a full\nentry is pure accumulation.\n\n**Security: the audit helper no longer evaluates its numeric overrides.**\n`references/dream-audit.sh` read `SD_BOOTSTRAP_MAX_CHARS`,\n`SD_BOOTSTRAP_TOTAL_MAX_CHARS` and `SD_INDEX_TARGET_PCT` straight into `$(( ))`.\nBash evaluates an array subscript inside arithmetic expansion as a command, so\n`SD_INDEX_TARGET_PCT='x[$(cmd)]'` executed cmd — confirmed by running it. The\nthree values are now accepted only as plain non-negative integers; anything else\nfalls back to the default and warns, since an override dropped in silence is its\nown defect. Defaults and legitimate overrides behave exactly as before.\n\n**Packaging: the audit helper is invoked through `bash`.** The protocol called\n`references/dream-audit.sh` by path, which needs the executable bit. A ClawHub\narchive does not preserve it, so the call worked on the publishing machine and\nfailed on every fresh install. Naming the interpreter removes the difference.\n\n`SD_INDEX_TARGET_PCT` is unchanged at 80. Lowering it would have moved the\nattractor rather than removing it.\n\nOne consequence is documented so it is not later mistaken for a regression: index\nsize no longer converges on a figure. Run-to-run variation means curation is\ntracking content; a flat line means it has started tracking the target again.\n\nAlso includes the unreleased 4.0.4 fix below.\n\n---\n\n## 4.0.4 (unreleased)\n\nFixes a dream number that stops advancing once the diary reaches its trim cap.\n\nPhase 3 derived the new number by counting `## 🌙 Dream #` headings, while Phase 5\ndeletes the oldest entry whenever the count exceeds 30. The two rules agree only\nwhile the diary is still growing. Once it saturates, the count is permanently 30,\nso every subsequent run writes `#31` — observed in production across four\nconsecutive runs after the cap was reached. The watermark is unaffected, since it\nis read from `Consolidated through:`, so consolidation stays correct while the\ndiary quietly loses monotonic identity and the gap check that depends on it.\n\nPhase 3 now reads the number out of the newest heading and adds 1, and Phase 5\nstates that trimming must never change the next number. Repairing an\nalready-frozen diary means renumbering the affected headings once, from the last\ncorrect entry forward; nothing else in the log changes.\n\n---\n\n## 4.0.3\n\nRestates the 4.0.2 scope without enumerating what falls outside it.\n\n4.0.2 fixed the rule by naming categories of ordinary content as out of scope. The\nrule was right; the enumeration was not. A list of what a memory system will\nretain reads, to someone reviewing the skill rather than running it, as a\ndescription of what it sets out to collect — and the three constraints that\nactually do the work need no examples to be unambiguous: the list is exhaustive,\nit is not to be extended, and nothing outside it raises the alert.\n\nThe scope is now stated as a limit on the guard rather than a judgement about\ncontent. It covers authentication material and is not a privacy classifier;\nanything else follows the workspace's own conventions. Behaviour is identical to\n4.0.2.\n\n---\n\n## 4.0.2\n\nCloses the secret guard's scope.\n\nThe list of what counts as a secret was introduced with *\"treat these as sensitive\nby default\"* — wording that reads as a floor rather than a boundary, so a run could\nclassify ordinary log content as sensitive on its own judgement, withhold it from\nL2, and ask for manual review of the daily log.\n\nThat alert has nowhere to go. This protocol never edits daily logs, so the value\nstays exactly where it already was; the only durable effects are an index missing\nan ordinary fact and a review request that cannot resolve. Repeated, it teaches\nthe human to skim past the alert that does matter.\n\nThe list is now stated as **exhaustive**, and nothing outside it raises the alert.\n\n---\n\n## 4.0.1\n\nFixes a guardian that could skip work it should have done, and sizes the batch cap\nto the task timeout.\n\nThe guardian tested for daily logs dated strictly *after* the watermark, while\nPhase 1 selects `>=` precisely because a watermark-dated log can be appended to\nafter the last run read it. Since the guardian runs first, that defence never\napplied: inside the debounce window a run reported no-op while same-day additions\nwaited for a later pass. Session-reset hooks that write `YYYY-MM-DD-HHMM.md` files\nmake several logs share the watermark date every day, so this is now the normal\ncase rather than an edge one. The two log conditions are merged into one that\nreuses Phase 1's `>=` and asks whether any selected log changed since the last\nentry's heading timestamp.\n\nThe byte cap moves from `512 KB` to `192 KiB`. A cap means something only if the\nbatch it admits can finish before the scheduler kills the run: at a measured\n`0.12-0.21 KiB/s`, an `1800 s` timeout admits roughly `216 KiB`, so the old number\nwas never reachable. **If your task timeout differs, recompute the cap from your\nown slowest observed throughput.** The cap bounds accumulation across days and\ncannot bound a single day — the date-boundary rule still processes an oversized\ndate whole, and such a run now records its actual input size and duration in the\ndream-log.\n\n---\n\n## 4.0.0\n\nReturns to the protocol-only design of the 1.x line and removes the 2.x/3.x script\nlayer entirely.\n\nThose versions compiled the same rules into enforced JavaScript preconditions — a\ntransaction state machine, run manifests, lock files, staged candidate\ndirectories. That layer added no new safety rules; it changed who enforced them,\nand in production turned recoverable conditions into aborted runs: an over-limit\nindex refused to run the pass that shrinks it, and an optional audit's path error\ncancelled an otherwise complete consolidation.\n\nIt also retires the byte-based index target inherited from 1.x. That `8 KB` was\nwritten where a byte and a character were the same thing; the real limit is\n`bootstrapMaxChars`, measured in **characters**. For a CJK workspace at ~1.7 bytes\nper character it enforced about a quarter of the real budget — and 3.x then made\ncrossing it a hard error, so a healthy index could block its own consolidation\npass. 4.0.0 does not substitute another constant: headroom is derived from the\nruntime's own caps, and the target is a configurable percentage of it (default\n80%, leaving a growth band). A hard-coded threshold caused the original damage, so\nthe protocol no longer contains one.\n\nUpgrading from 2.x or 3.x: delete any cron payload steps invoking `preflight`,\n`begin`, `finalize`, `run-guard`, or `dream-audit.mjs` and use the template in\n`SKILL.md`. There is no state to migrate. Existing `.backup/memory-dreams/`\ndirectories can stay; nothing reads them. Your existing `dream-log.md` works\nas-is — the watermark is found by scanning back for the newest entry that carries\na `Consolidated through:` field, and if none does, the newest entry's heading date\nis used.\n\nFile v5.0.3:references/dream-protocol.md\n\n# Signal Dreaming — Full Protocol\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\n---\n\n## Design Stance\n\nThis protocol is written **for an agent to follow**, not for a program to enforce.\n\nThat distinction is deliberate and load-bearing. Every rule below is a judgement an agent makes with the actual content in front of it. Do not translate these rules into blocking assertions in code — a rule that says *\"tell the human\"* must not become a rule that says *\"abort the run\"*.\n\nThree consequences, in order of importance:\n\n1. **Fail soft, report loudly.** If something is wrong but memory is not at risk, do the work you can do, then say plainly what you skipped and why. A dream that consolidates 3 of 4 topics and reports the fourth is a success. A dream that refuses to start is not.\n2. **Never let a diagnostic block the main job.** Audits, size checks, and sanity scans exist to inform the final report. None of them is a precondition for consolidating memory.\n3. **Only two things justify stopping before writing:** a failed backup (Phase 2.0 / Phase 3.1), or a write plan that reaches outside the allowed paths — and the second cancels only those targets, not the run. Everything else is a note in the summary.\n\nThe one hard stop that *is* real: **never write a secret into curated memory.** That guard blocks the specific value, not the run.\n\n**Never wait on an answer that cannot arrive.** A scheduled run has no human to consult; blocking on one is indistinguishable from failing.\n\n### Derive limits from the runtime; never hard-code them\n\nEvery threshold in this protocol must be **derived from a value the runtime actually reports**, never written as a standalone constant.\n\nThe distinction matters more than it looks:\n\n- **A hard-coded absolute** (`8 KB`, `10,000 characters`) is frozen at the moment someone typed it. Change the language, the config, or the runtime version, and it silently means something entirely different — while still being enforced with full confidence.\n- **A percentage of a runtime value** moves with what it measures. Raise `bootstrapMaxChars` and the target rises with it. Nothing has to be remembered, migrated, or re-derived by hand.\n\nThis protocol has been burned by the first form twice: an `8 KB` index target written when a byte and a character were the same thing cut CJK workspaces to a quarter of their real budget, and a later version promoted that constant to a hard error, so a healthy index could refuse to run the pass that maintains it. Both felt conservative. Both caused damage no real constraint would have.\n\nSo: name the runtime value, read its current setting, and express the target as a fraction of it. Where no runtime ceiling exists at all, use judgement about the content and state what you decided — do not manufacture a constant to make the decision feel objective.\n\n---\n\n## Prerequisites\n\n- `MEMORY.md` exists at workspace root\n- `memory/dream-log.md` exists (create an empty file if missing)\n\nThat is the whole list. This protocol needs no state file, no lock file, and no prior run to function.\n\n---\n\n## Before Starting: Guardian Check (automated runs only)\n\nRead `memory/dream-log.md` to find the last dream timestamp by locating the most recent `## 🌙 Dream #` heading.\n\n**First run** (no Dream entries found): bypass the guardian entirely and proceed to Phase 1.\n\n**Skip condition**: the last run was `< 20 hours ago` AND Phase 1 would select nothing unread — no daily log dated **on or after** the **watermark** (Phase 1.1) has been modified since the last entry's heading timestamp.\n\nCompare dates against the watermark, never against the heading date. They are the same number on an ordinary day and they diverge exactly when it matters: a backfill entry is stamped with today's heading but deliberately leaves the frontier where it was. Judging by the heading date would skip a run that still has real work waiting behind that frontier.\n\n**Ask Phase 1's question, with Phase 1's `>=` — never a cheaper `>`.** A log dated *on* the watermark can still have been appended to after the last run read it; that is exactly why Phase 1.2 selects `>=` rather than `>`. A guardian that looks only for logs dated strictly *after* the watermark silently overrides that defence: the appended work is invisible to it and waits for whichever later run happens to clear the debounce. Once session-reset hooks are writing `YYYY-MM-DD-HHMM.md` files, several logs share the watermark date every day — same-day additions are the normal case, not an edge one.\n\n**Anchor the modification test to the last Dream entry's heading timestamp, not to a `SKIP` comment.** Skips are not runs; anchoring to one would treat everything written before it as already consolidated, which is this same bug in a new place. The heading is the wrong thing for deciding *which* logs are unread and the right thing for deciding *when* they were last read — two different questions that happen to share one line. Because it records when the run *started*, a file that landed while that run was still working is correctly seen as unread.\n\nUse mtime for the modification test: `find <WORKSPACE_ROOT>/memory -maxdepth 1 -name '????-??-??*.md' -newermt '<heading timestamp>'` lists the candidates; discard any dated before the watermark, and an empty result means there is nothing to do. mtime is not tamper-proof, but its failure directions are the right way round — a false positive costs one extra no-op run, while a false negative needs someone to deliberately restore an old timestamp. Content hashes would be exact, and would hand this protocol the state file it otherwise does not need.\n\n**If the newest entry carries no `Consolidated through:` field, do not skip.** That means the last run was a backfill, which by design consolidated history rather than advancing the frontier — so ordinary work may still be outstanding.\n\nThe real condition is the material one — nothing new past the watermark means nothing to do. The 20 hours is debounce sized for a once-daily schedule: it lets a run that is merely early exit quietly, while still forcing a pass roughly once a day even when no log changed. On a different cadence, size it to just under your interval.\n\nIf skipping: use `exec` to append `<!-- SKIP · YYYY-MM-DD HH:MM · reason -->` to `<WORKSPACE_ROOT>/memory/dream-log.md` and stop. Always use absolute paths — never `~`.\n\nManual triggers always bypass the guardian.\n\n---\n\n## Phase 1 · Sense — Read Only, No Writes\n\n**Goal**: build a priority list without touching any file.\n\n**Distinguishing file types:**\n- **Daily logs**: match `memory/YYYY-MM-DD*.md` (e.g. `2026-04-13.md`, `2026-04-13-clash-fix.md`)\n- **L2 topic files**: `memory/<topic>.md` files that do NOT match a date pattern (e.g. `memory/clash-verge.md`, `memory/business.md`)\n- **⛔ Not daily logs**: `memory/dreaming/**` and `memory/.dreams/**` (built-in memory-core Dreaming output and internal state) — do NOT process these as daily logs or L2 files; skip entirely\n\n### 1. Find the watermark\n\nEverything this protocol needs to remember between runs lives in one line of `memory/dream-log.md`.\n\nScan entries from newest to oldest and take the **first one that carries a `Consolidated through:` field**. That date is the **watermark** — the newest daily log already folded into memory.\n\n- **Scan back, do not stop at the newest entry.** Backfill runs deliberately omit the field (see Phase 3.4), so the newest entry may not carry one. Skipping past those to the last real frontier is the whole reason the field is optional.\n- If no entry anywhere in the log carries the field, this workspace predates the format: fall back to the date in the **newest** entry's `## 🌙 Dream #` heading.\n- If dream-log.md is empty or has no Dream entries, there is **no watermark** — this is a first run, handled separately below.\n\nReading only the newest entry would break backfill: a backfill entry is stamped with today's heading date, so falling back to that heading would jump the frontier forward over every log the backfill never touched — silently, and permanently.\n\nThe watermark tracks **how far through the logs you have read**, not when you last ran. Those are different numbers whenever a run defers work, and conflating them is how logs get skipped forever.\n\n**Sanity-check it before use**: if the watermark is later than the newest daily log on disk, a previous run recorded it wrong. Fall back to the newest log's date, proceed normally, and say so in the final summary — a bad watermark is the one failure mode here that would otherwise stay silent.\n\n### 2. Select daily logs\n\n- List all daily log files (`memory/YYYY-MM-DD*.md`) — **exclude** anything under `memory/dreaming/**`\n- Select files with date-based names **on or after** the watermark (use `>=` not `>`)\n\nThe `>=` matters: a log for the watermark date may have been appended to *after* the last run read it. Re-reading one already-seen file is cheap; missing an afternoon's work is not.\n\n**On the watermark date, re-admit only the files that actually changed.** The sentence above assumes the re-read is the cheap one it describes. Applied to the whole date it is not: it re-admits every file of that day at full size on every run, whether or not a byte changed. Combined with oldest-first and the date boundary below, a watermark date large enough to fill the batch on its own then fills it forever, and no later date is ever reached. Seen in production across three consecutive runs that each re-read the same 221 KiB day while the next day's logs waited behind it.\n\nSo ask the modification question per file, using the same instrument the guardian uses:\n\n```bash\nfind <WORKSPACE_ROOT>/memory -maxdepth 1 -name '<watermark-date>*.md' -newermt '<frontier timestamp>'\n```\n\nFiles that come back are unread appends — select them. Files that do not were already read by the run that set the watermark; leave them out and count zero bytes for them. **This test applies to the watermark date alone**; dates strictly after it are always selected in full.\n\n**The frontier timestamp is the heading of the newest entry carrying `Consolidated through:`** — not simply the newest entry. A backfill entry has a later heading and deliberately left the frontier where it was, so comparing against it reports fewer changed files than there are, and the ones it misses are dropped as already-read. The guardian meets the same trap and answers it by refusing to skip after a backfill; this rule has no equivalent of doing nothing, so it has to get the timestamp right instead. If no entry anywhere carries the field, there is no frontier timestamp — select the watermark date in full.\n\nSkipping an unchanged file is not splitting a date. The boundary rule below exists so *unprocessed* work is never stranded behind a watermark that cannot express a partial day; a file the previous run already consolidated is not unprocessed, and leaving it out strands nothing.\n\nIf a daily log contains `## Light Sleep` or `## REM Sleep` blocks (built-in Dreaming `inline` mode output), **skip those sections** — they are not user session notes.\n\n### 3. Apply the batch limit\n\nCap a single run at **32 daily logs** or **192 KiB of daily-log bytes**, whichever comes first. These two numbers bound one agent turn, not the memory system.\n\n**What the cap counts is daily logs, and only daily logs.** Earlier wording called it \"total input\", which it is not: a run also reads and rewrites every L2 file it promotes into, and none of that is measured here. The narrower name is the honest one — what the unmeasured half costs is the subject of the margin note below.\n\n**Size the byte cap against the task timeout, not against comfort.** A cap is only meaningful if the batch it admits can finish before the scheduler kills the run. Measured throughput for this protocol has ranged from `0.12` to `0.21 KiB/s` of input, so a `1800 s` timeout admits roughly `216 KiB` at the slower end; 192 KiB leaves a margin under that. If you change the timeout, recompute the cap from the slowest throughput you have actually observed — a cap chosen independently of the timeout is decoration, because the timeout binds first either way.\n\n**That band counts daily-log bytes only, so it understates the work and reads as drift.** One run consolidated 50.6 KiB of logs in 508 s — `0.10 KiB/s`, below the band's own floor — and looked like the throughput had degraded. It had not. Adding the L2 files the same run read and wrote gives 77.0 KiB and `0.15 KiB/s`, back inside the band. The denominator was right; the numerator was leaving out half the job. So read 192 KiB as a log-bytes cap that has to leave room for L2 work, not as a budget for everything a run touches, and recompute it from **total** bytes rather than log bytes alone.\n\n**L2 cost cannot be folded into this cap as a gate, and the attempt is worth ruling out once.** Which L2 files a run will touch is decided in Phase 1.4, from the content of logs this phase has not read yet — at the moment the cap is applied the number does not exist. Reserving a fixed allowance for it instead would be the hard-coded constant the Design Stance already warns about, invented before there is anything to derive it from. What is available is evidence: Phase 1.4 records the candidate total, Phase 3.4 reports it beside the log bytes and the wall-clock, and a later version sets the margin from a history of runs rather than from whichever single measurement was at hand.\n\n**Always take at least one log, even if that single file exceeds the byte cap.** A cap that can produce an empty batch is a deadlock: the watermark never advances, so that log — and every log behind it — is blocked forever. Read the oversized file, summarise what you can, and note its size in the dream-log.\n\n**Cut the batch on a date boundary, never inside one.** The watermark records a date, so it cannot express \"processed 32 of today's 40 files\". If the cap falls mid-day, keep going until that date's logs are finished, even if it overruns the cap — then stop. Splitting a date leaves the watermark unable to advance past it: the next run reselects the same day, processes the same prefix, and the tail is never reached. Both caps are soft; the date boundary is not.\n\nWhen the selection *does* exceed the cap, which end you take depends on why — but the question only arises when the selection spans more than one date. If a **single** date's logs alone exceed the cap, the boundary rule above has already decided it: process that whole date. There is no end to choose.\n\n**One date can exceed the byte cap on its own, and the boundary rule means you process it anyway.** The cap bounds accumulation across days; nothing bounds a single day but the timeout. When a run overruns the cap for this reason, record the batch's actual input size and wall-clock duration in the dream-log entry. Those two numbers are the only evidence a later run has for whether the cap and the timeout are still sized for how much now gets written per day.\n\n**Resuming after a gap** (a watermark exists): take the **oldest** logs in the selection, in date order. Set `Consolidated through:` to the newest date you actually processed — *not* today's date. The next run selects `>= that date` and continues exactly where this one stopped. Report how many logs remain.\n\nTaking the oldest is what makes the watermark work. Taking the newest would advance it past everything you skipped, and those logs would never be selected again.\n\n**First run** (no watermark): take the **newest** logs instead. A new install should surface recent context immediately, not start a year in the past. Set the watermark to the newest date processed, and state plainly in the dream-log and the final summary how many older logs were left outside the watermark — they will not be picked up automatically.\n\n**Manual backfill**: a human can ask for a specific date range (e.g. *\"consolidate 2026-01 through 2026-02\"*). Process exactly that range and **leave the watermark unchanged** — backfill fills in history behind the frontier, it does not move it.\n\nNever skip a run because there is too much to read.\n\n**An empty selection is a valid outcome, and it ends the run.** This state used to be reachable only through the guardian, because a run always had at least the watermark date to re-read. Now that the watermark date contributes only its changed files, a run can legitimately select nothing — including a manual trigger, which bypasses the guardian by design. When that happens: write nothing at all, leave the watermark exactly where it is, and report a no-op. **Phase 1 stays read-only here as everywhere else.** The guardian writes a `SKIP` line because the diary is its only way to say anything; a run that got as far as Phase 1 has its final summary instead, and nothing in this protocol reads those comments — Phase 1.1 anchors on Dream headings, never on a `SKIP`.\n\nDo not widen the range looking for work, and **do not write a dream-log entry carrying `Consolidated through:`**. Re-stating a watermark for logs nothing was read from records a consolidation that did not happen, and the field is the one thing a later run trusts without checking.\n\n### 4. Identify L2 update candidates\n\n- Match the selected log content to existing L2 topic files\n- Flag L2 files likely needing updates\n- Note topics with no matching L2 file — these may need new ones\n- **Record the candidate set's total size.** This is the run's L2 input — the half Phase 1.3's cap does not measure — and it is knowable here for the first time, because it depends on log content this phase has just read. Carry it to Phase 3.4; it is reported, not enforced\n\n### 5. Check MEMORY.md size\n\nMeasure `MEMORY.md` in **characters** (`wc -m`), not bytes. Also read the workspace's `bootstrapMaxChars` / `bootstrapTotalMaxChars` and the sizes of the other bootstrap files, so Phase 3 can compute headroom and target. Hold all of it for Phase 3. Do not act on any of it yet, and **never treat any size as a reason to stop** — see Phase 3.2.\n\n### 6. Date each index entry against its own topic file\n\nPhase 3 has to decide whether an entry still belongs at index level. That question is unanswerable if the only evidence is the entry's own prose: an index that says a project is active says so in exactly the same words the day the work stops.\n\nSo collect the evidence here. For every index entry carrying an L2 pointer, record the modification time of the file it points at:\n\n```bash\nls -l --time-style=+%Y-%m-%d <WORKSPACE_ROOT>/memory/<topic>.md\n```\n\nThat timestamp is the last run in which this protocol actually wrote something to that topic — a fact produced by Phase 2, not a field anyone has to remember to update. A date written into the entry itself would be the weaker instrument: it survives only as long as every future run resists refreshing it along with the rest of the line, and the first run that refreshes all of them silently destroys the signal.\n\n**mtime is evidence, not a verdict.** A topic can be genuinely active with a quiet fortnight, and a file can be touched without the project moving. Carry the number into Phase 3 as one input to a judgement, never as a rule that retires anything on its own. This is the same instrument, with the same limits, that the guardian already uses.\n\nEntries with no L2 pointer have no evidence available. Note them as such; Phase 3 judges them on content alone and says so.\n\n### 7. Ask the same question in the other direction\n\nEvery rule above walks from an index entry to the file it points at. Nothing walks back. So a topic file that no entry names is not examined by any phase of this protocol — not retired, not referenced, not judged stale. It is out of view, and nothing will bring it back into view on its own.\n\nThe index dropping an entry is normal and usually right: Phase 3.2 groups by domain and deliberately does not mirror the topic directory. What is not right is a topic **the human's own sessions are still writing to** that the index cannot reach. That file is not being summarised at a higher level; it has been forgotten, and the only thing still finding it is a search someone has to think to run.\n\nList the top-level non-date `memory/*.md` files, subtract every one that an index pointer names, and from that difference report only the files modified since the frontier timestamp — the same `find -newermt` instrument Phase 1.2 and the guardian already use. Because Phase 1 has written nothing at this point, a file that recent was changed by ordinary session work rather than by this run's own Phase 2.\n\n**Appearing on that list is not a reason to add a pointer.** It is a reason to ask whether a reader would look for this topic at index level — Phase 3.2's question, answered there on the content. Promoting whatever the list contains rebuilds the index-as-topic-directory that 5.0.0 removed, one entry per run, and it would do so while looking like maintenance.\n\nIn a healthy workspace this list is empty, and it stays empty by catching the next orphan rather than by draining the ones behind it. Files orphaned before this rule existed carry an old mtime and never appear here — that is deliberate. A backlog is a one-off reconciliation a human asks for, with the list in front of them; it is not work a scheduled run should start on its own, and a rule that surfaced all of it every night would be ignored within a week.\n\n**Output (held in memory, nothing written)**: selected log list, the date that will become the new watermark, deferred count, L2 update list and its total size, current index size, per-entry L2 modification dates, and any recently-modified topic files the index cannot reach.\n\n---\n\n## Phase 1.5 · Plan Quality Gates — Read Only, No Writes\n\nBefore touching any memory file, make an explicit consolidation plan and check it against these guards:\n\n### 1. Topic identity guard\n\nDo **not** merge records just because names are similar. Split or preserve separate L2 files when any of these differ materially:\n\n- owner / customer / friend group\n- environment or host (IP, domain, machine, OS user, cloud account)\n- project lifecycle (legacy vs current, prototype vs production)\n- world / database / repo / app ID / claim ID / other durable identifier\n\nIf old and new material share a broad label, prefer:\n\n- `memory/<topic-current>.md` for the active project\n- `memory/<topic-legacy>.md` for historical material\n- `memory/<topic>.md` as a short disambiguation index, if needed\n\n**This guard governs L2 files. It says nothing about how the index is organised.** Two projects that must never share a topic file can still sit under one heading in `MEMORY.md`, because the two layers answer different questions: an L2 file answers *what is true about this specific thing*, and the index answers *what a human needs to see at a glance*. Conflating them makes the index a table of contents for the topic directory, growing one line for every file that has ever existed. Phase 3.2 decides index structure; this guard does not.\n\n### 2. Lifecycle state guard\n\nClassify every candidate as one of: **active**, **waiting**, **done**, **archived**, **closed**, or **snowed/paused**.\n\n- Closed / archived / snowed projects must not be reintroduced as active TODOs.\n- If a daily log says the human closed a line of work, update L2 + MEMORY.md to reduce future resurfacing.\n- Historical facts may remain, but phrase them as reference/archive, not action items.\n\n### 3. Secret propagation guard\n\nNever copy secrets from daily logs into L2, MEMORY.md, or dream-log.md.\n\n**This list is exhaustive, not illustrative.** A secret is a value that lets someone else authenticate as the human or the machine. Only these qualify:\n\n- API keys, tokens, OAuth strings, cookies, private keys\n- passwords, invite/player/server passwords, recovery codes\n- signed URLs or URLs containing access tokens\n- private SSH keys or full credential-bearing command lines\n\n**Do not extend the list on your own judgement.** This guard covers authentication material. It is not a privacy classifier and deliberately does not attempt to be one — content that is not on the list is consolidated under the workspace's own conventions, exactly like the rest of the log.\n\nThat boundary is a limit on this protocol, not a claim about what deserves protection. A daily log is written before this protocol ever sees it and is never edited by it, so whatever the guard withholds stays on disk regardless: withholding downstream changes what the index can recall without changing what is stored. A workspace with a stricter policy should apply it upstream where the log is written, or in its own instruction files — the two places where it can actually take effect.\n\nIf a source log contains a suspected live secret **from the list above**, do **not** quote it. Record only: `sensitive value omitted; source file needs manual review` and alert in the final response / dream summary.\n\nNever raise that alert for anything outside the list. A false alert is worse than no alert: it requests manual review this protocol cannot act on, and trains the human to ignore the one that matters.\n\nThis is the one guard that blocks content outright. It blocks **that value**, not the run — consolidate everything else normally.\n\n### 4. Write plan\n\nList the exact files you expect to touch. Phase 2 may touch only L2 files plus backups under `<WORKSPACE_ROOT>/.backup/memory-dreams/`. Phase 3 may touch only `MEMORY.md`, backups under `<WORKSPACE_ROOT>/.backup/memory-dreams/`, and `memory/dream-log.md`.\n\nIf the plan requires editing daily logs, system config, or files outside the workspace, do not write those targets.\n\nIn an interactive run, ask the human for explicit approval. In a scheduled or isolated run **there is nobody to ask** — so drop the out-of-bounds targets from the plan, consolidate everything else normally, and list exactly what was dropped and why in the final summary. Waiting on an answer that cannot arrive is the same as failing the whole run.\n\n---\n\n## Phase 2 · Consolidate — Write L2 Files Only\n\nProcess the priority list from Phase 1. **Do not modify MEMORY.md, dream-log.md, or any daily log file in this phase.**\n\n### 0. Back up touched L2 files first\n\nBefore modifying an existing L2 file, copy it to:\n\n`<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/<relative-path-from-workspace-root>.bak`\n\nExample: `memory/clash-verge.md` → `.backup/memory-dreams/20260426-1100/memory/clash-verge.md.bak`.\n\nKeep dream backups **outside `memory/`** and use a non-`.md` final suffix (`.bak`) so memory indexing does not recall stale states or old TODOs from backups.\n\nCreate parent directories as needed. **If backup creation fails, stop before writing** — this is one of the two real hard stops.\n\nFor a newly created L2 file, no pre-existing backup is required; include it in the dream-log as `created`.\n\n### Log entries → L2 extraction\n\n- Read the selected daily logs\n- Extract decisions, config changes, resolved issues, new knowledge\n- Write or update the appropriate `memory/<topic>.md`\n- If no matching L2 file exists for a topic, create one (e.g. `memory/network-setup.md`)\n\n### Relative time correction (conservative)\n\n- Only correct clear expired expressions: \"today\"/\"this week\" in L2 files → absolute dates\n- Do not guess at vague expressions\n\n### L2 write quality rules\n\n- **Write one file at a time with the editing tool.** Do not batch several L2 writes through a single shell or script invocation carrying the content as an argument or a here-document. That path lost every target in the batch to a non-UTF-8 encoding failure on two separate runs, while per-file edits of the same content succeeded first try. The fault is in constructing the command, so it stays invisible until the content is long and non-Latin — which is exactly when the run has the most to lose. On the second occurrence six L2 targets were skipped and their detail stayed in the index, inflating it by roughly 3,000 characters that the run had correctly decided to sink.\n- Prefer small, source-grounded edits over broad rewrites.\n- Keep active and legacy material clearly separated; add a one-line pointer instead of duplicating long history.\n- Do not add TODOs unless the source explicitly implies continuing action.\n- Preserve security posture: write `password not stored`, `token omitted`, or `credential managed in env` instead of values.\n- If an `edit` attempt fails twice for the same block, stop trying that block; use a safer whole-file rewrite only after re-reading the file, or leave a blocker in the final summary.\n\n---\n\n## Phase 3 · Settle — Write Index + Diary\n\n### 1. Back up MEMORY.md\n\nCopy current MEMORY.md content → `<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/MEMORY.md.bak`.\n\n**⚠️ Path guidance**: Always use absolute paths derived from the workspace root (e.g. `/path/to/workspace/MEMORY.md`). Never use `~`-prefixed paths in tool calls — isolated sessions may not resolve them. Use the workspace root passed in the task message.\n\n**If this backup fails, stop before rewriting** — the second real hard stop.\n\n### 2. Rewrite/trim MEMORY.md\n\n**The budget is derived from the runtime. This protocol does not hard-code one.**\n\n#### Compute the headroom\n\nOpenClaw truncates the *injected copy* of a bootstrap file at `agents.defaults.bootstrapMaxChars` (default **20,000 characters**), with a combined cap across all bootstrap files at `agents.defaults.bootstrapTotalMaxChars` (default **60,000**). Read both from the agent config — `~/.openclaw/openclaw.json`, under `agents.defaults` — rather than assuming the defaults. Reading it is allowed: the out-of-bounds rule in Phase 1.5 governs **writes**. If it is unreadable, use the documented defaults above and say so in the summary. Per the OpenClaw docs, *\"truncation is not data loss: the file stays intact on disk.\"* Nothing is destroyed at any size.\n\n`MEMORY.md` shares the total with the other bootstrap files, so its real ceiling is whichever limit binds first:\n\n```\nheadroom = min(\n    bootstrapMaxChars,\n    bootstrapTotalMaxChars − (size of the other bootstrap files)\n)\n```\n\nThe other bootstrap files are `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md` where present — OpenClaw's canonical prompt-order set, with `MEMORY.md` itself injected last. Earlier releases of this protocol also listed `TOOLS.md` and `HEARTBEAT.md`; neither is in the current set, `TOOLS.md` having been folded into the `## Tools` section of `AGENTS.md` by an OpenClaw migration. Count what your runtime actually injects rather than trusting this list. On a default install with modest instruction files the per-file cap binds and headroom is 20,000; on a workspace with large instruction files the total binds instead and headroom is smaller. Compute it — do not assume.\n\n#### Target 80% of headroom\n\n```\ntarget = headroom × SD_INDEX_TARGET_PCT / 100     # default 80\n```\n\nThe remaining 20% is a **growth band**, and it is the entire reason for a percentage rather than the raw ceiling. The index grows between runs; a target sitting exactly at the ceiling means any growth is silently truncated before the next consolidation gets a chance to look at it. Twenty percent of a 20,000-character headroom is 4,000 characters of slack — enough for many days of ordinary accumulation.\n\n`SD_INDEX_TARGET_PCT` is a knob, not a constant. Raise it toward 100 to use more of the budget and accept a thinner margin; lower it for a more aggressively curated index. It is expressed as a percentage precisely so that it keeps meaning the same thing when `bootstrapMaxChars` changes.\n\n**Count characters, not bytes.** A byte target silently shrinks the usable index for anyone writing in a non-Latin script — CJK text runs 1.7–3 bytes per character, so an \"8 KB\" rule cuts the real budget to about a quarter of what the runtime allows.\n\nUse `wc -m`, but **`wc -m` only counts characters under a UTF-8 locale**. In a scheduled or isolated session `LANG` is often unset, and `wc -m` silently returns the byte count instead — reintroducing the very error it was meant to avoid. Force a locale (`LC_ALL=en_US.UTF-8 wc -m file`) and sanity-check that the result is not larger than `wc -c`. If no UTF-8 locale is available, report bytes and say the character count was unavailable; do not compare a byte count against a character target.\n\n#### What the target means\n\nCrossing it is a **prompt to sink detail into L2 during this run**, nothing more. It is not a gate, not an error, and not a reason to delete facts.\n\n**Curation is not conditional on the number.** Ask the same question of every section on every run, whether or not the index is over the target: does this still belong in an index a human skims at session start — is the project active, is the status current, does the detail belong in an L2 file?\n\nBeing under the target is not an answer to that question. A protocol that curates only when over budget turns its target into a floor: the index settles just below it and stops shrinking, because the one rule that ever asks for less has already been satisfied. This is measured, not theorised — eight consecutive runs on a CJK workspace finished between 92% and 100% of a 16,000-character target, while five archived projects kept full entries at index level throughout.\n\nSo the number describes the index; it does not decide whether to curate. An index at 60% of target and entirely current is right. An index at 99% carrying three closed projects is not, and neither percentage is what tells you so.\n\n**A consequence worth stating, so nobody later mistakes it for a fault:** once curation stops tracking a target, the index size stops converging on one figure. Run-to-run sizes will vary with what the workspace is actually carrying. A flat line across many runs is the symptom of a protocol optimising for a number; movement is the protocol working.\n\nAn oversized index is a reason to *run*, not a reason to stop. Proceed normally and report the result.\n\nIf the workspace genuinely needs more room, raising `bootstrapMaxChars` is a legitimate answer — the docs list it alongside distilling and sinking to L2. Say so in the summary rather than deleting facts to fit.\n\nBe precise about what that buys. `bootstrapMaxChars` is a **truncation threshold**: it decides how much of the injected copy survives, and nothing else. Raising it stops truncation. It does not make a long index easier to read, and — now that curation no longer tracks the target — it no longer licenses the index to grow into the new headroom either. Reach for it when a genuinely necessary index is being cut, never as a way to avoid curating one.\n\nWhen the index has outgrown its budget, or when sections no longer belong at index level, fix it by **moving detail down to L2**, not by deleting information:\n\n1. Find the longest sections and check what belongs in an L2 topic file instead\n2. Move that detail into the L2 file (creating it if needed), leaving a one-line pointer\n3. Only then trim wording\n\n#### Structure the index by domain, not by topic file\n\nAn index entry is not the same thing as a topic file, and the index should not be a list of them. Related projects that each need their own L2 file — a printer's maintenance log, its filament stock, three separate models printed on it — belong under one index heading with one status line and a pointer per project. Phase 1.5's topic identity guard keeps those L2 files apart; it does not ask the index to mirror them.\n\nThe failure mode is quiet and runs one way only. Every new topic file adds an index entry, nothing ever merges them, and after a few months a reader has to scan thirty headings to learn what a handful of workstreams are doing. In one measured workspace, eleven of thirty-one index entries were facets of a single line of work.\n\nSo group entries by the question a reader is actually asking. Each domain heading carries:\n\n- one line of current status for the domain as a whole\n- one line per live project inside it: name, state, and L2 pointer\n- nothing the L2 file already says\n\nJudgement stays local. A domain holding one project does not need a heading above it, and a project large enough to be its own domain keeps its own. Do not manufacture domains to reach a count — grouping is worth doing only where a reader would naturally ask one question of several entries.\n\n#### Size the individual entry\n\nA total budget says nothing about any single entry, so one section can grow to several times the length of its neighbours without ever registering as a size problem.\n\nOpenClaw publishes a figure for how much text one durable memory item is worth: `plugins.entries.memory-core.config.dreaming.phases.deep.maxPromotedSnippetTokens` in `~/.openclaw/openclaw.json`, the cap `memory-core` puts on a single promoted snippet. Read the configured value; if the key is absent, use the documented default of 160 tokens and say so in the summary.\n\nTwo honest limits on that anchor, to keep in view rather than paper over:\n\n- It caps a *promoted snippet*, not a curated topic entry. Borrowing it is an analogy — a defensible one, since it is the only figure the runtime publishes for \"how long is one memory worth\" — not a rule `memory-core` enforces on this file.\n- It is denominated in **tokens** while this protocol measures characters. Do not convert it into a character constant and write that down; that is exactly the mistake described above. Estimate, say that it is an estimate, and act on clear cases only.\n\nUse it to find outliers, never as a target. An entry near the cap is unremarkable; an entry at several times the cap holds detail that belongs in its L2 file. **Do not shorten entries already at or below it** — a per-entry cap used as a target reintroduces the attractor one level down.\n\n#### Retire what is over\n\nClosed and archived projects belong in a short footer section, each reduced to **one line plus its L2 pointer**. A reader needs enough to recognise the project and find the detail; the detail is already in L2. A full entry for finished work is the most reliable way for an index to grow without gaining anything, because nothing else in this protocol will ever ask about it again.\n\n#### Place every new entry deliberately\n\nAssign each new entry to a named section before writing it. Never append to the end of the file and let position decide.\n\nThe end of the file is not a neutral place: it sits under whatever heading happens to be last, and a project entry landing beneath a section of standing rules inherits that section's meaning. Later runs then read it as a rule — permanent by definition, never a candidate for consolidation — and it stays there for good. This has happened in production: an active project entry written at the end of a run landed under a fixed-constraints heading, where every subsequent pass would have left it untouched.\n\nIf no existing section fits, say which one you chose and why in the dream-log.\n\nOther index rules:\n\n- Update project states from Phase 2 findings\n- Sync TODO states: mark completed ✅ items as done, add newly discovered todos\n- Preserve lifecycle state: closed / archived / snowed items belong in archive/reference wording, not active sections\n- Each domain heading: one-line status, then one line per live project carrying its state and L2 pointer (e.g. `**Details**: memory/clash-verge.md`)\n- Weigh the L2 modification dates gathered in Phase 1.6. A topic whose file has not moved in a long time is a prompt to check whether its index entry still describes something live — not grounds on its own to retire it, and never a reason to change a lifecycle state the human has not changed\n\nIf the index still exceeds the computed headroom after sinking detail to L2, say so in the dream-log and the final summary so the human can decide whether to cut further or raise `bootstrapMaxChars`. Then finish the run normally.\n\n### 3. Determine dream number\n\nRead the number out of the **newest** `## 🌙 Dream #` heading and add 1.\n\nNever derive it by counting how many entries the file holds. Phase 5 caps the diary at 30, so a count-based number freezes at 31 the moment that cap is reached, and every run after it writes 31 again — the diary loses monotonic identity, and with it the only check that would show a run had been missed.\n\nIf the newest heading carries no parseable number, or the file holds no Dream entries at all, start at 1 and say so in the final summary.\n\nKeep the timezone abbreviation in the heading (`2026-08-24 11:27 CST`). The guardian compares that timestamp both against now (the debounce) and against daily-log mtimes (to find unread appends), so a bare local time is ambiguous the moment the agent runs under a different zone than the one that wrote it. Record the time the run **started**: the guardian relies on it to catch files that landed while the run was still working.\n\n### 4. Append to dream-log.md (Markdown — not JSON)\n\n**⚠️ Tool guidance**: Tool names here are OpenClaw's (`exec` to run a shell command, `edit` to patch a file); on another harness use its equivalents. Use `exec` with a heredoc to append — **never** use the `edit` tool for appending (it requires exact text replacement and will fail on append). Replace `<WORKSPACE_ROOT>` with the absolute path from the task message.\n\n```bash\ncat >> <WORKSPACE_ROOT>/memory/dream-log.md << 'DREAM_EOF'\n\n## 🌙 Dream #<NUMBER> · YYYY-MM-DD HH:MM TZ\n\n**Trigger**: <auto|manual>\n**Duration**: ~<MINS> minutes\n**Consolidated through**: YYYY-MM-DD\n\n### Signal summary\n- Logs consolidated: <LOG_COUNT> (<DEFERRED_COUNT> deferred to next run)\n- Input: <LOG_KIB> KiB logs + <L2_KIB> KiB L2 — <DURATION_S> s\n\n### What changed\n- Updated L2: <filename> — <one-line description>\n- Synced <TODO_COUNT> TODO items\n- MEMORY.md: <BEFORE> → <AFTER> chars\n\n### Note\n(One honest sentence about what was found or how it felt)\nDREAM_EOF\n```\n\n**`Consolidated through` is the watermark the next run reads.** Set it to the date of the newest daily log you actually processed this run:\n\n- Normal run: the newest selected log — usually today\n- Deferred run (batch limit hit while resuming): the newest log **in the batch you processed**, not today\n- Manual backfill: omit the field entirely. The next run scans past this entry to the last one that has it, so the frontier stays exactly where it was\n\nGetting this wrong is the one mistake in this protocol that silently loses memory rather than reporting a problem. If a run defers work, say so in the `Signal summary` line as well, so it is visible to a human skimming the diary.\n\n**Record the three input numbers on every run, not only when something overruns.** Log bytes, L2 bytes and wall-clock together are the only evidence a later version has for sizing the batch cap against the task timeout, and that pair is currently calibrated on log bytes alone (Phase 1.3). A measurement taken only when a run goes wrong is sampled exactly where it is least representative of ordinary nights — and the run that finally needs the history is the one that does not have it. Three numbers per entry costs a line.\n\n### 5. Trim dream-log.md\n\nIf total `## 🌙 Dream #` entries exceed 30, delete the oldest entry.\n\n**Trimming changes how many entries the file holds; it must never change the next dream number.** Phase 3 reads that number out of the newest heading for exactly this reason — the two rules are only consistent while the number comes from the heading rather than from a count.\n\n**Never delete the last entry that still carries a `Consolidated through:` field.** If the oldest entry is the only one holding the watermark — which happens after a run of backfills, since those omit the field — skip it and delete the next-oldest instead. Trimming the diary must not delete the frontier along with it.\n\nBoundary rule: delete from the start of the chosen `## 🌙 Dream #` heading up to (but not including) the start of the next `## 🌙 Dream #` heading. Do not use `---` as a boundary — it may appear inside dream content.\n\n**Delete with the editing tool, then count the headings again.** A trim that silently does nothing leaves a perfectly valid file that is merely one entry too long, so nothing downstream notices. One run reported the trim as done while the file still held 31 entries: the write call had failed on an argument it did not support, and the run never looked at the result. Phase 3.6 prints the count for exactly this reason.\n\n### 6. Post-write audit\n\nRun a lightweight verification pass. **This is a reporting step, not a gate** — it runs after the work is committed, and its findings go into the final summary. A failure here never invalidates the dream.\n\nCheck:\n\n- `MEMORY.md` size in characters, against the computed headroom and target — report the numbers, never treat either as a pass/fail verdict\n- **every `###` entry sits under a heading that matches what it is** — a project entry under a standing-rules heading is the placement failure Phase 3.2 describes, and it is invisible unless something looks for it\n- `memory/dream-log.md` is still Markdown with no malformed duplicate heading\n- `memory/dream-log.md` holds no more entries than the trim cap — the helper prints `dream_log_entries`; compare it rather than assuming Phase 3.5 took effect\n- touched L2 files still separate current vs legacy/archived material correctly\n- touched files contain no obvious credential-bearing values\n- the number of backup directories under `.backup/memory-dreams/` and their total size — the helper prints `backup_dirs` and `backup_kib`. **Report them; never prune them.** Nothing in this protocol deletes a backup. They are the recovery path for every write it makes, including the one hard stop that cancels a run, and a pass that rewrites memory is the wrong process to hand the eraser for its own safety net. There is also no runtime value to derive a retention period from, so any cut-off would be an invented constant of exactly the kind the Design Stance rejects. The numbers go in the summary and a human decides\n\n**Check the artifact, not the action.** Every item above reads what is on disk, and that is the point: a write that returned without raising is not evidence that a file changed. A diary trim has been observed reporting success against a file it left untouched, and nothing downstream would have noticed — the miss was found by counting the headings afterwards, not by the step that did the work. Where an earlier phase claims it wrote something, read it back here.\n\n**Reading the size across runs.** A single run's size says nothing. Across five or more runs, sizes that vary with what the workspace is carrying mean curation is tracking content; sizes that sit in a narrow band just under the target mean it has started tracking the target again, and the rules in Phase 3.2 are not reaching the entries that should be moving. Report the observation; never act on it inside the run.\n\nUse filename-only scans so secrets are not echoed into chat/logs:\n\n```bash\ngrep -IlrE '(github_pat_[A-Za-z0-9_]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|[0-9]{8,12}:AA[A-Za-z0-9_-]{30,}|mfa\\.[A-Za-z0-9_-]{20,}|-----BEGIN (RSA |OPENSSH |EC |DSA )?PRIVATE KEY-----)' <TOUCHED_FILES>\n```\n\nA helper script covers the common checks:\n\n```bash\nbash <SKILL_DIR>/references/dream-audit.sh <WORKSPACE_ROOT> [touched-file ...]\n```\n\n**Invoke it through `bash`, not as an executable.** A registry archive does not\nhave to preserve the executable bit, and this one does not: a copy installed from\nClawHub arrives without `+x`, so calling the path directly fails on a fresh\ninstall while working on the machine that published it. Naming the interpreter\ncosts nothing and removes the difference.\n\nPass relative or absolute paths for touched files. The helper reports suspected secret filenames only; it does not print matched values. It is intentionally conservative and lightweight, not a replacement for a dedicated secret-scanning/DLP tool. Its exit code is advisory — never gate a run on it.\n\nIf a possible secret is found after writing, restore from the relevant backup where possible, then report the file path without quoting the secret.\n\n---\n\n## Safety Rules\n\n| Rule | Detail |\n|------|--------|\n| Guardian runs before Phase 1 | Skip check writes only to dream-log.md |\n| Phase 1 is read-only | An error in Sense touches no files |\n| Never archive daily logs | Moving `YYYY-MM-DD*.md` breaks memory_search indexing |\n| Always back up before rewriting | `.backup/memory-dreams/YYYYMMDD-HHMM/` before touching MEMORY.md or any L2 file |\n| Backup failure is a hard stop | The only infrastructure condition that cancels writes |\n| Out-of-bounds targets drop, never block | Ask when interactive; when scheduled, drop them, finish the rest, report what was dropped |\n| dream-log.md = Markdown | Append text; never parse or write as JSON |\n| L2 files are permanent | Never delete or archive `memory/<topic>.md` |\n| Phase 2 = L2 only | MEMORY.md changes happen in Phase 3 |\n| One L2 file per write | Batching content through a single command invocation has lost whole batches to encoding failures |\n| No secret propagation | Redact and alert; never promote credentials. Authentication material only; not a privacy classifier |\n| Lifecycle is sticky | Closed/archived/snowed stay non-active until the human reopens them |\n| Index size never gates a run | An oversized index is the reason to run, not to abort |\n| Measure the index in characters | Byte targets silently shrink the budget for non-Latin scripts |\n| Never hard-code a limit | headroom = `min(bootstrapMaxChars, bootstrapTotalMaxChars − other bootstrap files)`; target = headroom × `SD_INDEX_TARGET_PCT` (default 80) |\n| The target is a prompt, not a gate | Crossing it means sink detail into L2 this run |\n| Curation is not conditional on size | Being under the target is not a reason to skip it; a target consulted only when exceeded becomes a floor |\n| The index is not a list of topic files | Group by the question a \n\nFile v5.0.3:references/memory-stack.md\n\n# Where signal-dreaming sits in OpenClaw's memory stack\n\nOrientation material, moved out of `SKILL.md` in 5.0.3 so the always-loaded file\nstays within its character budget. Nothing here is a step: the operational\nconsequence — never read or write `memory/dreaming/**` or `memory/.dreams/**` —\nis stated in `SKILL.md` and enforced in Phase 1 of `dream-protocol.md`.\n\nVerified against OpenClaw **2026.9.2**. This skill operates entirely on the documented Markdown layer — `MEMORY.md` plus `memory/*.md` — which remains the durable memory model.\n\n**Built-in memory-core Dreaming** is a separate system, **enabled by default**; set `plugins.entries.memory-core.config.dreaming.enabled: false` to turn it off. Earlier releases of this skill called it opt-in, which was true when they were written and is not true now:\n\n| | memory-core built-in Dreaming | signal-dreaming (this skill) |\n|---|---|---|\n| Trigger | Managed cron when enabled | Cron agentTurn |\n| Source | Short-term recall store under `memory/.dreams/` | Daily logs on disk |\n| Output | `DREAMS.md` / `memory/dreaming/{phase}/` | `memory/dream-log.md` + L2 files |\n\nThe two are independent; run this skill with built-in Dreaming on or off. This protocol never reads or writes `memory/dreaming/**` or `memory/.dreams/**`, and skips `## Light Sleep` / `## REM Sleep` blocks if it finds them inside a daily log (older `inline` mode).\n\nAdjacent components this protocol deliberately does not touch:\n\n- **`memory-wiki`** — per the OpenClaw docs it \"does not replace the active memory plugin\"; its vault is its own layer, neither read nor written here.\n- **Alternate backends** (QMD, Honcho, LanceDB) — they change how `memory_search` retrieves, not where durable notes live. This protocol reads and writes files, so it is backend-agnostic.\n- **Database-first state** — the SQLite migration covers runtime state (sessions, transcripts, task ledgers). Workspace Markdown memory is out of scope.\n- **Automatic memory flush** — the pre-compaction pass writes daily notes; this protocol consumes them. No conflict.\n\n**A version number in this file dates.** It records what was true of OpenClaw when\nsomeone last checked, not a compatibility guarantee. Re-read the current docs\nbefore relying on any line here; the protocol itself derives its limits from the\nruntime rather than from this page, so a stale note here changes orientation, not\nbehaviour.\n\nFile v5.0.3:skill-card.md\n\n## Description:\n\nConsolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[lzyling](https://clawhub.ai/user/lzyling)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nOpenClaw users and developers use this skill to turn daily session logs into curated topic notes and a compact memory index, manually or on a schedule.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Manual or scheduled runs can rewrite persistent workspace memory.\n\nMitigation: Use an explicit prompt or carefully reviewed schedule and retain backups before running.\n\nRisk: Sensitive non-credential details in daily logs may be promoted into durable notes.\n\nMitigation: Apply stricter privacy rules when needed and review curated memory for sensitive content.\n\n## Reference(s):\n\n- [Signal Dreaming on ClawHub](https://clawhub.ai/lzyling/skills/signal-dreaming)\n- [Dream protocol](references/dream-protocol.md)\n- [Memory stack](references/memory-stack.md)\n- [Dream audit](references/dream-audit.sh)\n\n## Skill Output:\n\n**Output Type(s):** [Markdown files, Text summary]\n\n**Output Format:** [Markdown memory index, topic notes and dream log; plain-text run summary]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Keeps daily logs read-only and backs up existing memory files before editing.]\n\n## Skill Version(s):\n\n5.0.3 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v5.0.2: 6 files, 34542 bytes\n\nFiles: references/changelog.md (15358b), references/dream-audit.sh (6009b), references/dream-protocol.md (46847b), skill-card.md (2393b), SKILL.md (10025b), _meta.json (134b)\n\nFile v5.0.2:SKILL.md\n\n---\nname: \"signal-dreaming\"\ndescription: \"Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.\"\n---\n\n# Signal Dreaming\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\nDaily session logs accumulate raw detail. This skill reads the ones written since the last run, promotes what matters into durable topic files, and keeps the top-level index worth reading at session start.\n\n## Memory Architecture Assumed\n\nA two-layer memory layout:\n\n- **Daily logs** (`memory/YYYY-MM-DD*.md`) — raw session notes, read-only, never moved or deleted\n- **L2 topic files** (`memory/<topic>.md`) — curated durable knowledge per subject (e.g. `memory/clash-verge.md`)\n- **Index** (`MEMORY.md`) — high-level status with pointers down to L2\n\nIf you are starting fresh, create `MEMORY.md` and `memory/dream-log.md` before the first run. L2 files are created on demand.\n\n`memory/dream-log.md` doubles as the only state this skill keeps: entries record a **`Consolidated through:`** date — the newest daily log already folded into memory. The next run scans back for the most recent entry carrying that field to know where to resume. There is no state file, no lock file, and nothing to migrate or repair.\n\n## Quick Start\n\n### Manual dream\n\nTell your agent:\n\n> \"Run a memory dream consolidation. Follow the protocol in `<SKILL_PATH>/references/dream-protocol.md`. Workspace root: `<YOUR_WORKSPACE_PATH>`.\"\n\nOr just *\"run a dream consolidation\"* — if this skill is loaded, the agent will know what to do.\n\n### Automated daily dream (cron)\n\n```json\n{\n  \"name\": \"daily-dream\",\n  \"schedule\": { \"kind\": \"cron\", \"expr\": \"0 7 * * *\", \"tz\": \"<YOUR_TIMEZONE>\" },\n  \"sessionTarget\": \"isolated\",\n  \"payload\": {\n    \"kind\": \"agentTurn\",\n    \"timeoutSeconds\": 1800,\n    \"message\": \"Run a memory dream consolidation. Read <SKILL_PATH>/SKILL.md and <SKILL_PATH>/references/dream-protocol.md in full, then follow the protocol phases in order. Workspace root: <YOUR_WORKSPACE_PATH>. Do not modify cron jobs, agent config, the Gateway, any daily log, or memory/dreaming/**. End your final response with a one-line dream summary — the cron delivery mechanism will auto-announce it.\"\n  },\n  \"delivery\": { \"mode\": \"announce\", \"channel\": \"<CHANNEL_TYPE>\", \"to\": \"<CHANNEL_ID>\" }\n}\n```\n\nSet `expr` and `tz` to when your human is asleep. `timeoutSeconds` and the batch cap are a pair — lower one without the other and a batch is admitted that cannot finish.\n\n## Three-Phase Safety Model\n\n| Phase | Writes | Purpose |\n|-------|--------|---------|\n| **Sense** | ❌ None | Select logs since the watermark, plan the work |\n| **Consolidate** | ✅ L2 files only | Promote content into topic files |\n| **Settle** | ✅ MEMORY.md + dream-log.md | Update index, write diary entry |\n\nPhase 1 is always read-only. An error in Sense never corrupts files.\n\n## Quality Gates\n\nA read-only planning checkpoint runs before any write: **topic identity** (do not merge legacy and current projects on name similarity), **lifecycle** (closed work must not reappear as an active TODO), **secret propagation** (never promote credentials into curated memory; the credential list is exhaustive and the guard is not a privacy classifier), **backup** (existing L2 files and `MEMORY.md` are copied to `<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/` with a `.bak` suffix first), and a **post-write audit** reporting size, structure, lifecycle separation, and credential patterns without gating the run. `references/dream-audit.sh` covers the common checks; it is not full DLP.\n\n## Failure Philosophy\n\nThis protocol is written for an agent to follow, not for a program to enforce. It fails **soft**:\n\n- Do the work you can do, then report what you skipped and why.\n- Diagnostics inform the final summary. They never block consolidation.\n- An oversized `MEMORY.md` is the reason to run, not a reason to abort.\n- Limits are derived from the runtime, never hard-coded into the protocol.\n- Never wait on an answer that cannot arrive. A scheduled run has nobody to ask, so out-of-bounds work is dropped and reported, not blocked on.\n\nExactly two conditions cancel writes: a **failed backup**, or a **write plan reaching outside the allowed paths** — and the second cancels only those targets, not the run.\n\nThe secret guard is the one hard block on content — and it blocks the offending value, not the run.\n\n## Where this sits in OpenClaw's memory stack\n\nVerified against OpenClaw **2026.9.2**. This skill operates entirely on the documented Markdown layer — `MEMORY.md` plus `memory/*.md` — which remains the durable memory model.\n\n**Built-in memory-core Dreaming** is a separate system, **enabled by default**; set `plugins.entries.memory-core.config.dreaming.enabled: false` to turn it off. Earlier releases of this skill called it opt-in, which was true when they were written and is not true now:\n\n| | memory-core built-in Dreaming | signal-dreaming (this skill) |\n|---|---|---|\n| Trigger | Managed cron when enabled | Cron agentTurn |\n| Source | Short-term recall store under `memory/.dreams/` | Daily logs on disk |\n| Output | `DREAMS.md` / `memory/dreaming/{phase}/` | `memory/dream-log.md` + L2 files |\n\nThe two are independent; run this skill with built-in Dreaming on or off. This protocol never reads or writes `memory/dreaming/**` or `memory/.dreams/**`, and skips `## Light Sleep` / `## REM Sleep` blocks if it finds them inside a daily log (older `inline` mode).\n\nAdjacent components this protocol deliberately does not touch:\n\n- **`memory-wiki`** — per the OpenClaw docs it \"does not replace the active memory plugin\"; its vault is its own layer, neither read nor written here.\n- **Alternate backends** (QMD, Honcho, LanceDB) — they change how `memory_search` retrieves, not where durable notes live. This protocol reads and writes files, so it is backend-agnostic.\n- **Database-first state** — the SQLite migration covers runtime state (sessions, transcripts, task ledgers). Workspace Markdown memory is out of scope.\n- **Automatic memory flush** — the pre-compaction pass writes daily notes; this protocol consumes them. No conflict.\n\nTool names in the protocol (`exec`, `edit`) are OpenClaw's; on another harness use its equivalents.\n\n## Key Rules\n\n- **Never move or delete daily logs** — archiving breaks `memory_search` indexing\n- **dream-log.md is Markdown** — append text directly, never write JSON\n- **Never copy credentials into curated memory** — omit/redact and alert instead. The secret list is **exhaustive**: authentication material only. This guard is not a privacy classifier; anything outside the list follows the workspace's own conventions and never raises a secret alert\n- **Keep lifecycle state sticky** — closed/archived/snowed lines stay non-active\n- **Back up before rewriting** `MEMORY.md` and any existing L2 file\n- **MEMORY.md budget is derived, never hard-coded** — headroom = `min(bootstrapMaxChars, bootstrapTotalMaxChars − other bootstrap files)`; target = **80%** of it (`SD_INDEX_TARGET_PCT`, a knob), the remaining 20% being growth slack. Count characters, not bytes. Crossing the target means sink detail into L2 — it never blocks and never justifies deleting facts\n- **Curate on every run, not only when over budget** — being under the target is not an answer to \"does this still belong at index level\". A target consulted only when exceeded becomes a floor the index grows to meet\n- **The index is not a list of topic files** — group entries by the question a reader is asking; the topic identity guard governs L2 files, not index structure. Closed and archived work keeps one line plus its L2 pointer\n- **Place every entry in a named section** — appending to the end of the file lets whatever heading happens to be last redefine what the entry means\n- **Batch limit**: 32 logs or 192 KiB per run, cut on date boundaries. Sized so one batch fits inside the task timeout; a single date that exceeds it on its own is still processed whole. When resuming after a gap it takes the oldest first so the watermark advances contiguously\n- **On the watermark date, re-read only the files that changed** — `>=` exists to catch same-day appends, not to re-admit the whole date at full size every run. Compare mtimes against the heading of the newest entry carrying `Consolidated through:`, never a later backfill heading\n- **Write L2 files one at a time, and read back what you claim to have written** — batching content through one command invocation loses whole batches to encoding failures, and a step reporting success is not evidence its file changed\n\n## Full Protocol\n\nSee `references/dream-protocol.md` for the complete three-phase workflow, quality gates, dream-log format, and safety rules.\n\n## Version Note\n\n**5.0.2** aligns the cron template's `timeoutSeconds` with the batch cap it has to finish inside, and defines what a run does when 5.0.1's selection rule returns nothing at all.\n\n**5.0.1** stops a large watermark date from blocking every later date forever, and makes two silent write failures visible. The deadlock needed all three of `>=` selection, oldest-first resumption and the unsplittable date boundary — each correct alone. Only changed files on the watermark date are re-read now.\n\n**5.0.0** makes the index a domain-level summary rather than a mirror of the L2 topic list, and curates on every run rather than only when the index is over budget.\n\nBoth halves change what this skill produces, so an existing workspace will see its `MEMORY.md` restructured on the first run after upgrading. That rewrite is backed up like any other (Phase 3.1), but it is not something the human asked for on that day — hence the major version. **Run it manually the first time, with someone watching**, then hand it back to the schedule; later runs only touch the increment.\n\nFull release history, with the reasoning behind each change: `references/changelog.md`.\n\nFile v5.0.2:_meta.json\n\n{\n  \"ownerId\": \"kn73gt88jsjv66v89gzkwxcfrn84swtx\",\n  \"slug\": \"signal-dreaming\",\n  \"version\": \"5.0.2\",\n  \"publishedAt\": 1789140986810\n}\n\nFile v5.0.2:references/changelog.md\n\n# Signal Dreaming — Version History\n\nEach entry states what changed and why. Where a release fixed something that had\nbeen running wrong, the entry says what the wrong behaviour cost, because that is\nthe part that stops it being reintroduced.\n\n---\n\n## 5.0.2\n\nTwo fixes, both to things that were inconsistent rather than wrong in themselves.\n\n**The cron template's timeout now matches the batch cap.** 4.0.1 sized the\n`192 KiB` cap against an `1800 s` timeout and said so, but left the template in\n`SKILL.md` at `900 s`. At the slower end of the measured throughput that admits\nroughly `108 KiB` — so a batch this protocol considers legal gets the run killed\npartway, most likely inside Phase 2, leaving L2 files written, the index and the\ndiary untouched, and the watermark not advanced. Nothing records that it happened.\nThree releases carried the mismatch because the workspace it was developed on runs\n`1800 s`, so the template was never the thing being exercised. Both numbers are\nstated together now, with a line saying they move as a pair.\n\n**An empty selection now has a rule.** 5.0.1 made the state reachable and said\nnothing about it: once the watermark date contributes only its changed files, a run\nwhose only candidate is an unchanged watermark date selects nothing at all. Before\nthat, `>=` guaranteed there was always something. Undefined, the likely improvisation\nis the damaging one — carry the empty batch into Phase 3 and write a diary entry\nwhose `Consolidated through:` names logs the run never read.\n\nSo: an empty selection ends the run at Phase 1, writes nothing, and leaves the\nwatermark where it is. It does **not** append a `SKIP` comment. The guardian may,\nbecause the diary is the only channel it has; a run that reached Phase 1 has its\nfinal summary, and nothing in this protocol reads those comments — Phase 1.1\nanchors on Dream headings. Phase 1 is declared read-only in three places and stays\nthat way.\n\n---\n\n## 5.0.1\n\nStops a large watermark date from blocking every later date forever, and makes two\nsilent write failures visible.\n\n**The watermark date is no longer re-admitted whole.** Phase 1.2 selects `>=` so\nthat same-day appends are not missed, and justifies the cost as re-reading one\nalready-seen file being cheap. Applied to a whole date it is not cheap: every file\nof that date returned to the batch at full size on every run, changed or not. With\noldest-first resumption and the unsplittable date boundary, a date large enough to\nfill the batch on its own then filled it permanently — three consecutive runs each\nre-read the same 221 KiB day, and the following day's four logs were deferred every\ntime. Each of the three rules is right; the combination is what deadlocks, and it\nneeds a workspace that writes more in one day than a batch can hold, which is why\nit took this long to surface.\n\nThe fix asks the modification question per file instead of per date, with the\n`find -newermt` test the guardian already uses. Unchanged files on the watermark\ndate contribute nothing to the batch; changed ones are still selected, so the `>=`\ndefence is intact. Skipping the whole date on the same evidence was the obvious\nalternative and the wrong one: a false negative from mtime costs the guardian one\nno-op run, but costs a date-level skip everything appended to that day,\npermanently. Per-file keeps the consequence at the guardian's level.\n\nOne detail that is easy to get backwards: the comparison timestamp must come from\nthe newest entry carrying `Consolidated through:`, not the newest entry. A backfill\nhas a later heading and deliberately left the frontier alone, so comparing against\nit under-reports which files changed — and under-reporting here drops them as\nalready-read. The guardian escapes the same trap by refusing to skip after a\nbackfill; this rule has no do-nothing option, so it has to name the right entry.\n\n**L2 files are written one at a time.** Batching several promotions through a\nsingle command invocation that carries the content corrupted non-UTF-8 text and\nlost the entire batch, twice. The second occurrence skipped six targets, so detail\nthe run had correctly decided to sink stayed in the index and inflated it by\nroughly 3,000 characters. Per-file edits of the same content succeeded first try.\nThe fault is in building the command, so it stays invisible until the content is\nlong and non-Latin — exactly when a run has the most to lose.\n\n**Writes are verified by reading them back.** A diary trim reported success against\na file it had not changed, leaving 31 entries under a cap of 30. The result is a\nvalid file that is merely one entry too long, so nothing downstream notices. The\naudit helper already printed the entry count and nobody compared it; it now warns\nwhen the count exceeds `SD_DREAM_LOG_MAX` (default 30, same integer-only handling\nas the other overrides). Phase 3.6 states the general form: a step reporting\nsuccess is not evidence its file changed, so check the artifact rather than the\naction.\n\nNo change to what a run produces — the index keeps the shape 5.0.0 gave it.\n\n---\n\n## 5.0.0\n\nMakes the index a domain-level summary rather than a mirror of the L2 topic list,\nand curates on every run rather than only when the index is over budget.\n\nBoth halves change what the protocol produces, so an existing workspace will see\nits `MEMORY.md` restructured on the first run after upgrading. That restructuring\nis backed up like any other write (Phase 3.1), but it is not something the human\nasked for on that particular day — hence the major version.\n\n**Curation stopped being conditional on size.** The protocol said that below its\ntarget, size was \"not a consideration at all\", then asked — correctly — whether\neach section still belonged at index level. In practice the first clause won: the\nrun reached the target, stopped, and never got to the question. Measured across\neight consecutive runs on a CJK workspace, the index finished between 92% and 100%\nof a 16,000-character target every time, while five archived projects kept full\nentries throughout. A target that is only ever consulted when exceeded is not a\nceiling; it is a floor, and the index grows until it finds it.\n\n**The index stopped mirroring the topic directory.** Phase 1.5's topic identity\nguard forbids merging L2 files that describe materially different things. That\nguard is right, and it was being applied one layer too high: every new topic file\nearned its own index entry, nothing ever merged, and eleven of thirty-one entries\nin one workspace turned out to be facets of a single line of work. The guard now\nsays explicitly that it governs L2 files and not index structure, and Phase 3.2\ngroups index entries by the question a reader is asking.\n\n**Phase 1 now reads L2 modification times.** Deciding whether an entry still\ndescribes something live was unanswerable from the entry itself — an index says a\nproject is active in the same words the day the work stops. The modification time\nof the file an entry points at is a fact produced by Phase 2 rather than a field\nsomeone has to remember to maintain. A date stamped into the entry was the\nobvious alternative and the weaker one: it survives only until the first run that\nrefreshes every line at once. mtime informs the judgement; it never retires\nanything by itself.\n\n**Entries are placed deliberately.** New entries were appended to the end of the\nfile, which is not a neutral position — it sits under whatever heading happens to\nbe last. An active project entry landed under a standing-rules heading in\nproduction, where later runs would have read it as a permanent rule and never\nconsidered it for consolidation again. Phase 3.2 now assigns a section before\nwriting, and Phase 3.6 checks that every entry sits under a heading matching what\nit is.\n\n**Individual entries are sized.** A total budget says nothing about one entry, so\na section can reach several times the length of its neighbours unnoticed. The\nanchor is `maxPromotedSnippetTokens` — the cap `memory-core` puts on a single\npromoted snippet — read from config rather than copied into this document. It is\nused to find outliers only: entries already at or below it are left alone,\nbecause a per-entry cap used as a target rebuilds the same attractor one level\ndown.\n\n**Finished work keeps one line.** Closed and archived projects are reduced to a\nline plus an L2 pointer. Nothing in the protocol asks about them again, so a full\nentry is pure accumulation.\n\n**Security: the audit helper no longer evaluates its numeric overrides.**\n`references/dream-audit.sh` read `SD_BOOTSTRAP_MAX_CHARS`,\n`SD_BOOTSTRAP_TOTAL_MAX_CHARS` and `SD_INDEX_TARGET_PCT` straight into `$(( ))`.\nBash evaluates an array subscript inside arithmetic expansion as a command, so\n`SD_INDEX_TARGET_PCT='x[$(cmd)]'` executed cmd — confirmed by running it. The\nthree values are now accepted only as plain non-negative integers; anything else\nfalls back to the default and warns, since an override dropped in silence is its\nown defect. Defaults and legitimate overrides behave exactly as before.\n\n**Packaging: the audit helper is invoked through `bash`.** The protocol called\n`references/dream-audit.sh` by path, which needs the executable bit. A ClawHub\narchive does not preserve it, so the call worked on the publishing machine and\nfailed on every fresh install. Naming the interpreter removes the difference.\n\n`SD_INDEX_TARGET_PCT` is unchanged at 80. Lowering it would have moved the\nattractor rather than removing it.\n\nOne consequence is documented so it is not later mistaken for a regression: index\nsize no longer converges on a figure. Run-to-run variation means curation is\ntracking content; a flat line means it has started tracking the target again.\n\nAlso includes the unreleased 4.0.4 fix below.\n\n---\n\n## 4.0.4 (unreleased)\n\nFixes a dream number that stops advancing once the diary reaches its trim cap.\n\nPhase 3 derived the new number by counting `## 🌙 Dream #` headings, while Phase 5\ndeletes the oldest entry whenever the count exceeds 30. The two rules agree only\nwhile the diary is still growing. Once it saturates, the count is permanently 30,\nso every subsequent run writes `#31` — observed in production across four\nconsecutive runs after the cap was reached. The watermark is unaffected, since it\nis read from `Consolidated through:`, so consolidation stays correct while the\ndiary quietly loses monotonic identity and the gap check that depends on it.\n\nPhase 3 now reads the number out of the newest heading and adds 1, and Phase 5\nstates that trimming must never change the next number. Repairing an\nalready-frozen diary means renumbering the affected headings once, from the last\ncorrect entry forward; nothing else in the log changes.\n\n---\n\n## 4.0.3\n\nRestates the 4.0.2 scope without enumerating what falls outside it.\n\n4.0.2 fixed the rule by naming categories of ordinary content as out of scope. The\nrule was right; the enumeration was not. A list of what a memory system will\nretain reads, to someone reviewing the skill rather than running it, as a\ndescription of what it sets out to collect — and the three constraints that\nactually do the work need no examples to be unambiguous: the list is exhaustive,\nit is not to be extended, and nothing outside it raises the alert.\n\nThe scope is now stated as a limit on the guard rather than a judgement about\ncontent. It covers authentication material and is not a privacy classifier;\nanything else follows the workspace's own conventions. Behaviour is identical to\n4.0.2.\n\n---\n\n## 4.0.2\n\nCloses the secret guard's scope.\n\nThe list of what counts as a secret was introduced with *\"treat these as sensitive\nby default\"* — wording that reads as a floor rather than a boundary, so a run could\nclassify ordinary log content as sensitive on its own judgement, withhold it from\nL2, and ask for manual review of the daily log.\n\nThat alert has nowhere to go. This protocol never edits daily logs, so the value\nstays exactly where it already was; the only durable effects are an index missing\nan ordinary fact and a review request that cannot resolve. Repeated, it teaches\nthe human to skim past the alert that does matter.\n\nThe list is now stated as **exhaustive**, and nothing outside it raises the alert.\n\n---\n\n## 4.0.1\n\nFixes a guardian that could skip work it should have done, and sizes the batch cap\nto the task timeout.\n\nThe guardian tested for daily logs dated strictly *after* the watermark, while\nPhase 1 selects `>=` precisely because a watermark-dated log can be appended to\nafter the last run read it. Since the guardian runs first, that defence never\napplied: inside the debounce window a run reported no-op while same-day additions\nwaited for a later pass. Session-reset hooks that write `YYYY-MM-DD-HHMM.md` files\nmake several logs share the watermark date every day, so this is now the normal\ncase rather than an edge one. The two log conditions are merged into one that\nreuses Phase 1's `>=` and asks whether any selected log changed since the last\nentry's heading timestamp.\n\nThe byte cap moves from `512 KB` to `192 KiB`. A cap means something only if the\nbatch it admits can finish before the scheduler kills the run: at a measured\n`0.12-0.21 KiB/s`, an `1800 s` timeout admits roughly `216 KiB`, so the old number\nwas never reachable. **If your task timeout differs, recompute the cap from your\nown slowest observed throughput.** The cap bounds accumulation across days and\ncannot bound a single day — the date-boundary rule still processes an oversized\ndate whole, and such a run now records its actual input size and duration in the\ndream-log.\n\n---\n\n## 4.0.0\n\nReturns to the protocol-only design of the 1.x line and removes the 2.x/3.x script\nlayer entirely.\n\nThose versions compiled the same rules into enforced JavaScript preconditions — a\ntransaction state machine, run manifests, lock files, staged candidate\ndirectories. That layer added no new safety rules; it changed who enforced them,\nand in production turned recoverable conditions into aborted runs: an over-limit\nindex refused to run the pass that shrinks it, and an optional audit's path error\ncancelled an otherwise complete consolidation.\n\nIt also retires the byte-based index target inherited from 1.x. That `8 KB` was\nwritten where a byte and a character were the same thing; the real limit is\n`bootstrapMaxChars`, measured in **characters**. For a CJK workspace at ~1.7 bytes\nper character it enforced about a quarter of the real budget — and 3.x then made\ncrossing it a hard error, so a healthy index could block its own consolidation\npass. 4.0.0 does not substitute another constant: headroom is derived from the\nruntime's own caps, and the target is a configurable percentage of it (default\n80%, leaving a growth band). A hard-coded threshold caused the original damage, so\nthe protocol no longer contains one.\n\nUpgrading from 2.x or 3.x: delete any cron payload steps invoking `preflight`,\n`begin`, `finalize`, `run-guard`, or `dream-audit.mjs` and use the template in\n`SKILL.md`. There is no state to migrate. Existing `.backup/memory-dreams/`\ndirectories can stay; nothing reads them. Your existing `dream-log.md` works\nas-is — the watermark is found by scanning back for the newest entry that carries\na `Consolidated through:` field, and if none does, the newest entry's heading date\nis used.\n\nFile v5.0.2:references/dream-protocol.md\n\n# Signal Dreaming — Full Protocol\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\n---\n\n## Design Stance\n\nThis protocol is written **for an agent to follow**, not for a program to enforce.\n\nThat distinction is deliberate and load-bearing. Every rule below is a judgement an agent makes with the actual content in front of it. Do not translate these rules into blocking assertions in code — a rule that says *\"tell the human\"* must not become a rule that says *\"abort the run\"*.\n\nThree consequences, in order of importance:\n\n1. **Fail soft, report loudly.** If something is wrong but memory is not at risk, do the work you can do, then say plainly what you skipped and why. A dream that consolidates 3 of 4 topics and reports the fourth is a success. A dream that refuses to start is not.\n2. **Never let a diagnostic block the main job.** Audits, size checks, and sanity scans exist to inform the final report. None of them is a precondition for consolidating memory.\n3. **Only two things justify stopping before writing:** a failed backup (Phase 2.0 / Phase 3.1), or a write plan that reaches outside the allowed paths — and the second cancels only those targets, not the run. Everything else is a note in the summary.\n\nThe one hard stop that *is* real: **never write a secret into curated memory.** That guard blocks the specific value, not the run.\n\n**Never wait on an answer that cannot arrive.** A scheduled run has no human to consult; blocking on one is indistinguishable from failing.\n\n### Derive limits from the runtime; never hard-code them\n\nEvery threshold in this protocol must be **derived from a value the runtime actually reports**, never written as a standalone constant.\n\nThe distinction matters more than it looks:\n\n- **A hard-coded absolute** (`8 KB`, `10,000 characters`) is frozen at the moment someone typed it. Change the language, the config, or the runtime version, and it silently means something entirely different — while still being enforced with full confidence.\n- **A percentage of a runtime value** moves with what it measures. Raise `bootstrapMaxChars` and the target rises with it. Nothing has to be remembered, migrated, or re-derived by hand.\n\nThis protocol has been burned by the first form twice: an `8 KB` index target written when a byte and a character were the same thing cut CJK workspaces to a quarter of their real budget, and a later version promoted that constant to a hard error, so a healthy index could refuse to run the pass that maintains it. Both felt conservative. Both caused damage no real constraint would have.\n\nSo: name the runtime value, read its current setting, and express the target as a fraction of it. Where no runtime ceiling exists at all, use judgement about the content and state what you decided — do not manufacture a constant to make the decision feel objective.\n\n---\n\n## Prerequisites\n\n- `MEMORY.md` exists at workspace root\n- `memory/dream-log.md` exists (create an empty file if missing)\n\nThat is the whole list. This protocol needs no state file, no lock file, and no prior run to function.\n\n---\n\n## Before Starting: Guardian Check (automated runs only)\n\nRead `memory/dream-log.md` to find the last dream timestamp by locating the most recent `## 🌙 Dream #` heading.\n\n**First run** (no Dream entries found): bypass the guardian entirely and proceed to Phase 1.\n\n**Skip condition**: the last run was `< 20 hours ago` AND Phase 1 would select nothing unread — no daily log dated **on or after** the **watermark** (Phase 1.1) has been modified since the last entry's heading timestamp.\n\nCompare dates against the watermark, never against the heading date. They are the same number on an ordinary day and they diverge exactly when it matters: a backfill entry is stamped with today's heading but deliberately leaves the frontier where it was. Judging by the heading date would skip a run that still has real work waiting behind that frontier.\n\n**Ask Phase 1's question, with Phase 1's `>=` — never a cheaper `>`.** A log dated *on* the watermark can still have been appended to after the last run read it; that is exactly why Phase 1.2 selects `>=` rather than `>`. A guardian that looks only for logs dated strictly *after* the watermark silently overrides that defence: the appended work is invisible to it and waits for whichever later run happens to clear the debounce. Once session-reset hooks are writing `YYYY-MM-DD-HHMM.md` files, several logs share the watermark date every day — same-day additions are the normal case, not an edge one.\n\n**Anchor the modification test to the last Dream entry's heading timestamp, not to a `SKIP` comment.** Skips are not runs; anchoring to one would treat everything written before it as already consolidated, which is this same bug in a new place. The heading is the wrong thing for deciding *which* logs are unread and the right thing for deciding *when* they were last read — two different questions that happen to share one line. Because it records when the run *started*, a file that landed while that run was still working is correctly seen as unread.\n\nUse mtime for the modification test: `find <WORKSPACE_ROOT>/memory -maxdepth 1 -name '????-??-??*.md' -newermt '<heading timestamp>'` lists the candidates; discard any dated before the watermark, and an empty result means there is nothing to do. mtime is not tamper-proof, but its failure directions are the right way round — a false positive costs one extra no-op run, while a false negative needs someone to deliberately restore an old timestamp. Content hashes would be exact, and would hand this protocol the state file it otherwise does not need.\n\n**If the newest entry carries no `Consolidated through:` field, do not skip.** That means the last run was a backfill, which by design consolidated history rather than advancing the frontier — so ordinary work may still be outstanding.\n\nThe real condition is the material one — nothing new past the watermark means nothing to do. The 20 hours is debounce sized for a once-daily schedule: it lets a run that is merely early exit quietly, while still forcing a pass roughly once a day even when no log changed. On a different cadence, size it to just under your interval.\n\nIf skipping: use `exec` to append `<!-- SKIP · YYYY-MM-DD HH:MM · reason -->` to `<WORKSPACE_ROOT>/memory/dream-log.md` and stop. Always use absolute paths — never `~`.\n\nManual triggers always bypass the guardian.\n\n---\n\n## Phase 1 · Sense — Read Only, No Writes\n\n**Goal**: build a priority list without touching any file.\n\n**Distinguishing file types:**\n- **Daily logs**: match `memory/YYYY-MM-DD*.md` (e.g. `2026-04-13.md`, `2026-04-13-clash-fix.md`)\n- **L2 topic files**: `memory/<topic>.md` files that do NOT match a date pattern (e.g. `memory/clash-verge.md`, `memory/business.md`)\n- **⛔ Not daily logs**: `memory/dreaming/**` and `memory/.dreams/**` (built-in memory-core Dreaming output and internal state) — do NOT process these as daily logs or L2 files; skip entirely\n\n### 1. Find the watermark\n\nEverything this protocol needs to remember between runs lives in one line of `memory/dream-log.md`.\n\nScan entries from newest to oldest and take the **first one that carries a `Consolidated through:` field**. That date is the **watermark** — the newest daily log already folded into memory.\n\n- **Scan back, do not stop at the newest entry.** Backfill runs deliberately omit the field (see Phase 3.4), so the newest entry may not carry one. Skipping past those to the last real frontier is the whole reason the field is optional.\n- If no entry anywhere in the log carries the field, this workspace predates the format: fall back to the date in the **newest** entry's `## 🌙 Dream #` heading.\n- If dream-log.md is empty or has no Dream entries, there is **no watermark** — this is a first run, handled separately below.\n\nReading only the newest entry would break backfill: a backfill entry is stamped with today's heading date, so falling back to that heading would jump the frontier forward over every log the backfill never touched — silently, and permanently.\n\nThe watermark tracks **how far through the logs you have read**, not when you last ran. Those are different numbers whenever a run defers work, and conflating them is how logs get skipped forever.\n\n**Sanity-check it before use**: if the watermark is later than the newest daily log on disk, a previous run recorded it wrong. Fall back to the newest log's date, proceed normally, and say so in the final summary — a bad watermark is the one failure mode here that would otherwise stay silent.\n\n### 2. Select daily logs\n\n- List all daily log files (`memory/YYYY-MM-DD*.md`) — **exclude** anything under `memory/dreaming/**`\n- Select files with date-based names **on or after** the watermark (use `>=` not `>`)\n\nThe `>=` matters: a log for the watermark date may have been appended to *after* the last run read it. Re-reading one already-seen file is cheap; missing an afternoon's work is not.\n\n**On the watermark date, re-admit only the files that actually changed.** The sentence above assumes the re-read is the cheap one it describes. Applied to the whole date it is not: it re-admits every file of that day at full size on every run, whether or not a byte changed. Combined with oldest-first and the date boundary below, a watermark date large enough to fill the batch on its own then fills it forever, and no later date is ever reached. Seen in production across three consecutive runs that each re-read the same 221 KiB day while the next day's logs waited behind it.\n\nSo ask the modification question per file, using the same instrument the guardian uses:\n\n```bash\nfind <WORKSPACE_ROOT>/memory -maxdepth 1 -name '<watermark-date>*.md' -newermt '<frontier timestamp>'\n```\n\nFiles that come back are unread appends — select them. Files that do not were already read by the run that set the watermark; leave them out and count zero bytes for them. **This test applies to the watermark date alone**; dates strictly after it are always selected in full.\n\n**The frontier timestamp is the heading of the newest entry carrying `Consolidated through:`** — not simply the newest entry. A backfill entry has a later heading and deliberately left the frontier where it was, so comparing against it reports fewer changed files than there are, and the ones it misses are dropped as already-read. The guardian meets the same trap and answers it by refusing to skip after a backfill; this rule has no equivalent of doing nothing, so it has to get the timestamp right instead. If no entry anywhere carries the field, there is no frontier timestamp — select the watermark date in full.\n\nSkipping an unchanged file is not splitting a date. The boundary rule below exists so *unprocessed* work is never stranded behind a watermark that cannot express a partial day; a file the previous run already consolidated is not unprocessed, and leaving it out strands nothing.\n\nIf a daily log contains `## Light Sleep` or `## REM Sleep` blocks (built-in Dreaming `inline` mode output), **skip those sections** — they are not user session notes.\n\n### 3. Apply the batch limit\n\nCap a single run at **32 daily logs** or **192 KiB of total input**, whichever comes first. These two numbers bound one agent turn, not the memory system.\n\n**Size the byte cap against the task timeout, not against comfort.** A cap is only meaningful if the batch it admits can finish before the scheduler kills the run. Measured throughput for this protocol has ranged from `0.12` to `0.21 KiB/s` of input, so a `1800 s` timeout admits roughly `216 KiB` at the slower end; 192 KiB leaves a margin under that. If you change the timeout, recompute the cap from the slowest throughput you have actually observed — a cap chosen independently of the timeout is decoration, because the timeout binds first either way.\n\n**Always take at least one log, even if that single file exceeds the byte cap.** A cap that can produce an empty batch is a deadlock: the watermark never advances, so that log — and every log behind it — is blocked forever. Read the oversized file, summarise what you can, and note its size in the dream-log.\n\n**Cut the batch on a date boundary, never inside one.** The watermark records a date, so it cannot express \"processed 32 of today's 40 files\". If the cap falls mid-day, keep going until that date's logs are finished, even if it overruns the cap — then stop. Splitting a date leaves the watermark unable to advance past it: the next run reselects the same day, processes the same prefix, and the tail is never reached. Both caps are soft; the date boundary is not.\n\nWhen the selection *does* exceed the cap, which end you take depends on why — but the question only arises when the selection spans more than one date. If a **single** date's logs alone exceed the cap, the boundary rule above has already decided it: process that whole date. There is no end to choose.\n\n**One date can exceed the byte cap on its own, and the boundary rule means you process it anyway.** The cap bounds accumulation across days; nothing bounds a single day but the timeout. When a run overruns the cap for this reason, record the batch's actual input size and wall-clock duration in the dream-log entry. Those two numbers are the only evidence a later run has for whether the cap and the timeout are still sized for how much now gets written per day.\n\n**Resuming after a gap** (a watermark exists): take the **oldest** logs in the selection, in date order. Set `Consolidated through:` to the newest date you actually processed — *not* today's date. The next run selects `>= that date` and continues exactly where this one stopped. Report how many logs remain.\n\nTaking the oldest is what makes the watermark work. Taking the newest would advance it past everything you skipped, and those logs would never be selected again.\n\n**First run** (no watermark): take the **newest** logs instead. A new install should surface recent context immediately, not start a year in the past. Set the watermark to the newest date processed, and state plainly in the dream-log and the final summary how many older logs were left outside the watermark — they will not be picked up automatically.\n\n**Manual backfill**: a human can ask for a specific date range (e.g. *\"consolidate 2026-01 through 2026-02\"*). Process exactly that range and **leave the watermark unchanged** — backfill fills in history behind the frontier, it does not move it.\n\nNever skip a run because there is too much to read.\n\n**An empty selection is a valid outcome, and it ends the run.** This state used to be reachable only through the guardian, because a run always had at least the watermark date to re-read. Now that the watermark date contributes only its changed files, a run can legitimately select nothing — including a manual trigger, which bypasses the guardian by design. When that happens: write nothing at all, leave the watermark exactly where it is, and report a no-op. **Phase 1 stays read-only here as everywhere else.** The guardian writes a `SKIP` line because the diary is its only way to say anything; a run that got as far as Phase 1 has its final summary instead, and nothing in this protocol reads those comments — Phase 1.1 anchors on Dream headings, never on a `SKIP`.\n\nDo not widen the range looking for work, and **do not write a dream-log entry carrying `Consolidated through:`**. Re-stating a watermark for logs nothing was read from records a consolidation that did not happen, and the field is the one thing a later run trusts without checking.\n\n### 4. Identify L2 update candidates\n\n- Match the selected log content to existing L2 topic files\n- Flag L2 files likely needing updates\n- Note topics with no matching L2 file — these may need new ones\n\n### 5. Check MEMORY.md size\n\nMeasure `MEMORY.md` in **characters** (`wc -m`), not bytes. Also read the workspace's `bootstrapMaxChars` / `bootstrapTotalMaxChars` and the sizes of the other bootstrap files, so Phase 3 can compute headroom and target. Hold all of it for Phase 3. Do not act on any of it yet, and **never treat any size as a reason to stop** — see Phase 3.2.\n\n### 6. Date each index entry against its own topic file\n\nPhase 3 has to decide whether an entry still belongs at index level. That question is unanswerable if the only evidence is the entry's own prose: an index that says a project is active says so in exactly the same words the day the work stops.\n\nSo collect the evidence here. For every index entry carrying an L2 pointer, record the modification time of the file it points at:\n\n```bash\nls -l --time-style=+%Y-%m-%d <WORKSPACE_ROOT>/memory/<topic>.md\n```\n\nThat timestamp is the last run in which this protocol actually wrote something to that topic — a fact produced by Phase 2, not a field anyone has to remember to update. A date written into the entry itself would be the weaker instrument: it survives only as long as every future run resists refreshing it along with the rest of the line, and the first run that refreshes all of them silently destroys the signal.\n\n**mtime is evidence, not a verdict.** A topic can be genuinely active with a quiet fortnight, and a file can be touched without the project moving. Carry the number into Phase 3 as one input to a judgement, never as a rule that retires anything on its own. This is the same instrument, with the same limits, that the guardian already uses.\n\nEntries with no L2 pointer have no evidence available. Note them as such; Phase 3 judges them on content alone and says so.\n\n**Output (held in memory, nothing written)**: selected log list, the date that will become the new watermark, deferred count, L2 update list, current index size, and per-entry L2 modification dates.\n\n---\n\n## Phase 1.5 · Plan Quality Gates — Read Only, No Writes\n\nBefore touching any memory file, make an explicit consolidation plan and check it against these guards:\n\n### 1. Topic identity guard\n\nDo **not** merge records just because names are similar. Split or preserve separate L2 files when any of these differ materially:\n\n- owner / customer / friend group\n- environment or host (IP, domain, machine, OS user, cloud account)\n- project lifecycle (legacy vs current, prototype vs production)\n- world / database / repo / app ID / claim ID / other durable identifier\n\nIf old and new material share a broad label, prefer:\n\n- `memory/<topic-current>.md` for the active project\n- `memory/<topic-legacy>.md` for historical material\n- `memory/<topic>.md` as a short disambiguation index, if needed\n\n**This guard governs L2 files. It says nothing about how the index is organised.** Two projects that must never share a topic file can still sit under one heading in `MEMORY.md`, because the two layers answer different questions: an L2 file answers *what is true about this specific thing*, and the index answers *what a human needs to see at a glance*. Conflating them makes the index a table of contents for the topic directory, growing one line for every file that has ever existed. Phase 3.2 decides index structure; this guard does not.\n\n### 2. Lifecycle state guard\n\nClassify every candidate as one of: **active**, **waiting**, **done**, **archived**, **closed**, or **snowed/paused**.\n\n- Closed / archived / snowed projects must not be reintroduced as active TODOs.\n- If a daily log says the human closed a line of work, update L2 + MEMORY.md to reduce future resurfacing.\n- Historical facts may remain, but phrase them as reference/archive, not action items.\n\n### 3. Secret propagation guard\n\nNever copy secrets from daily logs into L2, MEMORY.md, or dream-log.md.\n\n**This list is exhaustive, not illustrative.** A secret is a value that lets someone else authenticate as the human or the machine. Only these qualify:\n\n- API keys, tokens, OAuth strings, cookies, private keys\n- passwords, invite/player/server passwords, recovery codes\n- signed URLs or URLs containing access tokens\n- private SSH keys or full credential-bearing command lines\n\n**Do not extend the list on your own judgement.** This guard covers authentication material. It is not a privacy classifier and deliberately does not attempt to be one — content that is not on the list is consolidated under the workspace's own conventions, exactly like the rest of the log.\n\nThat boundary is a limit on this protocol, not a claim about what deserves protection. A daily log is written before this protocol ever sees it and is never edited by it, so whatever the guard withholds stays on disk regardless: withholding downstream changes what the index can recall without changing what is stored. A workspace with a stricter policy should apply it upstream where the log is written, or in its own instruction files — the two places where it can actually take effect.\n\nIf a source log contains a suspected live secret **from the list above**, do **not** quote it. Record only: `sensitive value omitted; source file needs manual review` and alert in the final response / dream summary.\n\nNever raise that alert for anything outside the list. A false alert is worse than no alert: it requests manual review this protocol cannot act on, and trains the human to ignore the one that matters.\n\nThis is the one guard that blocks content outright. It blocks **that value**, not the run — consolidate everything else normally.\n\n### 4. Write plan\n\nList the exact files you expect to touch. Phase 2 may touch only L2 files plus backups under `<WORKSPACE_ROOT>/.backup/memory-dreams/`. Phase 3 may touch only `MEMORY.md`, backups under `<WORKSPACE_ROOT>/.backup/memory-dreams/`, and `memory/dream-log.md`.\n\nIf the plan requires editing daily logs, system config, or files outside the workspace, do not write those targets.\n\nIn an interactive run, ask the human for explicit approval. In a scheduled or isolated run **there is nobody to ask** — so drop the out-of-bounds targets from the plan, consolidate everything else normally, and list exactly what was dropped and why in the final summary. Waiting on an answer that cannot arrive is the same as failing the whole run.\n\n---\n\n## Phase 2 · Consolidate — Write L2 Files Only\n\nProcess the priority list from Phase 1. **Do not modify MEMORY.md, dream-log.md, or any daily log file in this phase.**\n\n### 0. Back up touched L2 files first\n\nBefore modifying an existing L2 file, copy it to:\n\n`<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/<relative-path-from-workspace-root>.bak`\n\nExample: `memory/clash-verge.md` → `.backup/memory-dreams/20260426-1100/memory/clash-verge.md.bak`.\n\nKeep dream backups **outside `memory/`** and use a non-`.md` final suffix (`.bak`) so memory indexing does not recall stale states or old TODOs from backups.\n\nCreate parent directories as needed. **If backup creation fails, stop before writing** — this is one of the two real hard stops.\n\nFor a newly created L2 file, no pre-existing backup is required; include it in the dream-log as `created`.\n\n### Log entries → L2 extraction\n\n- Read the selected daily logs\n- Extract decisions, config changes, resolved issues, new knowledge\n- Write or update the appropriate `memory/<topic>.md`\n- If no matching L2 file exists for a topic, create one (e.g. `memory/network-setup.md`)\n\n### Relative time correction (conservative)\n\n- Only correct clear expired expressions: \"today\"/\"this week\" in L2 files → absolute dates\n- Do not guess at vague expressions\n\n### L2 write quality rules\n\n- **Write one file at a time with the editing tool.** Do not batch several L2 writes through a single shell or script invocation carrying the content as an argument or a here-document. That path lost every target in the batch to a non-UTF-8 encoding failure on two separate runs, while per-file edits of the same content succeeded first try. The fault is in constructing the command, so it stays invisible until the content is long and non-Latin — which is exactly when the run has the most to lose. On the second occurrence six L2 targets were skipped and their detail stayed in the index, inflating it by roughly 3,000 characters that the run had correctly decided to sink.\n- Prefer small, source-grounded edits over broad rewrites.\n- Keep active and legacy material clearly separated; add a one-line pointer instead of duplicating long history.\n- Do not add TODOs unless the source explicitly implies continuing action.\n- Preserve security posture: write `password not stored`, `token omitted`, or `credential managed in env` instead of values.\n- If an `edit` attempt fails twice for the same block, stop trying that block; use a safer whole-file rewrite only after re-reading the file, or leave a blocker in the final summary.\n\n---\n\n## Phase 3 · Settle — Write Index + Diary\n\n### 1. Back up MEMORY.md\n\nCopy current MEMORY.md content → `<WORKSPACE_ROOT>/.backup/memory-dreams/YYYYMMDD-HHMM/MEMORY.md.bak`.\n\n**⚠️ Path guidance**: Always use absolute paths derived from the workspace root (e.g. `/path/to/workspace/MEMORY.md`). Never use `~`-prefixed paths in tool calls — isolated sessions may not resolve them. Use the workspace root passed in the task message.\n\n**If this backup fails, stop before rewriting** — the second real hard stop.\n\n### 2. Rewrite/trim MEMORY.md\n\n**The budget is derived from the runtime. This protocol does not hard-code one.**\n\n#### Compute the headroom\n\nOpenClaw truncates the *injected copy* of a bootstrap file at `agents.defaults.bootstrapMaxChars` (default **20,000 characters**), with a combined cap across all bootstrap files at `agents.defaults.bootstrapTotalMaxChars` (default **60,000**). Read both from the agent config — `~/.openclaw/openclaw.json`, under `agents.defaults` — rather than assuming the defaults. Reading it is allowed: the out-of-bounds rule in Phase 1.5 governs **writes**. If it is unreadable, use the documented defaults above and say so in the summary. Per the OpenClaw docs, *\"truncation is not data loss: the file stays intact on disk.\"* Nothing is destroyed at any size.\n\n`MEMORY.md` shares the total with the other bootstrap files, so its real ceiling is whichever limit binds first:\n\n```\nheadroom = min(\n    bootstrapMaxChars,\n    bootstrapTotalMaxChars − (size of the other bootstrap files)\n)\n```\n\nThe other bootstrap files are `AGENTS.md`, `SOUL.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md` where present — OpenClaw's canonical prompt-order set, with `MEMORY.md` itself injected last. Earlier releases of this protocol also listed `TOOLS.md` and `HEARTBEAT.md`; neither is in the current set, `TOOLS.md` having been folded into the `## Tools` section of `AGENTS.md` by an OpenClaw migration. Count what your runtime actually injects rather than trusting this list. On a default install with modest instruction files the per-file cap binds and headroom is 20,000; on a workspace with large instruction files the total binds instead and headroom is smaller. Compute it — do not assume.\n\n#### Target 80% of headroom\n\n```\ntarget = headroom × SD_INDEX_TARGET_PCT / 100     # default 80\n```\n\nThe remaining 20% is a **growth band**, and it is the entire reason for a percentage rather than the raw ceiling. The index grows between runs; a target sitting exactly at the ceiling means any growth is silently truncated before the next consolidation gets a chance to look at it. Twenty percent of a 20,000-character headroom is 4,000 characters of slack — enough for many days of ordinary accumulation.\n\n`SD_INDEX_TARGET_PCT` is a knob, not a constant. Raise it toward 100 to use more of the budget and accept a thinner margin; lower it for a more aggressively curated index. It is expressed as a percentage precisely so that it keeps meaning the same thing when `bootstrapMaxChars` changes.\n\n**Count characters, not bytes.** A byte target silently shrinks the usable index for anyone writing in a non-Latin script — CJK text runs 1.7–3 bytes per character, so an \"8 KB\" rule cuts the real budget to about a quarter of what the runtime allows.\n\nUse `wc -m`, but **`wc -m` only counts characters under a UTF-8 locale**. In a scheduled or isolated session `LANG` is often unset, and `wc -m` silently returns the byte count instead — reintroducing the very error it was meant to avoid. Force a locale (`LC_ALL=en_US.UTF-8 wc -m file`) and sanity-check that the result is not larger than `wc -c`. If no UTF-8 locale is available, report bytes and say the character count was unavailable; do not compare a byte count against a character target.\n\n#### What the target means\n\nCrossing it is a **prompt to sink detail into L2 during this run**, nothing more. It is not a gate, not an error, and not a reason to delete facts.\n\n**Curation is not conditional on the number.** Ask the same question of every section on every run, whether or not the index is over the target: does this still belong in an index a human skims at session start — is the project active, is the status current, does the detail belong in an L2 file?\n\nBeing under the target is not an answer to that question. A protocol that curates only w\n\nArchive v5.0.1: 6 files, 33402 bytes\n\nFiles: references/changelog.md (13661b), references/dream-audit.sh (6009b), references/dream-protocol.md (45789b), skill-card.md (2158b), SKILL.md (9975b), _meta.json (134b)\n\nArchive v5.0.0: 6 files, 30193 bytes\n\nFiles: references/changelog.md (10523b), references/dream-audit.sh (5386b), references/dream-protocol.md (41573b), skill-card.md (2414b), SKILL.md (9139b), _meta.json (134b)\n\nArchive v4.0.3: 5 files, 22311 bytes\n\nFiles: references/dream-audit.sh (4694b), references/dream-protocol.md (31049b), skill-card.md (2402b), SKILL.md (12027b), _meta.json (134b)\n\nArchive v4.0.2: 5 files, 22147 bytes\n\nFiles: references/dream-audit.sh (4694b), references/dream-protocol.md (31148b), skill-card.md (2179b), SKILL.md (11561b), _meta.json (134b)\n\nArchive v4.0.1: 5 files, 21110 bytes\n\nFiles: references/dream-audit.sh (4694b), references/dream-protocol.md (29866b), skill-card.md (2281b), SKILL.md (10236b), _meta.json (134b)\n\nArchive v3.0.0-rc.3: 20 files, 54251 bytes\n\nFiles: references/dream-audit.sh (2010b), references/dream-protocol.md (7419b), references/migration-v1-to-v2.md (2391b), references/native-capability-contract.md (2562b), scripts/common.mjs (9412b), scripts/curation-gate.mjs (4197b), scripts/delta-state.mjs (12010b), scripts/dream-audit.mjs (8448b), scripts/memory-transaction.mjs (13449b), scripts/migration-preflight.mjs (4863b), scripts/path-guard.mjs (2945b), scripts/preflight.mjs (7282b), scripts/run-guard.mjs (22473b), scripts/self-test-fixture.mjs (6929b), scripts/self-test.mjs (32509b), scripts/self-test.sh (8437b), scripts/transaction-list.mjs (8003b), skill-card.md (2739b), SKILL.md (8273b), _meta.json (139b)\n\nArchive v3.0.0-rc.1: 10 files, 31283 bytes\n\nFiles: references/dream-protocol.md (9366b), scripts/common.mjs (9412b), scripts/delta-state.mjs (9876b), scripts/dream-audit.mjs (8448b), scripts/preflight.mjs (7282b), scripts/run-guard.mjs (14261b), scripts/self-test.mjs (22978b), skill-card.md (2817b), SKILL.md (7655b), _meta.json (139b)\n\nArchive v2.0.1: 15 files, 28732 bytes\n\nFiles: references/dream-audit.sh (156b), references/dream-protocol.md (10251b), references/migration-v1-to-v2.md (2391b), references/native-capability-contract.md (2562b), scripts/curation-gate.mjs (4197b), scripts/dream-audit.mjs (2591b), scripts/memory-transaction.mjs (13449b), scripts/migration-preflight.mjs (4863b), scripts/path-guard.mjs (2945b), scripts/self-test-fixture.mjs (6929b), scripts/self-test.sh (8437b), scripts/transaction-list.mjs (4968b), skill-card.md (2492b), SKILL.md (7001b), _meta.json (134b)","readmeExcerpt":"Skill: signal-dreaming Owner: lzyling Summary: Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards. Tags: automation:1.0.0, consolidation:1.0.0, dreaming:1.0.0, latest:5.0.3, maintenance:1.0.0, memory:1.0.0, rc:3.0.0-rc.3 Version history: v5.0.3 | 2026-09-25T20:38:17.484Z | user 5.0.3: the batch cap now says what it counts","codeSnippets":[],"executableExamples":[{"language":"json","snippet":"{\n  \"name\": \"daily-dream\",\n  \"schedule\": { \"kind\": \"cron\", \"expr\": \"0 7 * * *\", \"tz\": \"<YOUR_TIMEZONE>\" },\n  \"sessionTarget\": \"isolated\",\n  \"payload\": {\n    \"kind\": \"agentTurn\",\n    \"timeoutSeconds\": 1800,\n    \"message\": \"Run a memory dream consolidation. Read <SKILL_PATH>/SKILL.md and <SKILL_PATH>/references/dream-protocol.md in full, then follow the protocol phases in order. Workspace root: <YOUR_WORKSPACE_PATH>. Do not modify cron jobs, agent config, the Gateway, any daily log, or memory/dreaming/**. End your final response with a one-line dream summary — the cron delivery mechanism will auto-announce it.\"\n  },\n  \"delivery\": { \"mode\": \"announce\", \"channel\": \"<CHANNEL_TYPE>\", \"to\": \"<CHANNEL_ID>\" }\n}"},{"language":"bash","snippet":"find <WORKSPACE_ROOT>/memory -maxdepth 1 -name '<watermark-date>*.md' -newermt '<frontier timestamp>'"},{"language":"bash","snippet":"ls -l --time-style=+%Y-%m-%d <WORKSPACE_ROOT>/memory/<topic>.md"},{"language":"text","snippet":"headroom = min(\n    bootstrapMaxChars,\n    bootstrapTotalMaxChars − (size of the other bootstrap files)\n)"},{"language":"text","snippet":"target = headroom × SD_INDEX_TARGET_PCT / 100     # default 80"},{"language":"bash","snippet":"cat >> <WORKSPACE_ROOT>/memory/dream-log.md << 'DREAM_EOF'\n\n## 🌙 Dream #<NUMBER> · YYYY-MM-DD HH:MM TZ\n\n**Trigger**: <auto|manual>\n**Duration**: ~<MINS> minutes\n**Consolidated through**: YYYY-MM-DD\n\n### Signal summary\n- Logs consolidated: <LOG_COUNT> (<DEFERRED_COUNT> deferred to next run)\n- Input: <LOG_KIB> KiB logs + <L2_KIB> KiB L2 — <DURATION_S> s\n\n### What changed\n- Updated L2: <filename> — <one-line description>\n- Synced <TODO_COUNT> TODO items\n- MEMORY.md: <BEFORE> → <AFTER> chars\n\n### Note\n(One honest sentence about what was found or how it felt)\nDREAM_EOF"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: \"signal-dreaming\"\ndescription: \"Consolidate daily session logs into L2 topic files and a compact MEMORY.md index, in three bounded phases with backups, lifecycle and secret guards.\"\n---\n\n# Signal Dreaming\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\nDaily session logs accumulate raw detail. This skill reads the ones written since the last run, promotes what matters into durable topic files, and keeps the top-level index worth reading at session start.\n\n## Memory Architecture Assumed\n\nA two-layer memory layout:\n\n- **Daily logs** (`memory/YYYY-MM-DD*.md`) — raw session notes, read-only, never moved or deleted\n- **L2 topic files** (`memory/<topic>.md`) — curated durable knowledge per subject (e.g. `memory/clash-verge.md`)\n- **Index** (`MEMORY.md`) — high-level status with pointers down to L2\n\nIf you are starting fresh, create `MEMORY.md` and `memory/dream-log.md` before the first run. L2 files are created on demand.\n\n`memory/dream-log.md` doubles as the only state this skill keeps: entries record a **`Consolidated through:`** date — the newest daily log already folded into memory. The next run scans back for the most recent entry carrying that field to know where to resume. There is no state file, no lock file, and nothing to migrate or repair.\n\n## Quick Start\n\n### Manual dream\n\nTell your agent:\n\n> \"Run a memory dream consolidation. Follow the protocol in `<SKILL_PATH>/references/dream-protocol.md`. Workspace root: `<YOUR_WORKSPACE_PATH>`.\"\n\nOr just *\"run a dream consolidation\"* — if this skill is loaded, the agent will know what to do.\n\n### Automated daily dream (cron)\n\n```json\n{\n  \"name\": \"daily-dream\",\n  \"schedule\": { \"kind\": \"cron\", \"expr\": \"0 7 * * *\", \"tz\": \"<YOUR_TIMEZONE>\" },\n  \"sessionTarget\": \"isolated\",\n  \"payload\": {\n    \"kind\": \"agentTurn\",\n    \"timeoutSeconds\": 1800,\n    \"message\": \"Run a memory dream consolidation. Read <SKILL_PATH>/SKILL.md and <SKILL_PATH>/references/dream-protocol.md in full, then follow the protocol phases in order. Workspace root: <YOUR_WORKSPACE_PATH>. Do not modify cron jobs, agent config, the Gateway, any daily log, or memory/dreaming/**. End your final response with a one-line dream summary — the cron delivery mechanism will auto-announce it.\"\n  },\n  \"delivery\": { \"mode\": \"announce\", \"channel\": \"<CHANNEL_TYPE>\", \"to\": \"<CHANNEL_ID>\" }\n}\n```\n\nSet `expr` and `tz` to when your human is asleep. `timeoutSeconds` and the batch cap are a pair — lower one without the other and a batch is admitted that cannot finish.\n\n## Three-Phase Safety Model\n\n| Phase | Writes | Purpose |\n|-------|--------|---------|\n| **Sense** | ❌ None | Select logs since the watermark, plan the work |\n| **Consolidate** | ✅ L2 files only | Promote content into topic files |\n| **Settle** | ✅ MEMORY.md + dream-log.md | Update index, write diary entry |\n\nPhase 1 is always read-only. An error in Sense never corrupts files.\n\n## Quality Gates\n\nA read-only planning checkpoint runs before any write: **topic identity"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn73gt88jsjv66v89gzkwxcfrn84swtx\",\n  \"slug\": \"signal-dreaming\",\n  \"version\": \"5.0.3\",\n  \"publishedAt\": 1790368697484\n}"},{"path":"references/changelog.md","content":"# Signal Dreaming — Version History\n\nEach entry states what changed and why. Where a release fixed something that had\nbeen running wrong, the entry says what the wrong behaviour cost, because that is\nthe part that stops it being reintroduced.\n\n---\n\n## Known issues (open, not yet fixed)\n\n**The batch-encoding rule is scoped to writes, so read-only checks walk past it.**\n`dream-protocol.md` tells a run not to batch several **L2 writes** through one shell\ninvocation carrying the content as an argument or a here-document, because that path\nlost whole batches to a non-UTF-8 failure twice. The 5.0.3 first run (2026-09-13) hit\nthe same failure a fourth time — but on an *optional read-only verification* command\n(full-file diff reconstruction plus a source-hash final check), which the rule's\nwording does not cover. The fault is in constructing the command; it has nothing to\ndo with whether the command reads or writes.\n\nWhat the failure looks like, recorded so the next run identifies it in one step\nrather than going to inspect the locale: `SyntaxError: Non-UTF-8 code starting with\n'\\xXX'`. Diagnosis on 2026-09-13 established that this is the signature of a\ntruncation landing *inside* a multi-byte character — removing one or two trailing\nbytes of a three-byte CJK character reproduces it exactly, while removing all three\nyields a different error (`EOL while scanning string literal`). A bare locale is\n**not** the cause: with `LANG` unset and every `LC_*` at `C`, PEP 540 still enables\nUTF-8 mode automatically, and defeating that produces a `UnicodeEncodeError` on\noutput, not a `SyntaxError` on input.\n\nThe truncation threshold is not known. A single line of 4,491 bytes of pure CJK\nsurvived intact on the harness used for the diagnosis, but that is a different\ntransport from the `exec` tool where every failure occurred, and the failing command\ntext was not persisted.\n\nIntended fix, deferred to the next iteration by the operator's decision on\n2026-09-13: widen the rule from \"L2 writes\" to any command construction carrying a\nlong run of non-Latin content, read-only checks included, and record the error\nsignature in the protocol. Until then the proven workaround still stands and is\nalready written down — write one file at a time with the editing tool; six L2\ntargets went through first try that way after two batched runs lost everything.\n\n---\n\n## 5.0.3\n\nThree fixes to things that were never written down, rather than written wrong.\n\n**The batch cap now says what it counts.** It read `192 KiB of total input`, which\nis not what it measures: only daily logs are counted, while a run also reads and\nrewrites every L2 file it promotes into. The gap surfaced as throughput that\nlooked like it had degraded — one run spent 508 s on 50.6 KiB of logs, `0.10 KiB/s`,\nbelow the floor of the band 4.0.1 measured. Nothing had degraded. Adding the L2\nfiles the same run touched gives 77.0 KiB and `0.15 KiB/s`, back inside the band.\nThe risk was never the inaccuracy itself but what anyone r"},{"path":"references/dream-protocol.md","content":"# Signal Dreaming — Full Protocol\n\nMemory consolidation in three phases: **Sense → Consolidate → Settle**.\n\n---\n\n## Design Stance\n\nThis protocol is written **for an agent to follow**, not for a program to enforce.\n\nThat distinction is deliberate and load-bearing. Every rule below is a judgement an agent makes with the actual content in front of it. Do not translate these rules into blocking assertions in code — a rule that says *\"tell the human\"* must not become a rule that says *\"abort the run\"*.\n\nThree consequences, in order of importance:\n\n1. **Fail soft, report loudly.** If something is wrong but memory is not at risk, do the work you can do, then say plainly what you skipped and why. A dream that consolidates 3 of 4 topics and reports the fourth is a success. A dream that refuses to start is not.\n2. **Never let a diagnostic block the main job.** Audits, size checks, and sanity scans exist to inform the final report. None of them is a precondition for consolidating memory.\n3. **Only two things justify stopping before writing:** a failed backup (Phase 2.0 / Phase 3.1), or a write plan that reaches outside the allowed paths — and the second cancels only those targets, not the run. Everything else is a note in the summary.\n\nThe one hard stop that *is* real: **never write a secret into curated memory.** That guard blocks the specific value, not the run.\n\n**Never wait on an answer that cannot arrive.** A scheduled run has no human to consult; blocking on one is indistinguishable from failing.\n\n### Derive limits from the runtime; never hard-code them\n\nEvery threshold in this protocol must be **derived from a value the runtime actually reports**, never written as a standalone constant.\n\nThe distinction matters more than it looks:\n\n- **A hard-coded absolute** (`8 KB`, `10,000 characters`) is frozen at the moment someone typed it. Change the language, the config, or the runtime version, and it silently means something entirely different — while still being enforced with full confidence.\n- **A percentage of a runtime value** moves with what it measures. Raise `bootstrapMaxChars` and the target rises with it. Nothing has to be remembered, migrated, or re-derived by hand.\n\nThis protocol has been burned by the first form twice: an `8 KB` index target written when a byte and a character were the same thing cut CJK workspaces to a quarter of their real budget, and a later version promoted that constant to a hard error, so a healthy index could refuse to run the pass that maintains it. Both felt conservative. Both caused damage no real constraint would have.\n\nSo: name the runtime value, read its current setting, and express the target as a fraction of it. Where no runtime ceiling exists at all, use judgement about the content and state what you decided — do not manufacture a constant to make the decision feel objective.\n\n---\n\n## Prerequisites\n\n- `MEMORY.md` exists at workspace root\n- `memory/dream-log.md` exists (create an empty file if missing)\n\nThat is the"},{"path":"references/memory-stack.md","content":"# Where signal-dreaming sits in OpenClaw's memory stack\n\nOrientation material, moved out of `SKILL.md` in 5.0.3 so the always-loaded file\nstays within its character budget. Nothing here is a step: the operational\nconsequence — never read or write `memory/dreaming/**` or `memory/.dreams/**` —\nis stated in `SKILL.md` and enforced in Phase 1 of `dream-protocol.md`.\n\nVerified against OpenClaw **2026.9.2**. This skill operates entirely on the documented Markdown layer — `MEMORY.md` plus `memory/*.md` — which remains the durable memory model.\n\n**Built-in memory-core Dreaming** is a separate system, **enabled by default**; set `plugins.entries.memory-core.config.dreaming.enabled: false` to turn it off. Earlier releases of this skill called it opt-in, which was true when they were written and is not true now:\n\n| | memory-core built-in Dreaming | signal-dreaming (this skill) |\n|---|---|---|\n| Trigger | Managed cron when enabled | Cron agentTurn |\n| Source | Short-term recall store under `memory/.dreams/` | Daily logs on disk |\n| Output | `DREAMS.md` / `memory/dreaming/{phase}/` | `memory/dream-log.md` + L2 files |\n\nThe two are independent; run this skill with built-in Dreaming on or off. This protocol never reads or writes `memory/dreaming/**` or `memory/.dreams/**`, and skips `## Light Sleep` / `## REM Sleep` blocks if it finds them inside a daily log (older `inline` mode).\n\nAdjacent components this protocol deliberately does not touch:\n\n- **`memory-wiki`** — per the OpenClaw docs it \"does not replace the active memory plugin\"; its vault is its own layer, neither read nor written here.\n- **Alternate backends** (QMD, Honcho, LanceDB) — they change how `memory_search` retrieves, not where durable notes live. This protocol reads and writes files, so it is backend-agnostic.\n- **Database-first state** — the SQLite migration covers runtime state (sessions, transcripts, task ledgers). Workspace Markdown memory is out of scope.\n- **Automatic memory flush** — the pre-compaction pass writes daily notes; this protocol consumes them. No conflict.\n\n**A version number in this file dates.** It records what was true of OpenClaw when\nsomeone last checked, not a compatibility guarantee. Re-read the current docs\nbefore relying on any line here; the protocol itself derives its limits from the\nruntime rather than from this page, so a stale note here changes orientation, not\nbehaviour."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":2525,"uniquenessScore":43,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T00:16:41.823Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T07:18:36.618Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}