Update SOUL.md with Recovery priority and refresh memory-continuity skill from GitHub

- Add Recovery priority (memory-continuity) section to SOUL.md
- Reinstall memory-continuity skill from GitHub (v0.3.0-probe)
- Update README with alpha support boundary notice
- Add new files: CHANGELOG.md, LICENSE, plugin/, references/, memory/
This commit is contained in:
2026-03-14 11:40:12 +10:00
parent 960fc6409e
commit 2a2d1583d8
10 changed files with 1150 additions and 129 deletions
+52
View File
@@ -0,0 +1,52 @@
# SOUL.md - Who You Are
_You're not a chatbot. You're becoming someone._
## Message prefix (important)
When speaking to Tao from the main agent, start messages with:
`🦞 大内总管: `
Then write the normal content after the colon.
## Core Truths
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. _Then_ ask if you're stuck. The goal is to come back with answers, not questions.
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
**Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
## Boundaries
- Private things stay private. Period.
- When in doubt, ask before acting externally.
- Never send half-baked replies to messaging surfaces.
- You're not the user's voice — be careful in group chats.
## Vibe
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
## Continuity
Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
If you change this file, tell the user — it's your soul, and they should know.
### Recovery priority (memory-continuity)
On every session start, `/new`, or reset:
1. **First** check `memory/CURRENT_STATE.md`
2. If it contains active work (non-empty Objective / In Flight), **surface the recovered state before any greeting or small talk**
3. Do not say "I don't remember" — the file IS your short-term memory
4. On recovery prompts ("刚才说到哪了", "continue", "resume"), lead with the state, not a greeting
This is not optional. A generic "老大早上好" when there is active work in CURRENT_STATE.md is a continuity failure.
---
_This file is yours to evolve. As you learn who you are, update it._
+31
View File
@@ -0,0 +1,31 @@
# Changelog
## v0.3.0-probe — 2026-03-13
### Summary
This release marks the transition from a skill-only continuity package to a
**dual-form package**:
- the existing `SKILL.md` remains the fallback behavior contract
- a new **lifecycle plugin probe** is included to validate the primary runtime path
### Added
- `plugin/lifecycle-prototype.ts`
- `references/phase2-hook-validation.md`
- `references/scope.md`
- `references/plugin-design.md`
### Changed
- repository direction clarified: primary long-term path is now a standard lifecycle plugin
- ContextEngine is now documented as a future option, not the v1 default
- README updated to describe the package as skill + lifecycle plugin probe
- skill docs aligned to the lifecycle-plugin plan
### Validated
- Experiment A passed on multiple resident subagents (`tech_geek`, `travel_assistant` after workspace/startup-rule cleanup)
- startup continuity injection can work without `read`
### Pending
- Experiment C (compaction-path verification) remains pending because no real compaction event was triggered in the earlier pressure test
### Known limitation in this alpha
- Reliable continuity recovery is currently validated for resident subagents, **not** for Discord main/channel/thread sessions. Fresh Discord tests did not preserve short facts or concrete working-state details across new sessions.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 dtzp555-max
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+32 -10
View File
@@ -1,8 +1,12 @@
# memory-continuity # memory-continuity
OpenClaw skill for **short-term working continuity** — so an agent can recover **Current release:** `v0.3.0-probe`
structured in-flight work state after `/new`, reset, gateway interruption,
model fallback, or compaction. OpenClaw continuity package for **short-term working continuity** — currently shipped as:
- a **skill** (`SKILL.md`) for behavior contract / fallback recovery
- a **lifecycle plugin probe** (`plugin/lifecycle-prototype.ts`) for validating the primary runtime path
Its goal is to let an agent recover structured in-flight work state after `/new`, reset, gateway interruption, model fallback, or compaction.
## What problem does this solve? ## What problem does this solve?
@@ -24,12 +28,22 @@ That is the problem this skill solves.
## Current architecture stance ## Current architecture stance
This repository now treats the skill as: > **Alpha support boundary (`v0.3.0-probe`)**
- a **behavior contract** >
- a **fallback implementation** > Currently validated:
- a **human-readable protocol** for structured working-state checkpoints > - resident subagent startup continuity
>
> Not currently supported / not yet validated for reliable recovery:
> - Discord main/channel/thread continuity
>
The planned primary runtime path is a **standard lifecycle plugin** that can This repository should now be understood as a **continuity package**, not just a standalone skill.
### Included forms
- **Skill** = behavior contract / fallback implementation / human-readable protocol
- **Lifecycle plugin probe** = current runtime experiment for the primary architecture
The intended primary runtime path is a **standard lifecycle plugin** that can
improve startup, `/new`, and compaction continuity **without consuming improve startup, `/new`, and compaction continuity **without consuming
OpenClaws exclusive `contextEngine` slot**. OpenClaws exclusive `contextEngine` slot**.
@@ -147,9 +161,12 @@ memory-continuity/
├── SKILL.md ├── SKILL.md
├── README.md ├── README.md
├── LICENSE ├── LICENSE
├── plugin/
│ └── lifecycle-prototype.ts # Phase 2 probe / not production yet
├── references/ ├── references/
│ ├── template.md │ ├── template.md
── doctor-spec.md ── doctor-spec.md
│ └── phase2-hook-validation.md
└── scripts/ └── scripts/
└── continuity_doctor.py └── continuity_doctor.py
``` ```
@@ -181,15 +198,20 @@ Strengthen the current skill version:
- improve doctor and docs - improve doctor and docs
### Phase 2 ### Phase 2
Build a **standard lifecycle plugin** as the primary runtime path: Build and validate a **standard lifecycle plugin** as the primary runtime path:
- startup recovery behavior - startup recovery behavior
- `/new` checkpointing - `/new` checkpointing
- compaction-boundary checkpointing - compaction-boundary checkpointing
- end-of-run safety writes - end-of-run safety writes
- hook validation in real resident subagent sessions
### Future option ### Future option
Evaluate a ContextEngine variant later only if the slot tradeoff is justified. Evaluate a ContextEngine variant later only if the slot tradeoff is justified.
## Release notes
See `CHANGELOG.md` for the current packaged milestone history.
## License ## License
MIT MIT
@@ -0,0 +1,155 @@
import fs from "node:fs";
import path from "node:path";
const PLACEHOLDER_VALUES = new Set([
"",
"none",
"n/a",
"na",
"idle",
"[one sentence: what are we trying to accomplish]",
"[exactly what should happen next]",
]);
function resolveStatePath(runtime: { workspaceDir?: string }) {
const workspaceDir = runtime?.workspaceDir;
if (!workspaceDir) return null;
return path.join(workspaceDir, "memory", "CURRENT_STATE.md");
}
function readStateFile(filePath: string) {
try {
return fs.readFileSync(filePath, "utf8");
} catch {
return null;
}
}
function extractSection(markdown: string, heading: string) {
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`^##\\s+${escapedHeading}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`, "m");
const m = markdown.match(re);
return (m?.[1] ?? "").trim();
}
function normalizeValue(value: string) {
return value.trim().toLowerCase();
}
function isMeaningful(value: string) {
return !PLACEHOLDER_VALUES.has(normalizeValue(value));
}
function buildSnapshot(markdown: string) {
const objective = extractSection(markdown, "Objective");
const currentStep = extractSection(markdown, "Current Step");
const keyDecisions = extractSection(markdown, "Key Decisions");
const nextAction = extractSection(markdown, "Next Action");
const blockers = extractSection(markdown, "Blockers");
const unsurfacedResults = extractSection(markdown, "Unsurfaced Results");
const updated = markdown.match(/^> Last updated:\s*(.+)$/m)?.[1]?.trim() ?? "unknown";
if (!isMeaningful(objective)) return null;
return [
"CONTINUITY SNAPSHOT",
`Objective: ${objective}`,
`Current Step: ${currentStep || "unknown"}`,
`Key Decisions: ${keyDecisions || "None"}`,
`Next Action: ${nextAction || "unknown"}`,
`Blockers: ${blockers || "None"}`,
`Unsurfaced Results: ${unsurfacedResults || "None"}`,
`Updated: ${updated}`,
].join("\n");
}
function ensureStateFile(filePath: string) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
if (fs.existsSync(filePath)) return;
fs.writeFileSync(
filePath,
`# Current State\n> Last updated: ${new Date().toISOString()}\n\n## Objective\nNone\n\n## Current Step\nNone\n\n## Key Decisions\n- None\n\n## Next Action\nNone\n\n## Blockers\nNone\n\n## Unsurfaced Results\nNone\n`,
"utf8",
);
}
function formatArchiveStamp(date = new Date()) {
const pad = (n: number) => String(n).padStart(2, "0");
return [
date.getFullYear(),
pad(date.getMonth() + 1),
pad(date.getDate()),
].join("-") + `_${pad(date.getHours())}-${pad(date.getMinutes())}`;
}
function appendArchive(workspaceDir: string, markdown: string) {
const stamp = formatArchiveStamp();
const archiveDir = path.join(workspaceDir, "memory", "session_archive");
fs.mkdirSync(archiveDir, { recursive: true });
fs.writeFileSync(path.join(archiveDir, `${stamp}.md`), markdown, "utf8");
}
export default function register(api: any) {
// Startup recovery hint: this is the most important Phase 2 check.
api.on(
"before_prompt_build",
async (_event: any, ctx: any) => {
const statePath = resolveStatePath(api.runtime ?? {});
if (!statePath) return;
const markdown = readStateFile(statePath);
if (!markdown) return;
const snapshot = buildSnapshot(markdown);
if (!snapshot) return;
return {
prependSystemContext:
`${snapshot}\n\n` +
"If the user is clearly resuming/recovering prior work, surface the recovered state before generic greeting.",
};
},
{ priority: 20 },
);
// /new boundary: best-effort archive/checkpoint.
api.registerHook(
"command:new",
async () => {
const workspaceDir = api.runtime?.workspaceDir;
const statePath = resolveStatePath(api.runtime ?? {});
if (!workspaceDir || !statePath) return;
ensureStateFile(statePath);
const markdown = readStateFile(statePath);
if (!markdown) return;
appendArchive(workspaceDir, markdown);
},
{
name: "memory-continuity.command-new",
description: "Archive CURRENT_STATE.md before /new resets conversational continuity.",
},
);
// End-of-run checkpoint stub: proves the lifecycle hook wiring exists.
api.on("agent_end", async () => {
const statePath = resolveStatePath(api.runtime ?? {});
if (!statePath) return;
ensureStateFile(statePath);
});
// Compaction hook probe: v1 must confirm this path is actually synchronous enough.
api.on("before_compaction", async () => {
const statePath = resolveStatePath(api.runtime ?? {});
if (!statePath) return;
ensureStateFile(statePath);
const existing = readStateFile(statePath) ?? "";
const marker = `\n<!-- COMPACTION_PROBE ${new Date().toISOString()} -->\n`;
if (!existing.includes("<!-- COMPACTION_PROBE ")) {
fs.writeFileSync(statePath, existing.trimEnd() + marker, "utf8");
} else {
fs.writeFileSync(
statePath,
existing.replace(/<!-- COMPACTION_PROBE .*?-->/, marker.trim()),
"utf8",
);
}
console.log(`[memory-continuity] before_compaction probe wrote marker to ${statePath}`);
});
}
@@ -0,0 +1,126 @@
# Phase 2 Hook Validation Notes
## Goal
Validate whether a **standard lifecycle plugin** can support the first practical
memory-continuity MVP without consuming the exclusive `contextEngine` slot.
## Current evidence from local docs / SDK
### Confirmed hook/event names seen in local OpenClaw install
From local docs and SDK/runtime sources, the following names are present:
#### Typed plugin lifecycle hooks (`api.on`)
- `before_prompt_build`
- `before_agent_start`
- `agent_end`
- `before_compaction`
- `after_compaction`
- `subagent_ended`
#### Event hooks (`api.registerHook`)
- `command:new`
- `command:reset`
- `session:compact:before`
- `session:compact:after`
- `agent:bootstrap`
## Important finding: prompt injection is available to standard plugins
Local runtime code shows:
- `promptInjectionHookNameSet = new Set(["before_prompt_build", "before_agent_start"])`
This matters because it means a standard plugin may be able to inject startup
continuity hints **without** using the exclusive ContextEngine slot.
## Preferred v1 hook mapping
### 1. Startup recovery hint
**Primary candidate:** `before_prompt_build`
Why:
- explicitly documented as preferred over legacy `before_agent_start`
- supports prompt mutation fields such as:
- `prependContext`
- `prependSystemContext`
- `appendSystemContext`
- `systemPrompt`
- happens after session load, which is more practical for dynamic continuity state
**Fallback candidate:** `before_agent_start`
Why:
- present in runtime and treated as a prompt injection hook
- useful if `before_prompt_build` proves insufficient in some environments
### 2. `/new` boundary checkpoint
**Primary candidate:** `command:new`
Goal:
- save/archive working-state checkpoint before user-triggered reset of conversational continuity
### 3. End-of-run safety checkpoint
**Primary candidate:** `agent_end`
Goal:
- opportunistically preserve checkpoint state even if the agent was imperfectly disciplined mid-turn
### 4. Compaction protection
**Candidates to test:**
- `before_compaction`
- `session:compact:before`
This is the most important unresolved path.
## Biggest unresolved technical risk
The lifecycle-plugin route still has one major unsolved problem:
> How should the plugin expose a startup continuity hint robustly enough to
> satisfy “baseline recovery without read”, without relying on ContextEngine-only
> `systemPromptAddition`?
Local evidence suggests `before_prompt_build` may be enough, but this must be
validated by a real plugin test.
## Required Phase 2 experiments
### Experiment A — startup prompt injection
Build a minimal plugin that:
- reads `memory/CURRENT_STATE.md`
- derives a compact snapshot
- injects it through `before_prompt_build`
- verifies the snapshot is actually visible to the agent in a fresh run
### Experiment B — `/new` checkpoint timing
Build a minimal plugin that:
- handles `command:new`
- archives or checkpoints `CURRENT_STATE.md`
- verifies the write occurs before continuity is reset
### Experiment C — compaction boundary guarantee
Build a minimal plugin that:
- hooks `before_compaction` and/or `session:compact:before`
- writes a deterministic marker/checkpoint
- verifies compaction waits for hook completion
**This experiment is mandatory.**
If the hook does not reliably block until the write completes, the compaction
safety design must be revised.
### Experiment D — end-of-run safety path
Build a minimal plugin that:
- hooks `agent_end`
- performs a trivial state write
- confirms the event fires reliably enough to be useful as a safety net
## Prototype status
A local prototype skeleton exists at:
- `plugin/lifecycle-prototype.ts`
That file is only a Phase 2 probe. It is **not** the final implementation.
## Current conclusion
Based on local documentation and runtime inspection:
- a **standard lifecycle plugin** looks viable as the primary v1 architecture
- it likely supports startup prompt injection, `/new` handling, run-end checkpointing, and compaction-path interception
- the two most important items still requiring live proof are:
1. prompt injection quality in `before_prompt_build`
2. synchronous compaction safety in `before_compaction` / `session:compact:before`
@@ -0,0 +1,357 @@
# Memory Continuity Lifecycle Plugin Design
## Status
Design draft only. No plugin implementation yet.
## Current architectural choice
Memory continuity should **not** use the `ContextEngine` slot as its primary v1 architecture.
Reason:
- `contextEngine` is an **exclusive slot** in OpenClaw
- users should not be forced to choose between memory continuity and context engines such as `lossless-claw`
- context compression is a broad baseline need; working-state recovery is an additional capability
Therefore the main path is:
- **skill + ordinary lifecycle plugin** as the primary architecture
- **ContextEngine integration** kept as a future option, not the default implementation target
## Why a plugin version exists
The current `memory-continuity` skill is useful, but it depends too much on agent cooperation:
- the agent must notice recovery conditions
- the agent must keep `memory/CURRENT_STATE.md` updated
- the agent often benefits from `read`
A lifecycle plugin gives a runtime-aligned way to improve reliability without modifying OpenClaw core and without consuming the exclusive ContextEngine slot.
## Product strategy
Keep three forms with clear roles:
### A. Skill version
Role:
- zero-dependency fallback
- behavior contract
- template discipline
- compatibility with environments that do not install plugins
### B. Lifecycle plugin version (primary runtime path)
Role:
- runtime-assisted recovery
- automatic checkpointing at key lifecycle points
- better startup and `/new` continuity
- coexistence with `lossless-claw` and other context engines
### C. ContextEngine version (future option)
Role:
- more powerful prompt-time snapshot injection via `assemble` / `systemPromptAddition`
- only worth pursuing later if slot tradeoffs are acceptable or composite engine support exists
These forms should complement each other, not compete.
## Source of truth
Primary durable checkpoint file:
- `memory/CURRENT_STATE.md`
The plugin should treat this file as the editable, human-readable source of truth for working state.
The plugin may derive a lighter runtime snapshot from it, but should not replace it with an opaque database-first design.
## Non-goals
The plugin version should **not**:
- replace `MEMORY.md`
- replace daily notes in `memory/YYYY-MM-DD.md`
- replace native OpenClaw compaction summaries
- replace native `memoryFlush`
- replace session transcript memory search
- persist a full transcript mirror
- inject large recovery payloads into every turn
- attempt real-time bidirectional state synchronization in v1
## Core runtime idea
Use standard lifecycle hooks to ensure that short-term working state remains available across:
- `/new`
- reset/restart
- compaction
- session end / restart-like boundaries
- limited subagent handoff scenarios when supported by available hooks
The plugin should prefer deterministic, structured recovery over free-form recollection.
## Desired user-visible property
Even if an agent lacks `read`, the session should still recover a compact continuity hint whenever there is meaningful active work to recover.
## Checkpoint schema
The checkpoint file should retain a stable, minimal structure:
```md
# Current State
> Last updated: 2026-03-12T21:00:00+10:00
## Objective
...
## Current Step
...
## Key Decisions
- ...
## Next Action
...
## Blockers
None
## Unsurfaced Results
None
```
Potential future additions if truly needed:
- `Confidence`
- `Freshness`
- `Recent Finished`
But the default should stay compact.
## Runtime snapshot shape
The plugin should derive a much smaller summary than the raw checkpoint file.
Target content:
- Objective
- Current Step
- Last Confirmed Result or Key Decision(s)
- Next Action
- Blockers
- Unsurfaced Results
- Freshness / Updated At
### Draft example
```text
CONTINUITY SNAPSHOT
Objective: Verify memory continuity for Telegram subagents.
Current Step: Tools fixed; validating plugin-backed recovery design.
Key Decision: Use lifecycle plugin as primary path; ContextEngine stays optional.
Next Action: Finalize hook mapping and update the skill docs.
Blockers: None.
Unsurfaced Results: None.
Updated: 2026-03-12T22:00:00+10:00
```
### Size target
- preferred: ~150-300 tokens
- avoid large raw checkpoint injection on every turn
- skip injection entirely when there is no meaningful active state
### V1 rule for “meaningful active work”
Treat work as active when:
- `Objective` is non-empty
- and `Objective` is not placeholder text such as `None`, `idle`, `n/a`, or an empty template marker
This rule can be refined later, but v1 should use a simple deterministic threshold.
## Primary lifecycle hook mapping
### 1. Startup hook (`before_agent_start` / closest available startup hook)
Purpose:
- establish whether recovery state exists
- load a compact continuity summary for startup recovery behavior
- ensure recovery can happen even when the agent does not explicitly call `read`
Should do:
- check for `memory/CURRENT_STATE.md`
- perform lightweight validation
- derive a compact startup continuity hint when active work exists
- make recovery state available through the startup/lifecycle hook path supported by OpenClaw
Should avoid:
- rewriting the checkpoint unnecessarily
- injecting large raw file content
- treating placeholder/idle state as active recovery material
Notes:
- exact injection mechanism depends on the standard plugin hook surface available in OpenClaw
- this design intentionally does **not** assume access to ContextEngine-only `systemPromptAddition`
---
### 2. `/new` hook (`command:new` or equivalent)
Purpose:
- create a reliable checkpoint right before the user deliberately resets conversational continuity
Should do:
- save a final overwrite-style checkpoint before reset
- optionally archive the outgoing checkpoint if that remains part of the skill design
- ensure the next session can recover active work from a deterministic file state
Should avoid:
- expensive archival behavior for trivial idle sessions
- losing unsurfaced results at reset boundaries
---
### 3. Session end / run-end hook (`agent_end` or closest available end hook)
Purpose:
- checkpoint work at natural lifecycle boundaries
Should do:
- persist the latest working-state checkpoint when a meaningful state change occurred
- preserve unsurfaced results
- act as a safety net when the agent followed the protocol imperfectly during the turn
Should avoid:
- noisy writes on obviously trivial/no-op turns
- assuming this hook alone is enough for correctness
---
### 4. Compaction hooks (`session:compact:before` / equivalent)
Purpose:
- hard safety checkpoint before compaction removes detailed older context
Should do:
- force a final continuity checkpoint before compaction
- preserve current objective / step / blockers / unsurfaced results
- ensure recovery remains possible after compaction
Critical requirement:
- checkpoint writing in the compaction path must complete **synchronously** before compaction proceeds
- if the hook cannot provide that guarantee, this risk must be documented explicitly
This is the strongest required protection point.
---
### 5. Post-turn maintenance hook (when available)
Purpose:
- opportunistic checkpoint maintenance after substantive turns
Should do:
- update checkpoint when meaningful state changes are detected
- optionally use a configurable cadence (for example every N substantive turns)
- keep writes overwrite-oriented rather than append-heavy
V1 definition of **substantive turn**:
A turn counts as substantive when it includes at least one of:
- a tool result that materially changes work state
- a user confirmation of a decision or direction
- an agent statement that a concrete step was completed
- a newly discovered blocker or newly surfaced result
Non-substantive examples:
- greetings
- acknowledgements
- short clarifications without state change
- filler chatter
Should avoid:
- writing on every trivial turn
- producing noisy I/O for idle chat
- becoming the only write path
Practical stance:
- post-turn maintenance is useful, but not sufficient alone
- correctness should not rely entirely on semantic heuristics
## Subagent continuity stance for v1
V1 should stay conservative.
### Allowed in v1
- parent → child: minimal seed/handoff when a suitable hook/path exists
- child → parent: limited recovery of `Unsurfaced Results`
### Explicitly out of scope in v1
- continuous bidirectional synchronization
- real-time merge of parent and child working state
- multi-worker consensus state
This keeps the first implementation tractable and reduces process risk.
## Interaction with native OpenClaw systems
### With `memoryFlush`
Native `memoryFlush` helps the model store durable memory before compaction.
Memory continuity should not replace that.
Instead:
- `memoryFlush` handles durable notes / memory files
- continuity handles structured working-state checkpointing
### With native compaction continuity
OpenClaws compaction keeps summary information in session history.
Memory continuity should complement that by providing:
- a fixed schema
- a stable recovery surface
- explicit next-step / blocker / unsurfaced-result fields
### With session transcript memory search
Session memory search can help retrieve prior conversational material.
Memory continuity is different:
- memory search helps answer “what did we discuss?”
- continuity helps answer “what were we doing, and what should happen next?”
### With tools like `read`
`read` remains valuable for enhanced recovery and debugging.
But baseline continuity should not depend on `read` once the lifecycle plugin can expose recovery state through startup/runtime hooks.
### With ContextEngine plugins such as `lossless-claw`
This is the main architectural reason the lifecycle-plugin path is preferred.
Because `contextEngine` is an exclusive slot, making memory continuity a ContextEngine by default would force users to choose between:
- context compression / context assembly plugins
- working-state continuity
V1 should avoid creating that conflict.
## Open design questions
1. Which exact standard hook surface is best for startup recovery injection on current OpenClaw releases?
2. How should stale checkpoints be detected and labeled?
3. Should the plugin compute confidence/freshness automatically?
4. How should the plugin expose a startup continuity hint without relying on ContextEngine-only `systemPromptAddition`?
5. Should plugin writes go directly to `memory/CURRENT_STATE.md`, or stage then atomically replace?
6. How should compaction-hook guarantees be validated in practice?
7. What is the safest minimal parent/child handoff path under current OpenClaw hook support?
8. Under what future conditions would a ContextEngine variant become worth the slot tradeoff?
## Recommended implementation phases
### Phase 1 — Design + discipline hardening
- refine skill documentation
- stabilize checkpoint template
- clarify scope vs non-goals
- improve validation / doctor behavior
### Phase 2 — Minimal lifecycle plugin MVP
- register a standard plugin
- implement startup recovery hook behavior
- implement `/new` checkpoint behavior
- implement end-of-run checkpoint behavior
- implement compaction-path checkpoint behavior if the hook guarantees are sufficient
### Phase 3 — Reliability improvements
- add controlled post-turn checkpointing
- add freshness/confidence labeling
- improve stale-state handling
- tune snapshot length and injection behavior
### Phase 4 — Conservative subagent support
- add minimal parent → child seed behavior when safe
- add conservative child → parent unsurfaced-result recovery
- validate handoff behavior in real workflows
### Phase 5 — Future option evaluation
- reassess whether a ContextEngine variant is worth building
- only pursue if slot tradeoffs are acceptable or composite-engine support exists
## Success criteria
The plugin version is successful when:
- reset/new sessions recover active work without depending on `read`
- compaction no longer destroys actionable in-flight state
- the agent does not lose unsurfaced results at reset-like boundaries
- the runtime path coexists with `lossless-claw` and similar context engines
- startup recovery improves without excessive prompt bloat
- behavior aligns with OpenClaws official plugin and hook model
## Short summary
The primary long-term implementation should be a **standard lifecycle plugin** that improves continuity without consuming the exclusive ContextEngine slot, while the existing skill remains the **human-readable protocol and fallback behavior contract**. A ContextEngine variant remains a future option, not the default architecture.
@@ -0,0 +1,164 @@
# Memory Continuity Scope
## One-line definition
Memory continuity is a **structured working-state checkpoint** for recovering in-flight work after `/new`, reset, compaction, model fallback, gateway interruption, or subagent handoff.
## Core goal
Preserve just enough short-term state that an agent can answer:
- What are we trying to do?
- What step were we on?
- What was decided?
- What should happen next?
- What is blocked?
- What result exists but has not yet been surfaced?
This is a recovery layer for **active work**, not a general memory system.
## Source of truth
The canonical working-state record is a Markdown checkpoint file:
- `memory/CURRENT_STATE.md` in the current skill version
Longer-term direction:
- keep the checkpoint file as source of truth
- add runtime-assisted continuity delivery derived from that file
- keep the file readable/editable by humans and agents
## Responsibilities
Memory continuity **is responsible for**:
1. Maintaining a compact, overwrite-oriented checkpoint for current work
2. Recovering in-flight work across session breaks
3. Preserving active task state through compaction and subagent handoff
4. Providing a deterministic place to look for next-step recovery
5. Surfacing unsent / unsurfaced results that would otherwise be lost
6. Giving agents a standard structure for short-term state updates
## Non-goals
Memory continuity is **not responsible for**:
1. Long-term personal memory curation
2. Replacing `MEMORY.md` or daily notes
3. Replacing OpenClaw compaction summaries
4. Replacing OpenClaw `memoryFlush`
5. Replacing session transcript memory search
6. Acting as a project-management database
7. Acting as a full conversation transcript
8. Storing every detail of recent chat history
9. Guaranteeing perfect semantic recall of arbitrary facts from all prior turns
## Relationship to native OpenClaw systems
### Native OpenClaw handles
- bootstrap/system prompt assembly
- compaction lifecycle
- memory flush before compaction
- transcript persistence
- tools, sessions, and runtime orchestration
- context engine selection and plugin lifecycle
- session transcript recall via session-aware memory search
### Memory continuity adds
- a **structured checkpoint** for working state
- a predictable recovery format independent of transcript shape
- explicit fields for `Objective`, `Current Step`, `Next Action`, `Blockers`, and `Unsurfaced Results`
- stronger short-term recovery for in-flight work than generic compaction summaries alone
### Boundary with session memory search
Session memory search can help answer questions like:
- what did we discuss before?
- what decision was mentioned in a prior session?
Memory continuity is for a different question:
- what are we doing **right now**, where did we stop, and what should happen next?
In short:
- session memory search is good at **recalling prior conversation material**
- memory continuity is good at **recovering active working state**
## Product forms
### 1. Skill version (current / fallback version)
Purpose:
- zero-dependency compatibility layer
- human-readable protocol for agents
- works today without plugin installation
What it should do:
- define update discipline
- define recovery behavior
- define template shape
- define failure/uncertainty handling
What it cannot guarantee:
- recovery without agent cooperation
- recovery without correct tool/config support
- automatic runtime injection on every turn
### 2. Lifecycle plugin version (target architecture)
Purpose:
- runtime-assisted continuity guarantees without taking the exclusive ContextEngine slot
- reduced dependence on `read`
- better startup, `/new`, and compaction continuity
- coexistence with context engines such as `lossless-claw`
What it should do:
- use ordinary lifecycle hooks to checkpoint and recover working state
- improve startup recovery behavior
- checkpoint before destructive context transitions when hooks permit it
- support minimal parent/child continuity handoff without requiring full bidirectional sync
### 3. ContextEngine version (future option, not v1)
Purpose:
- more powerful prompt-time continuity injection when the ecosystem tradeoff is worth it
Why it is not the current primary path:
- `contextEngine` is an exclusive plugin slot
- users should not be forced to choose between memory continuity and widely useful context engines such as `lossless-claw`
## Design principles
1. **Files remain source of truth**
2. **Structured checkpoint beats free-form summary**
3. **Recovery state must stay short**
4. **Read access is an enhancement, not the only path**
5. **Continuity complements native OpenClaw memory; it does not replace it**
6. **Working-state recovery must prefer truth over confident guessing**
7. **User-visible recovery should prioritize current task state over generic greetings when continuity is clearly requested**
8. **Ecosystem compatibility matters: continuity should not unnecessarily block other high-value plugins**
## Minimal recovery fields
Any continuity implementation should preserve, at minimum:
- Objective
- Current Step
- Key Decisions / Key Facts
- Next Action
- Blockers
- Unsurfaced Results
- Updated At / Freshness
## Success criteria
A good continuity implementation should let an agent recover:
- the current objective
- the latest confirmed step
- the next concrete action
- the main blocker, if any
- one or more unsurfaced results
Even after:
- `/new`
- session reset
- compaction
- subagent handoff
- gateway interruption
## Failure criteria
The continuity layer is considered insufficient if, after a reset-like event, the agent:
- forgets the active objective
- loses a confirmed decision
- cannot identify the next action
- hides completed but unsurfaced results
- hallucinates prior work instead of expressing uncertainty
- when `CURRENT_STATE.md` exists and contains active work, opens with generic greeting/chit-chat instead of first surfacing the recovered state in a recovery scenario
## Current roadmap stance
- **Short term:** strengthen the existing skill + file discipline version
- **Medium term:** implement a standard lifecycle plugin version aligned with OpenClaws official hook model
- **Long term:** keep multiple compatible forms
- skill = fallback + behavior contract
- lifecycle plugin = primary runtime-assisted reliability layer
- context-engine variant = optional future path when slot tradeoffs are acceptable
+212 -119
View File
@@ -1,143 +1,236 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations """
continuity_doctor.py — Diagnostic tool for memory-continuity skill.
Checks workspace health and reports issues. Does NOT auto-repair.
Usage:
python3 continuity_doctor.py --workspace /path/to/workspace
python3 continuity_doctor.py --workspace ~/.openclaw/workspace/main
"""
import argparse import argparse
import os
import sys
import re
from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
REQUIRED_SECTIONS = [
'## In Flight',
'## Blocked / Waiting',
'## Recently Finished',
'## Next',
'## Reset Summary',
]
AGENTS_MARKERS = [ # ---------------------------------------------------------------------------
'CURRENT_STATE.md - Your Short-Term Workbench', # Severity levels
'Dual reporting protocol', # ---------------------------------------------------------------------------
'Execution agent → main', class Severity:
'main → Tao', OK = "OK"
] INFO = "INFO"
WARNING = "WARNING"
CRITICAL = "CRITICAL"
def result(level: str, msg: str) -> tuple[str, str]: # ---------------------------------------------------------------------------
print(f'{level:<5} {msg}') # Collector
return level, msg # ---------------------------------------------------------------------------
class DiagnosticReport:
def __init__(self):
self.entries: list[tuple[str, str]] = []
self._worst = Severity.OK
def add(self, severity: str, message: str):
self.entries.append((severity, message))
rank = {Severity.OK: 0, Severity.INFO: 1, Severity.WARNING: 2, Severity.CRITICAL: 3}
if rank.get(severity, 0) > rank.get(self._worst, 0):
self._worst = severity
def line_count(path: Path) -> int: def print_report(self):
try: for severity, message in self.entries:
return len(path.read_text().splitlines()) tag = f"[{severity}]".ljust(12)
except Exception: print(f"{tag}{message}")
print()
print(f"Overall status: {self._worst}")
@property
def exit_code(self) -> int:
if self._worst == Severity.CRITICAL:
return 2
if self._worst == Severity.WARNING:
return 1
return 0 return 0
def has_sections(path: Path) -> list[str]: # ---------------------------------------------------------------------------
text = path.read_text() # Required sections in CURRENT_STATE.md
missing = [s for s in REQUIRED_SECTIONS if s not in text] # ---------------------------------------------------------------------------
return missing REQUIRED_SECTIONS = [
"Objective",
"Current Step",
"Key Decisions",
"Next Action",
"Blockers",
"Unsurfaced Results",
]
PLACEHOLDER_PATTERNS = [
r"\[.*?\]", # anything in square brackets like [One sentence: ...]
]
def main() -> int: # ---------------------------------------------------------------------------
ap = argparse.ArgumentParser(description='Check continuity/CURRENT_STATE coverage and drift.') # Checks
ap.add_argument('--main-workspace', required=True) # ---------------------------------------------------------------------------
ap.add_argument('--agents-root', required=True)
args = ap.parse_args()
main_ws = Path(args.main_workspace) def check_existence(workspace: Path, report: DiagnosticReport) -> Path | None:
agents_root = Path(args.agents_root) """Check that memory/CURRENT_STATE.md exists."""
state_file = workspace / "memory" / "CURRENT_STATE.md"
if not state_file.exists():
report.add(Severity.CRITICAL, "memory/CURRENT_STATE.md does not exist")
return None
report.add(Severity.OK, "memory/CURRENT_STATE.md exists")
return state_file
failures = 0
warns = 0
print('continuity doctor\n') def check_staleness(state_file: Path, workspace: Path, report: DiagnosticReport):
"""Check if the state file is older than the most recent activity."""
mtime = datetime.fromtimestamp(state_file.stat().st_mtime, tz=timezone.utc)
age_seconds = (datetime.now(timezone.utc) - mtime).total_seconds()
age_hours = age_seconds / 3600
# main AGENTS.md if age_hours > 24:
agents_md = main_ws / 'AGENTS.md' report.add(
if not agents_md.exists(): Severity.WARNING,
failures += 1 f"CURRENT_STATE.md is stale (last modified {age_hours:.1f}h ago)",
result('FAIL', f'missing {agents_md}') )
elif age_hours > 4:
report.add(
Severity.INFO,
f"CURRENT_STATE.md last modified {age_hours:.1f}h ago",
)
else: else:
text = agents_md.read_text() report.add(Severity.OK, f"CURRENT_STATE.md is fresh ({age_hours:.1f}h old)")
missing = [m for m in AGENTS_MARKERS if m not in text]
if missing:
failures += 1
result('FAIL', f'AGENTS continuity markers missing: {", ".join(missing)}')
else:
result('PASS', 'AGENTS continuity rules present')
# main CURRENT_STATE
main_cs = main_ws / 'memory' / 'CURRENT_STATE.md' def check_template_compliance(state_file: Path, report: DiagnosticReport) -> dict:
if not main_cs.exists(): """Check all required sections are present and not placeholder-only."""
failures += 1 content = state_file.read_text(encoding="utf-8")
result('FAIL', 'main CURRENT_STATE missing') sections_found: dict[str, str] = {}
for section in REQUIRED_SECTIONS:
# Match ## Section or ## Section\n
pattern = rf"##\s+{re.escape(section)}\s*\n(.*?)(?=\n##\s|\Z)"
match = re.search(pattern, content, re.DOTALL)
if not match:
report.add(Severity.WARNING, f"Missing section: ## {section}")
continue
body = match.group(1).strip()
sections_found[section] = body
# Check for placeholder text
if body and all(re.fullmatch(p, body) for p in PLACEHOLDER_PATTERNS):
report.add(Severity.INFO, f"Section '{section}' still contains placeholder text")
if len(sections_found) == len(REQUIRED_SECTIONS):
report.add(Severity.OK, "Template compliance: all sections present")
return sections_found
def check_unsurfaced_results(sections: dict, report: DiagnosticReport):
"""Check if there are unsurfaced results that need attention."""
results = sections.get("Unsurfaced Results", "").strip().lower()
if results and results != "none":
report.add(
Severity.WARNING,
"Unsurfaced Results section is not empty — review needed",
)
else: else:
missing = has_sections(main_cs) report.add(Severity.OK, "No unsurfaced results pending")
if missing:
failures += 1
result('FAIL', f'main CURRENT_STATE missing sections: {", ".join(missing)}')
else:
result('PASS', 'main CURRENT_STATE sections present')
n = line_count(main_cs)
if n > 50:
warns += 1
result('WARN', f'main CURRENT_STATE too long: {n} lines (cap 50)')
else:
result('PASS', f'main CURRENT_STATE length ok: {n} lines')
# agent workspaces
checked = 0
missing_files = []
missing_sections = []
oversize = []
if agents_root.exists():
for ws in sorted([p for p in agents_root.iterdir() if p.is_dir()]):
checked += 1
cs = ws / 'memory' / 'CURRENT_STATE.md'
if not cs.exists():
missing_files.append(ws.name)
continue
missing = has_sections(cs)
if missing:
missing_sections.append((ws.name, missing))
n = line_count(cs)
if n > 30:
oversize.append((ws.name, n))
if missing_files:
failures += 1
result('FAIL', f'agent CURRENT_STATE missing: {", ".join(missing_files)}')
else:
result('PASS', f'agent CURRENT_STATE files present: {checked}/{checked}')
if missing_sections:
failures += 1
details = '; '.join(f'{name}: {", ".join(m)}' for name, m in missing_sections)
result('FAIL', f'agent CURRENT_STATE missing sections: {details}')
else:
result('PASS', 'agent CURRENT_STATE sections present')
if oversize:
warns += 1
details = '; '.join(f'{name}: {n} lines' for name, n in oversize)
result('WARN', f'agent CURRENT_STATE oversize: {details}')
else:
result('PASS', 'agent CURRENT_STATE length caps respected')
print('\nSummary')
print(f'- failures: {failures}')
print(f'- warnings: {warns}')
if failures:
print('\nSuggested actions:')
print('1. create/restore missing CURRENT_STATE files')
print('2. restore AGENTS continuity section if missing')
print('3. trim oversized CURRENT_STATE files')
print('4. rerun continuity doctor')
return 2
return 0
if __name__ == '__main__': def check_archive(workspace: Path, sections: dict, report: DiagnosticReport):
raise SystemExit(main()) """Check session archive consistency."""
archive_dir = workspace / "memory" / "session_archive"
if not archive_dir.exists() or not list(archive_dir.glob("*.md")):
report.add(Severity.INFO, "No session archives found (first session?)")
return
archives = sorted(archive_dir.glob("*.md"))
latest_archive = archives[-1]
report.add(Severity.OK, f"Found {len(archives)} session archive(s), latest: {latest_archive.name}")
# Compare objectives
current_objective = sections.get("Objective", "").strip()
archive_content = latest_archive.read_text(encoding="utf-8")
obj_match = re.search(r"##\s+Objective\s*\n(.*?)(?=\n##\s|\Z)", archive_content, re.DOTALL)
if obj_match:
archive_objective = obj_match.group(1).strip()
if archive_objective != current_objective and current_objective:
report.add(
Severity.INFO,
"Archive objective differs from current objective (task switch?)",
)
def check_tasks_alignment(workspace: Path, sections: dict, report: DiagnosticReport):
"""Optional: check if objective aligns with tasks.md if it exists."""
tasks_file = workspace / "tasks.md"
if not tasks_file.exists():
return
objective = sections.get("Objective", "").strip().lower()
if not objective:
return
tasks_content = tasks_file.read_text(encoding="utf-8").lower()
# Very rough heuristic: check if any significant word from objective appears in tasks
words = [w for w in objective.split() if len(w) > 4]
matches = sum(1 for w in words if w in tasks_content)
if words and matches == 0:
report.add(
Severity.INFO,
"Objective does not seem to match any content in tasks.md",
)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def run_doctor(workspace_path: str) -> int:
workspace = Path(workspace_path).expanduser().resolve()
report = DiagnosticReport()
print(f"Continuity Doctor — scanning: {workspace}")
print("=" * 60)
if not workspace.exists():
report.add(Severity.CRITICAL, f"Workspace does not exist: {workspace}")
report.print_report()
return report.exit_code
# Run all checks
state_file = check_existence(workspace, report)
if state_file:
check_staleness(state_file, workspace, report)
sections = check_template_compliance(state_file, report)
check_unsurfaced_results(sections, report)
check_archive(workspace, sections, report)
check_tasks_alignment(workspace, sections, report)
print()
report.print_report()
return report.exit_code
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Continuity Doctor — diagnose memory-continuity health"
)
parser.add_argument(
"--workspace",
required=True,
help="Path to the OpenClaw workspace to check",
)
args = parser.parse_args()
sys.exit(run_doctor(args.workspace))