Write Strategy Comparison

How others turn conversations into long-term memory, where our choices fit, and where to go next. Mechanisms are illustrated in Memory subsystem: mechanisms illustrated, and the authoritative text is in docs/reference/design/memory/overview.md. This page compares implementations only.

Layer 1 · our current implementation

Green rows

Behavior verified against running code in openprogram/memory/. Each table includes a green-background row.

Layer 2 · other implementations

Eight frameworks

references/ contains all eight. Missing features remain listed because not implementing a feature is itself a choice.

Layer 3 · our final design

Purple rows

Node markers (sections 04, 05, 09) and resident content generated from topics/core.md (section 07) are implemented. Authoritative descriptions are in written-marker.md and overview.md, “Resident content.”

Two notes before reading:First, references/claude-code-leaked and references/codex-cli are internal builds, much larger than their public counterparts; some capabilities below are absent from public versions. Second, conclusions refer to actual code in these two snapshots, with recorded line numbers; missing implementations are labeled “call site only.”
01 Is there long-term memory 02 When writes happen 03 What is written 04 What remains unwritten 05 Branches and restarts 06 Retrieval 07 Trimming full resident content 08 Implementation size 09 Where our design fits

01Is there long-term memoryLayer 2

“After closing this conversation, will the next one remember?” Four of eight do, four do not. The latter delegate this responsibility to user-written AGENTS.md or to another wrapped process.

Four store memory themselves; four delegate it The left side writes, stores, and retrieves its own memory; the right stores none and relies on alternatives. Has long-term memory No; another mechanism substitutes claude-code Files only: MEMORY.md indexes topic files containing details, with optional synchronization to a team-shared copy Files only codex-cli MEMORY.md, summary pages, and skill directories, plus a SQLite database scheduling two-stage distillation Disabled by default openclaw MEMORY.md is canonical; SQLite is only a rebuildable index: vector nearest neighbors combined with full-text ranking Files plus index hermes-agent Built-in MEMORY.md and USER.md Markdown files, plus eight optional hosted services, only one enabled at a time Files plus external service OpenProgram sources/ stores append-only originals; topics/ stores distilled facts; core.md is the small block included in every context Files only opencode User-written AGENTS.md or CLAUDE.md substitutes. The tool catalog contains no memory tool pi-mono Sessions are JSONL trees serving only replay and branching. Only AGENTS.md carries across sessions pi-ai Only an SDK for model providers, without even a session concept. The caller owns message arrays and supplies the full array each time weclaw A WeChat-to-agent integration stores only an opaque session id; the launched process owns all memory All four preserve complete originals codex, opencode, and pi-mono have complete transcripts, but nobody distills or reads them across sessions. Storing originals is not memory.
FrameworkPresentFormVerified location
claude-codeYesFiles only: MEMORY.md index, topic files, and dated logs; team edition synchronizes another copy to an HTTP servicesrc/memdir/paths.ts:223-235
src/memdir/memdir.ts:34,348
src/services/teamMemorySync/index.ts:1-80
codex-cliYes, but disabled by default~/.codex/memories/: MEMORY.md, summary pages, skill directory, and session summaries; SQLite only schedules distillationmemories/write/src/lib.rs:116-118
state/memory_migrations/0001_memories.sql:1-35
Default off: features/src/lib.rs:996-999
openclawYesMEMORY.md is canonical; SQLite is a rebuildable index combining vector nearest-neighbor and full-text rankingsrc/memory/root-memory-files.ts:4,8
extensions/memory-core/src/memory/manager-db.ts:10
.../manager-search.ts:148
opencodeNoUser-written AGENTS.md or CLAUDE.md; no memory tools in the catalogpackages/opencode/src/session/instruction.ts:60-68
Entire tool/ directory listed
hermes-agentYesMEMORY.md plus USER.md; eight optional hosted services, only one enabled at a timetools/memory_tool.py:56-60,138-173
agent/memory_manager.py:258-280
pi-aiNoProvider SDK, 12 files, no session conceptNo repository-wide grep matches; only OAuth credentials persist
pi-monoNoSession JSONL tree serves replay and branching; across sessions only AGENTS.mdpackages/coding-agent/src/core/session-manager.ts:428-436
weclawNoStores only opaque session ids; the wrapped process owns memoryagent/cli_agent.go:25,226-231
OpenProgramYesMarkdown workspace: sources/ plus topics/ plus core.mdopenprogram/memory/store.py:39
Two easily misread points:First, the internal codex build has fully implemented but default-disabled long-term memory. Looking only at default behavior wrongly suggests codex has no memory. Second, weclaw’s only persisted knowledge files (URLs pasted and saved as Markdown) deliberately bypass the agent and are never read back by a prompt. Classify by whether content returns to context, not whether it is Markdown.

02When writes happenLayer 2

The four writing frameworks trigger differently: one dispatches a background agent each turn; one starts a fire-and-forget task at turn start; one uses cron; one lets the model decide. We wait until enough content accumulates.

Same sequence of turns, five writing schedules The horizontal axis shows consecutive session turns. Bright outlines mark turns that trigger writes. claude-code Dispatch a background agent each turn The main agent already wrote memory this turn; skip Inherits main system + message prefix codex-cli Dispatch at each turn start Every turn dispatches, but work has strict conditions: insufficient quota exits; sessions idle under six hours are untouched. Tasks acquire a database lease; the winner works and records the watermark on completion. openclaw Scheduled by cron Daily at 03:00 No in-session writes. Short-term entries accumulate; the scheduled task ranks six weighted criteria and promotes qualifying entries. hermes-agent Model decides The model calls a memory tool itself Every ten turns, a background agent asks whether to save The background agent only reminds; the model decides whether and what to store. OpenProgram Write after enough accumulation This segment totals 16k tokens Session idle: write all remaining content Check per turn; write above threshold
FrameworkTimingTrigger
claude-codeBackground agent at turn end; skip if the main agent already wrote memorysrc/services/extractMemories/extractMemories.ts:348,598-603
Mutual-exclusion check: :121-148
codex-cliFire-and-forget task at turn start; handle only sessions idle over six hours, deduplicated by database leaseapp-server/src/request_processors/turn_processor.rs:578-591
memories/write/src/start.rs:23-81
memories/write/src/phase1.rs:149-187
openclawCron, default daily 03:00, promotes using six weighted criteriasrc/memory-host-sdk/dreaming.ts:17,36-39
extensions/memory-core/src/dreaming.ts:163-187
opencodeNo writesNo memory tools under tool/
hermes-agentModel may call tools anytime; a background agent reminds every ten turnstools/memory_tool.py:619-686
agent/conversation_loop.py:429-439
agent/background_review.py:34-43
pi-aiNo writes—
pi-monoNo long-term-memory writes; original events persist synchronouslypackages/coding-agent/src/core/agent-session.ts:515
weclawNo writes—
OpenProgramCheck each turn; write at 16k tokens, force remaining writes on idle, reorganize at 03:00openprogram/memory/provider.py:30,117
A shared conclusion:Three of four writers—claude-code, codex, and openclaw—write outside the conversation, for the same reason: the conversational model is working on the task, and memory-file edits add a call and distract from it. hermes alone makes the main model write, depending on whether it remembers to do so and requiring reminders every ten turns. We follow the other three.
A useful codex practice:Its writer task acquires a lease from a SQLite task table carrying retry counts and the last successful watermark, and checks remaining quota before work. We currently serialize using a cross-process write lock with a one-second timeout, achieving similar exclusion without retry counts; after two consecutive batch failures, only an event-bus record remains.

03What is writtenLayer 2

Original statements or model-distilled facts. The four retaining both—claude-code, codex, pi-mono, and ours—share one approach: originals are append-only, distilled content may be rewritten, and originals remain available for checking.

Three storage approaches Originals only opencode · weclaw user: The budget spreadsheet is due April 15 assistant: OK, I will create a Flask project first tool_use: write_file(app.py) Copy the conversation into storage only to resume it. Nobody distills it or reads it across sessions. In a new conversation, these words are effectively absent. Distilled facts only hermes-agent Craig is building a budget spreadsheet with Flask, due 2024-04-15 Originals do not enter memory. Tool instructions explicitly say “Do not store logs; search past sessions for those.” Small storage and fast reads are the benefit. If distillation is wrong, there is no source record to verify; it must be discarded and rewritten. Keep both claude-code · codex-cli · pi-mono · OpenProgram Originals Append-only, no edits sources/ Distilled facts Freely rewritable topics/ Each distilled statement has a footnote to its source Distilled facts serve the model; originals support verification. An implausible statement can be traced to what was actually said. Only our implementation adds footnotes; the other three stop short of this.
codex distills twice, not once.First, per-session processing compresses a full conversation into structured raw memories and a session summary. Second, another agent without network or approval prompts uses all first-stage artifacts and a workspace diff to rewrite MEMORY.md, summary pages, and skill directories. Writing and organizing are separate roles, like our writing and nightly reorganization, but separated by pipeline stage rather than time.

04How unwritten content is identifiedLayer 2

Eight implementations use five progress representations: none, scalar position, one message identity, in-data markers, and per-node identities. OpenProgram currently uses the last; no reference here shares that implementation.

Five cursor forms The same messages m1…m6: how each system records write progress. Orange indicates the cursor. A · no record hermes · weclaw m1 m2 m3 m4 m5 m6 hermes writes when the model chooses; there is no concept of pending content. weclaw directly hands off each completed turn and does not track afterward. B · one number codex · OpenProgram before migration m1 m2 m3 m4 m5 m6 Written through message 3 codex records timestamp watermarks; we recorded ordinal positions; both are comparable scalars. codex works at whole-session-file granularity and avoids forks; ours indexed positions within a branch and failed after branching, as below. C · one identity claude-code · pi-mono m1 m2 m3 m4 m5 m6 lastUuid = "m3" Scan from the array start; content after this id is new. claude-code keeps it only in-process, losing it on restart. If the id disappears, for example after compaction, treat everything as new, preferring repetition to permanently stopped extraction. D · markers inside data opencode · openclaw m1 m2 m3 m4 m5 m6 summary: true No separate cursor file; the boundary is stored within the data itself. openclaw uses two variants: promotedAt timestamps on entries, and HTML deduplication comments in the body, cross-checking one another. Cursor and data stay together, but changing the boundary requires changing the data. E · a complete identity set Current OpenProgram m1 m2 m3 m4 m5 m6 metadata.memory_written_scriptorium Markers reside on individual message nodes Inspect backward from the current branch’s newest node until a workspace marker is found; intervening unmarked nodes form the pending suffix. Why only we require a set A–D assume messages form a line: whether numeric, one id, or an in-data marker, “everything after this point is new.” With two branches in one session, “after this point” refers to two message sequences and becomes ambiguous. See section 05.
FrameworkFormStorageVerified location
claude-codeSingle message UUID, scan backwardIn-process closure variable, not persisted, lost on restartsrc/services/extractMemories/extractMemories.ts:305-309,340,348
Missing means all new: :103-108
codex-cliInteger timestamp watermark at session-file granularitySQLite task table with lease and retry countstate/memory_migrations/0001_memories.sql:3,7,17-31
memories/write/src/phase1.rs:149-187
openclawEntry-level promotedAt timestamp plus a body-embedded deduplication commentOn entries themselves plus MEMORY.md bodyextensions/memory-core/src/short-term-promotion.ts:84,1602-1612,1666-1669,1737
opencodeMessage-level summary: true boolean plus a tail_start_idOn messages themselvespackages/opencode/src/session/message-v2.ts:535-586
.../compaction.ts:386,437-442
hermes-agentNo memory cursor; persistence uses an integer index, compaction scans body markers—agent/agent_init.py:1026
agent/context_compressor.py:37-51,1215-1226
pi-aiNone——
pi-monoSingle entry id firstKeptEntryId, scan backwardIn compaction records, in the transcript filepackages/coding-agent/src/core/session-manager.ts:67-77,315-422
weclawNone; only a hard message-count cutoff—agent/http_agent.go:151-154
OpenProgram before migrationInteger ordinal positionruntime.jsonwritten-marker.md layer 1
Current OpenProgramWorkspace marker on each nodeSession node’s metadatawritten-marker.md layer 3
A practice to adopt:If claude-code cannot find the cursor’s message, it treats all messages as unwritten again, rather than returning zero. The comment states that zero would stop extraction for the rest of the session. Rewriting has bounded cost; silent stoppage has unbounded cost. Seeding cursors from source archives in our migration makes the same tradeoff: prefer a repeated batch to losing everything.
Another practice:openclaw deduplication records two mutually checking copies: promotion time on entries, plus MEMORY.md body HTML comments with keys. If either copy disappears, the other still prevents duplicates. Our set is recorded only in the cursor file; source archives can seed it, but the two do not cross-check.

05Branches and restartsLayer 2

This matters most for us. Of eight references, only pi-mono, like us, places branches inside the same session. Others either never branch or create another session file linked to its parent. Their cursors need not handle branches sharing a prefix inside one session because that structure does not exist.

The user returns to message three and asks again: three approaches No branching weclaw · pi-ai m1 m2 m3 m4 m5 Restarting requires clearing the session and creating another. weclaw permits one session per WeChat contact and discards the old one. New session with a parent pointer claude-code · codex · hermes opencode · openclaw Session A remains unchanged Session B records A as its parent Each session remains linear, so the cursor still works. Branch within the same session pi-mono · OpenProgram Shared prefix New branch One session id; two sequences share three messages. In an in-session DAG, “after this message” is ambiguous Main branch (written) m1 m2 m3 m4 m5 m6 New branch (pending) b4 b5 b6 Ordinal cursor: “written through 6” → b4 b5 b6 have indices 4 5 6 and are all skipped Single-id cursor: “written through m6” → m6 never appears on this branch Set: “m1…m6 are written” → b4 b5 b6: missing, pending pi-mono also uses an in-session tree but avoids this problem because its id marks compaction start, not eligibility for long-term-memory writing.
FrameworkBranching supportedImplementationVerified location
claude-codeYes, most complete/branch copies the whole session into a new one; /rewind can rewind conversation, files, or both; subagents write separate filessrc/commands/branch/branch.ts:61-173
src/components/MessageSelector.tsx:31
src/utils/sessionStorage.ts:247-258
codex-cliYescodex fork and TUI edits of earlier prompts create new threads, leaving the source unchangedthread-store/src/types.rs:183-190
tui/src/app_backtrack.rs:7-8
rollout/src/recorder.rs:97-98
openclawYes, subagents onlySubagent dispatch can inherit the parent transcript or start clean; no old-message edit/rerun implementation foundsrc/agents/tools/sessions-spawn-tool.ts:190,307,343-344
Message editing: no grep matches
opencodeCan copy; rewind deletesfork deep-copies an independent session; revert only sets a marker; at the next prompt, messages after it are actually deletedpackages/opencode/src/session/session.ts:733-774
.../revert.ts:100-134
.../prompt.ts:1155-1159
hermes-agentYes/branch copies all history into a new session linked by parent id; compaction also creates child sessions, and /resume automatically follows the chain to the latest onecli.py:6682-6809
agent/conversation_compression.py:381-397
hermes_state.py:1851-1869
pi-aiNo sessions——
pi-monoYes, sessions are treesEditing an old message creates a sibling branch; leaving a branch can preserve a generated summarypackages/agent/src/harness/agent-harness.ts:788-812
.../branch-summarization.ts:200-262
weclawNoOne per WeChat contact; /new deletes the old sessionmessaging/handler.go:340-345
OpenProgramYes, sessions are DAGsReasking, retrying, and spawning agents branch from intermediate points; multiple branches under one session id share prefixesopenprogram/store/session/session_store.py:988

The above explains session structure. What determines missing writes is the next question: what happens to the cursor after a fork. Eight implementations use three approaches; none simply ignores the issue.

After a fork: three cursor approaches An index cursor assumes one line within its scope. Forking threatens that assumption; each approach preserves it differently. Approach 1 · new cursor scope, re-extract shared prefix codex · claude-code · openclaw codex: forks create a new thread_id, which is the memory-cursor table primary key. The new thread has no row, so extraction restarts from the beginning. openclaw: forks create a new session and flush markers are deliberately cleared; comments prefer re-extraction in the new session to omission from hash collisions. claude-code: the cursor is an in-process closure variable, reused in the same process but empty in a new one; empty means everything is new. Cost: the shared prefix is extracted again. codex’s Phase 2 consolidation agent merges semantically, without mechanical deduplication. These systems accept that cost. Approach 2 · carry the boundary and translate coordinates opencode · hermes opencode: a fork gives every message a new id; the compaction boundary tail_start_id follows a lookup table to the new id, avoiding prefix recompaction. hermes: /branchCopy all history into a new session, then manually advance the index to the copied length at the fork. This works because the fork copies content, with a new coordinate system starting from the beginning, allowing translation or one-time advancement. Approach 3 · exclude the prefix from traversal pi-mono Like us, it has an in-session tree, but asks for branch changes relative to the nearest common ancestor: walk from leaf backward and stop at that ancestor. Traversal excludes the shared prefix, not a cursor. Therefore the tree needs no cursor for this purpose. Before migration · none of the three Forks then neither changed session id nor copied nodes; get_branch walked from head to the beginning while progress remained a positional index. Node-marker migration replaced exactly this implementation.
FrameworkCursor scope after a forkShared-prefix re-extractionVerified location
codex-cliNew scope: fork produces a new thread_id, the cursor table’s primary keyYes, entire prefix. Only Phase 2 consolidation merges semanticallythread-store/src/types.rs:78-79,97
state/memory_migrations/0001_memories.sql:1-12
state/src/runtime/memories.rs:104-108
memories/write/src/phase1.rs:290
claude-codeOne per process: closure variable, not persistedNo within the same process; yes deliberately in a new processsrc/services/extractMemories/extractMemories.ts:305-307,86-88
src/utils/backgroundHousekeeping.ts:34-36
src/commands/branch/branch.ts:68,117-146
openclawNew scope: fork creates a session and intentionally clears flush markersYes. Comments prefer repetition to omission from equal hashessrc/auto-reply/reply/session.ts:756-767,793-799
src/config/sessions/types.ts:370-371
src/auto-reply/reply/memory-flush.ts:117-123
opencodeNew scope with translated boundary: fork reissues every message id, and tail_start_id maps to the new idNopackages/opencode/src/session/session.ts:748-758,767-769
.../compaction.ts:244-248
hermes-agentNew session; manually advance index to copied lengthNo. Compaction-created sessions instead reset to 0 because they truly start emptycli.py:6701-6705,6744-6756,6776-6777
agent/agent_init.py:1026
agent/conversation_compression.py:379-380,407-408
pi-monoUnnecessary: take only leaf-to-nearest-common-ancestor changesNo; traversal excludes itpackages/agent/src/harness/compaction/branch-summarization.ts:87-97
packages/agent/src/harness/agent-harness.ts:737-834
pi-ai · weclawNo session forksNot applicableweclaw agent/http_agent.go:76-78 (/new clears the session)
OpenProgram before migrationUnchanged: same session id and positional cursorNo repetition, but a fork can misclassify unwritten nodes as writtenwritten-marker.md layer 1
Current OpenProgramUnchanged scope; inspect workspace markers backward from current branch tipNo; shared-prefix nodes already carry markers, and inspection stops therewritten-marker.md layer 3
The conclusion for this comparison:Index and timestamp cursors depend on one line per cursor scope. They preserve that assumption by changing scope on a fork: codex changes thread_id, openclaw changes session, claude-code changes process, and hermes manually advances the index at the fork. Scope changes work because their forks all copies content, giving the new side fresh coordinates. Our forks do not copy: both branches share a graph and session id, so neither scope switching nor coordinate translation applies. We therefore record identities rather than ordinal positions. pi-mono demonstrates another solution: its in-session tree excludes prefixes by stopping at the nearest common ancestor. That requires a comparison branch for every traversal, whereas memory asks what remains unwritten in the session, with no second branch to compare.
A useful pi-mono practice:When leaving a branch, it can generate a summary and attach it to the tree, preserving statements after abandonment. This addresses the gap acknowledged in our overview.md: only the head branch is written, and nobody revisits branches abandoned midway.
An unexpected opencode behavior:revert itself deletes nothing, but the next prompt starts by permanently deleting messages after the marker. Editing an old message and rerunning is therefore irreversible unless the old branch is forked first. Our DAG retains both, requiring a way to identify which content has been written.

06RetrievalLayer 2

How stored content returns to context: inject everything, keyword search, vector search, or model selection. The fourth is unique to claude-code and is the least expensive approach.

Four retrieval paths Inject everything opencode · pi-mono The entire AGENTS.md enters the system prompt, without chunking or filtering. For finer detail, the model uses file-reading and grep tools itself. Zero infrastructure. Large files exhaust the budget. Keyword search hermes · codex · OpenProgram We rank with BM25 and take five results every turn, without a model call. hermes: SQLite full-text session search. codex uses simpler line-by-line substring matching. Fast, inexpensive, explainable results. Paraphrases may not match. Vector search openclaw Nearest-neighbor search in SQLite, combined with full-text ranking. Paraphrases can still match. A dedicated subagent also searches before the user turn and inserts conclusions into the main context. Requires embeddings, indexes, and rebuilding—over half of more than twenty thousand lines. Let the model select claude-code Scan every memory file header, list filenames plus one-line descriptions, and ask a small model to choose at most five. The prompt includes a counterintuitive rule: omit usage docs for tools already being used, but select their pitfalls and known issues. No index; one inexpensive extra call.
The most useful practice to adopt.claude-code builds no index, instead requiring each memory file to start with a self-description. Retrieval lists these descriptions and uses an inexpensive small-model call to select five filenames. We currently lack this path, which naturally matches our topics/ structure. Selection is more precise than “choose if relevant”: omit uncertain matches and prefer empty results.
A useful codex feedback loop:The model embeds a hidden marker identifying memory used in its response. The server parses it, increments usage counts, then removes it before showing the response. Counts feed organization-stage ranking: frequently used content stays prominent while unused content is ranked lower, without manual scoring. Our nightly reorganization currently merges by content only, without this feedback.

07Resident content: trimming when fullLayer 2

Four of eight implementations limit the small memory block included every turn, with six gates in total. There are only two trimming approaches, and none delegates removal decisions entirely to an unconstrained model: deterministic truncation, or deterministic rejection followed by model edits.

Six gates, seven approaches including ours Left: limit size. Middle: what happens on overflow. Right: whether the action is disclosed. Limit Overflow behavior Disclosure claude-code MEMORY.md 200 lines 25000 bytes Two sequential gates: first 200 lines, then, if bytes still exceed the limit,backtrack to the last newlineand cut there, never mid-line. The byte gate was added after a production index stayed under 200 lines but reached 197KB. Yes. Append a warning naming the triggered gate and how to fix it claude-code SessionMemory 2000 tokens per section 12000 tokens total First level: model compression The prompt names two sections to preserve Second level: deterministic line truncation Fallback when model compression is insufficient Yes. Truncated sections retain “This section was truncated due to length” codex-cli AGENTS.md and summary pages 32 KiB combined budget Summary pages: 2500 tokens Multiple project AGENTS.md files share one remaining-byte budget and are cut wherever it ends. The global ~/.codex/AGENTS.md has no gate and is injected in full regardless of size. Mostly no. Only a log entry, invisible to both user and model openclaw MEMORY.md 10000 characters Below 12000-char gate Trim by ownership: remove only automatically promoted paragraphs, oldest dates first. Never alter handwritten content in the file. Yes. Return the dates removed by this operation openclaw Startup injection layer 12000 characters per file 60000 chars total Ordinary files retain 75% head and 25% tail. AGENTS.md is special:45% head, 35% regex-selected key lines, 15% tail. Regex recognizes headings, list lines, and words such as must, never, security, and commit; not model judgment. Yes. Add a separate truncation warning to the system prompt, by default every time hermes-agent MEMORY.md / USER.md 2200 / 1375 characters Characters, not tokens Reject rather than trim. Return the write unchanged; persist nothing. The model decides what to remove afterward but learns it is full only after rejection. Yes. Rejection returns current usage and the limit to the model OpenProgram core.md 2000 token Actual tiktoken count Reject the whole transaction. Reject the entire batch, not just this entry; the workspace remains byte-for-byte unchanged. Give the writer a repair instruction: core.md is full; leave it alone and put this in a topic file. Yes. Give repair guidance directly to the writer, with one retry opportunity
FrameworkResident blockLimitWhen fullDecision makerDisclosureVerified location
claude-code MEMORY.mdYes200 lines + 25000 bytesTruncate by lines, then by bytes at the last newlineDeterministicAppend a warning naming the gatesrc/memdir/memdir.ts:35,38,57-103
claude-code SessionMemoryYes2000 tokens per section / 12000 totalModel compression first, deterministic line truncation if insufficientModel, then deterministicRetain [... section truncated for length ...]src/services/SessionMemory/prompts.ts:8-9,67,185,256-324
claude-code CLAUDE.mdYes40000 characters warns only, without truncationDo nothing beyond “file too large” in the status panel—Status-panel noticesrc/utils/claudemd.ts:92,1132-1134
codex-cliYesProject 32 KiB; summary 2500 tokens; global unlimitedCut at the remaining-byte boundaryDeterministicLog entry onlycore/src/config/mod.rs:211
core/src/agents_md.rs:94,116-124
ext/memories/src/lib.rs:16
openclaw MEMORY.mdYes10000 charactersRemove only automatically promoted paragraphs, oldest first; preserve handwritten contentDeterministic, ownership-basedReturn removed datesextensions/memory-core/src/memory-budget.ts:25,100-164
short-term-promotion.ts:1719,1762
openclaw startup injection layerYes12000 per file / 60000 total charactersOrdinary files: 75% head, 25% tail; AGENTS.md: 45% head, 35% regex-selected key lines, 15% tailDeterministic regex selectionAdd a system-prompt truncation warning, by default every timesrc/agents/pi-embedded-helpers/bootstrap.ts:88-89,96-102,158-165
opencodeNo memory blockAGENTS.md has no limit; confirmed rather than not found———packages/opencode/src/session/instruction.ts:91-93,155-169
hermes-agentYes2200 / 1375 charactersHard-reject the write; let the model replace or remove contentDeterministic rejection + model removal selectionReturn usage and limit to the modeltools/memory_tool.py:149,295-306,359-366
pi-aiNo—————
pi-monoHas AGENTS.mdUnlimited, concatenate in full———packages/coding-agent/src/core/system-prompt.ts:61-68
weclawNoFixed configured system prompt, without learned content———config/config.go:28
OpenProgram topics/core.md → core.mdYes2000-token rendering budgetRuntime deterministically renders a budget-sized resident block; canonical Topic content is not deletedDeterministic renderingTopics excluded from the resident block remain retrievableopenprogram/memory/management/config.py
.../block_views.py

The table explains immediate overflow but leaves another question, which is precisely our gap: can new stable facts enter after the block is full? This depends on maintenance and whether the resident block is itself canonical.

Who maintains the resident block, and can new facts still enter Canonical storage determines whether trimming means omitted from this view or deleted. A maintenance path determines whether new facts can enter after saturation. Canonical location Maintenance path New facts after saturation claude-code MEMORY.md Topic files: index, not memory Prompt: no memory content in MEMORY.md Yes. Dream agent: after 24 hours and ≥5 sessions Four parts; final pruning/indexing: below 200 lines Yes. No gate exists on writes codex-cli memory_summary.md SQLite tables, then rendered into a raw-memory file The resident block is the final derived step Yes. Session-start full rewrite; six-hour cooldown Database pruning also deletes raw entries unused for 30 days Yes. Rewritten each round openclaw MEMORY.md Canonical file: manual + automatic content Hence ownership-based trimming is required Yes. Daily 03:00 cron promotes after the dream pipeline Before promotion: trim oldest automatic text to budget Yes. All old content removable; writes continue Comment: reject writes, lose latest material hermes-agent MEMORY.md / USER.md The file itself is canonical, with no other copy No. The only one of eight without maintenance No matches: consolidate / distill / prune / compact / dream Yes, but the model must first make room Rejection returns to the main conversational model opencode · pi-mono Handwritten AGENTS.md, injected in full Not applicable: no automatic writes, hence no maintenance problem Not applicable Current OpenProgram topics/core.md → core.md Topic files are canonical core.md is a Runtime-derived view rendered within budget Yes. Deterministic rebuild after every installed transaction Nightly organization edits Topics only, then rebuilds derived views Topics can still receive writes Over-budget content stays retrievable
FrameworkCanonical sourceMaintenance path and triggerAfter saturationVerified location
claude-codeIndex of the topic-file directoryDream agent: 24 hours and at least 5 sessionsNo write-path gate; truncation happens only at injection, leaving disk originals unchangedsrc/memdir/memdir.ts:227,322-325,363
src/services/autoDream/autoDream.ts:63-66,140-141,166-171
src/services/autoDream/consolidationPrompt.ts:53-61
codex-cliSQLite tables → raw-memory file → resident blockConsolidation agent at session start with six-hour post-success cooldown; prune database entries unused for 30 daysRewrite in full; injection applies a 2500-token fallback cutoffstate/src/runtime/memories.rs:21
memories/write/src/phase2.rs:139-166
memories/write/src/phase1.rs:110-133
ext/memories/src/lib.rs:16
openclawThe file itselfDaily 03:00 cron promotes after the dream pipelineTrim oldest automatic paragraphs before promotion; write new content even if all old paragraphs are removedsrc/memory-host-sdk/dreaming.ts:17
extensions/memory-core/src/memory-budget.ts:25,100-164
.../short-term-promotion.ts:1706-1727
hermes-agentThe file itselfNoHard-reject this write and return usage/limit to the main conversational model, which frees space with replace or removetools/memory_tool.py:149,295-306
No matches for consolidate / distill / prune / compact / dream
opencode · pi-monoHandwritten fileNot applicableNo limitpackages/opencode/src/session/instruction.ts:60-93
packages/coding-agent/src/core/resource-loader.ts:57-100
OpenProgram before migrationRoot directorycore.mdNone; organize_topics handles only topics/**.mdReject over-budget write transactionsadoption.html section 02
Current OpenProgramtopics/core.mdDeterministically rebuild alongside other derived views after every installed transactionRender to budget; Topic content outside the resident block stays retrievableoverview.md, “Resident content”
Adoption record.claude-code, codex, and openclaw separate editable canonical content from injection budgets. OpenProgram follows that principle, using topics/core.md as canonical source and core.md as a deterministic derived view. Unlike them, OpenProgram does not delete over-budget Topic content; retrieval can still return those blocks. Before migration, root core.md directly held canonical content and rejected over-budget transactions; that implementation has been replaced.
Conclusion on trimming full resident content:None of seven gates leaves removals to unconstrained model judgment. Five use deterministic truncation: claude-code MEMORY.md, codex, both openclaw layers, and claude-code SessionMemory’s fallback. Two reject deterministically before returning choices to the model: hermes and ours. Only claude-code SessionMemory involves model judgment, using model compression followed by deterministic fallback, with a prompt explicitly naming the two sections to preserve rather than allowing unconstrained edits. Four applicable lessons:First, Use two limits: line counts cannot constrain long lines; claude-code’s byte gate followed a 200-line, 197KB index. Second, ownership matters more than time when trimming; openclaw removes only its own writes and preserves handwritten content. Third, apply limits to all relevant content: codex limits project AGENTS.md but omits global AGENTS.md; claude-code hard-limits its own MEMORY.md but only warns for user-maintained CLAUDE.md. Fourth, disclose truncation. codex only logs it, leaving users and models unaware of omitted content. Our core.md currently falls under deterministic rejection with disclosure. We will not copy the first two lessons directly: another gate on the write path still excludes stable facts; ownership matters for openclaw because its resident file is the sole copy and deleting a line destroys it. Once core.md is derived, omission is non-destructive and ownership need not be checked. The decision and rationale are in What to adopt after comparison section 02.

08Implementation sizeLayer 2

Excludes tests. Colored bars represent long-term memory itself; gray bars represent compaction and transcript storage. Implementations range from 0 to 34,000 lines, almost entirely depending on whether they maintain an index.

Lines of memory-related code Bars use one scale. Gray bars are not long-term memory: transcript persistence and context compaction are needed regardless of remembering. openclaw 34000 Vectors, full-text, dream pipeline, promotion ranking. Two alternative backends add 12000 lines, excluded codex-cli 8900 + 3700 Two-stage distillation and read path About 60000 additional transcript-storage/forking lines are not memory OpenProgram 6380 Writing, transactions, BM25, topic format, nightly reorganization. All long-term memory; none is compaction claude-code 4900 + 4300 Memory directory and extraction agent Another 1500 lines for the CLAUDE.md loader and 2200 for team synchronization hermes-agent 2800 + 2400 Eight optional service plugins total 11600 lines, all default off pi-mono 5000 Entirely session tree and compaction; 0 long-term-memory lines; the same logic is implemented twice opencode 1550 Compaction and storage; 0 long-term-memory lines weclaw 200, all session-id bookkeeping pi-ai 0, not a single line
Retrieval accounts for the complexity.Vectors, embedding caches, index rebuilding, and hybrid ranking dominate openclaw’s 34000 lines. Removing that layer leaves roughly the same scale as claude-code’s 4900 lines. Our 6300 lines do not require vectors—embeddings are optional—and additional code implements transactions and topic-format validation. The dividing factor is maintaining an index, not merely having memory.

09Where our design fitsLayer 3

The preceding eight sections position the current design across dimensions. Most reuse established practices; node markers follow SessionDAG branch semantics, explained in section 05.

Our peers across seven dimensions Storage form Files only, like claude-code openclaw: index; hermes: external services. Files: readable, Git-compatible Who writes An out-of-conversation agent, like claude-code, codex, and openclaw Three of four do this. hermes relies on the main model remembering to write What is written Keep originals and distilled facts, like claude-code, codex, and pi-mono Only we link every distilled statement back to originals with a footnote Retrieval Keyword BM25, the same category as hermes and codex Missing claude-code’s small-model file-selection path; see section 06 Resident-block gate Deterministic rejection with disclosure, like hermes topics/core.md → derived core.md, 2000-token budget; section 07 Nightly organization Present, like claude-code, codex, and openclaw All four recognize that writing without organizing makes files grow indefinitely Session structure In-session DAG, shared only with pi-mono The other six are linear or represent forks as separate session files Cursor Set of written message ids; no other implementation does this Because of the preceding dimension: different session structure makes their cursor forms incorrect for ours. This is not greater foresight: others have linear sessions where “everything after this point is new” is unambiguous, while ours does not. Similarly, pi-mono has an in-session tree without needing a set because its id marks compaction start, not long-term-memory eligibility. The form follows session structure rather than preference.
The design itself is documented elsewhere.Where markers live, when they are set, how forks traverse, how missing markers recover, and positional-cursor migration are authoritatively documented in written-marker.md, with mechanism diagrams in architecture.html section 04. This page only explains how that form relates to other implementations. The background agent inherits the main session’s system prompt and message prefix. topics/core.md is canonical; core.md is generated deterministically within the 2000-token budget described in section 07. The session-start rewrite is performed by a consolidation agent. Its six-hour cooldown remains unchanged.
Current comparison-decision status. Implemented: topics/core.md as resident canonical source, per-node workspace markers, live-branch enumeration at session boundaries, and Topic footnotes preserving Source references. Not implemented: index-free retrieval using Topic self-descriptions and small-model file selection (claude-code, section 06); retrieval usage as nightly-organization input (codex, section 06); direct writer notification on head changes instead of post-turn thresholds or idle watcher.
Implementation record (2026-08-11): Four of eight have long-term memory; message-branching session structures appear only in pi-mono, openclaw, and OpenProgram. OpenProgram has replaced positional cursors with node workspace markers and uses topics/core.md as maintainable canonical resident content. Pre-migration rows remain as problem evidence; purple rows now indicate current implementation, not future plans.