No topics yet
Durable memory created by conversations and the background writer will appear here. Core rules are managed separately.
Open Memory settingsHow a conversation becomes topic files on disk, what happens if a write fails midway, how forks track previously written messages, and who maintains the small memory block injected into every session. The main text is in docs/reference/design/memory/overview.md; the complete design for section 04 is in written-marker.md. This page illustrates mechanisms only. Comparisons with other implementations and the rationale for our choices are in Memory writing: comparison of eight frameworks.
Two write entry points, five write steps, staged transactions, nightly organization, the silence-means-success return-value contract, and Section 05 and its resident-block derived view: core.md is rendered from topics/core.md; exceeding the budget no longer rejects transactions.
Section 04’s write cursor: the running implementation still uses positions, which can omit an entire fork or write only a fragment starting mid-conversation. The planned approach marks nodes as written and walks backward from head to the first marked node.
name is_available initialize shutdown
extract_before_discard: five methods have no runtime callers.
Two entry points share one write implementation. They differ only in the force switch: one waits for enough content; the other writes everything remaining.
Diagram details: Session storage. No messages passed; read session storage. Scan every five minutes; idle after 30 minutes. Write everything remaining regardless of batch threshold. Reorganize existing disk content; add nothing new. While content remains, run successive passes. After a session ends there is no next batch; stopping halfway leaves content unwritten forever. Select unwritten messages not covered by the cursor. Batch by accumulating 16k tokens. Call the model to edit staged files. Organize when enough content accumulates. Commit: install everything after validation. Topic files; the model edits only these. timeline/ and two other locations.
What happens in one pass. The threshold determines whether to call a model, not how much a single call writes; a day-long conversation may need several passes.
Diagram details: minus messages covered by the cursor. Leave the remainder for the next pass; After one day since global organization. Validation passes → install everything. Advance the cursor only after installation.
Diagram details: Accumulate messages from the beginning. The writer receives this batch’s source text.
Topic normalization rejects substantive uncited prose with INVALID_TOPIC_FORMAT before pruning empty sections or topics. Changes, patches, editor/CLI staged edits and writer shell commits share this validation. Rejection preserves committed topic/source bytes, Runtime state, revision and Git history. Cited native/legacy records and genuinely empty topics retain their existing rules.
An instance-owned trusted pre-install callback records autosave or restore history only after validation and stage preparation, before committed files are moved. Invalid edits never create a manual checkpoint; callback failure never installs. A valid edit still records the exact pre-edit snapshot and the existing checkpoint deadline. Tools and request text cannot supply this callable.
The model never edits the repository directly. It edits a temporary copy, installed only after complete validation. Failed validation discards the copy without changing a byte in the repository.
Diagram details: Read-only throughout the transaction; Paragraphs may be rewritten, merged, or moved.
Diagram details: Staging directory. Created under the system temporary directory. Writable; every writer edit this turn goes here. Filesystem-level denial prevents writes immediately. Read the baseline from the repository before copying. Reading it after copying. would compare an edit with itself and treat lost block IDs as though they never existed. core.md stays within budget; no dangling block links. Move existing data to backup. Install the complete staged copy. Rebuild derived views and commit. Recreate staging from a fresh copy. Leave the repository unchanged. Return the rejected text and a specific. instruction for correcting this rule to the writer. The workspace has already been restored to its pre-turn state. Take a fresh baseline before repair; taking it afterward would. parse text the model has just corrupted. Return COMMIT_REJECTED, leave the cursor unchanged, and keep these turns pending. Reporting success here would let the caller advance past content never installed on disk.
A session is a graph, not a linear sequence. Re-asking, retrying a reply, or spawning a subagent creates a branch. Every branch’s messages should enter memory, but shared prefixes should not be written twice.
Diagram details: Cursor stores ordinal=9; accept only larger ordinals. Walk backward from head, collect unmarked nodes, stop at the first marker. Traverse the whole branch and subtract IDs in the set. Value identifies the memory workspace; exported sessions. retain markers without being mistaken for another workspace. Observed: stored position 9 treats a five-turn branch as empty,. and an eleven-turn branch as its last two turns.
get_branch filters turns marked rewind , and compaction inserts summary nodes, so even one path changes between reads.advance_cursor raises
cursor cannot move backwards when a new ordinal is below the stored one, although moving backward is exactly what branching requires. A monotonic counter cannot be corrected into a representation of branching conversations.Diagram details: Use node markers. External bookkeeping that remains correct after forks requires a set, introducing file placement, migration, permanent ID retention, and read cost that grows with the conversation.
<state>/memory, separate from <state>/sessions , so node markers are not mistaken for concurrent memory writes. The session-index cache is affected: history-directory mtime changes on rewrites just as on appends. Another process rebuilds the index; measured cost for 289 nodes and 4.2 MB is 14–50 ms and is self-limiting. Git is what grows: the session directory runs git add -A per turn, and changed nodes become new blobs. Therefore, mark only the current batch; never rescan and rewrite every node, or each turn adds the entire session and total growth becomes quadratic. Two additional constraints: markers must not change updated_at(the idle watcher uses it to decide whether a session was handled; changing it would trigger another forced write), and must not use the “mark session synchronized” path.
core.md is the small block injected at each session’s start. It is now rendered from
topics/core.md; 2000 tokens is a rendering budget, not a transaction gate. Previously it was the writable source of truth: once full, it stopped changing and rejected every later transaction.
Diagram details: management/block_views.py _synchronize. Both writers and nightly organization use this file.
Diagram details: It reports COMMIT_REJECTED, outside the retryable set, so the watcher still marks the session handled. Ordinary topic file without a size limit. Both writers and nightly organization use only this file. Rebuilt after each successful installation. Include content up to 2000 tokens. Omitted blocks produce IDs, not errors. Omitted paragraphs remain in the source, indexed and discoverable through search and memory_get. A unique block in core.md could make clipping reject the entire transaction. Rendered IDs are always a subset. Observed: an 11,883-token handwritten core.md no longer blocks an unrelated write; rendering produced 1,983 tokens.
GET /api/memory/core returns both topics/core.md the authoritative source, root-level core.md rendered output, and effective injected content after considering memory.core.inject. Core defaults to a read-only effective-injection view with actual tokens and the 2000-token budget. “Full records” opens the editable source. Existing content/size/mtime continues to mean authoritative source for compatibility.topics/core.md, it moves project-specific, stale, or duplicate content to ordinary Topics or deletes it, retaining only rules, preferences, and identity facts needed by most future sessions.| Acceptance entry | Requirement |
|---|---|
GET /api/memory/core | reads a complete snapshot under the workspace lock; it retains authoritative content/size/mtime and additionally returns rendered content, effective injection, injection switch, token count, and budget. |
| Memory → Core | Defaults to actual injected content and token budget, explicitly showing empty injection when disabled. Users can switch to the full source and save through existing transactions. New drafts during save are not overwritten by refresh; all Markdown previews use shared sanitization. |
| Writer prompt | No longer requires every repeated process to become a Topic; Core excludes ordinary project progress. |
| Organizer prompt | Explicitly organizes project processes, duplicates, and stale states in Core; deleted content remains in Git history. |
topics/core.md was reduced from 31 records/about 15,305 tokens to three/about 429 tokens, with about 425 injected tokens. Repeated probes, old tool lists, repeated project checks, and invalid states were removed from current views but remain in Memory Git history. Automatic writing now starts at 8000 tokens. Retrieval uses organized Topics without directly mixing Source text. Current fixes cover special-token counting and continued edits during save; review conclusions are recorded after validation.This is the sole interface between memory and agent runtime. Every method except name has a default implementation, so replacement memory systems implement only methods they need. The diagram follows the session lifecycle and explicitly marks unconnected methods.
Diagram details: selection step: it is constructed directly. a session ID, so that fallback path is unreachable. Compactor supplies messages to discard. Memory selects what the summary retains.
Diagram details: so it is not mistaken for current user input. Two operations for selecting. a memory backend. Currently there is no. Should run once at session start. Consequence: the built-in implementation uses it to store. Opposite direction from the others: it stores nothing. The compactor has messages about to be discarded. and asks memory which should remain in the summary. Currently the idle watcher handles finalization. These five methods are not redundant; the runtime call sites are missing. They are extension points for third-party memory systems, which often need connections, session-end flushing, and pre-compaction preservation.
<memory-context> where it is produced; wrapping again at output would strip the inner layer and leave an empty wrapper. Also, the interface has no tool-registration method: memory systems with tools are plugins, already able to register commands, skills, MCP, hooks, and agents. A private route would bypass that mechanism.
Writes have three return outcomes interpreted by the watcher. Silence means success; failures distinguish transient conditions from those that will not change on retry.
Diagram details: Write complete, or below threshold with nothing due. Patch cites an unavailable source or has invalid formatting. and publish the reason on the event bus. endless retries only consume model quota. Invisible.
Diagram details: Writing finished, or the threshold was not reached and nothing is due. The opposite interpretation would turn that omission into endless retries. Sending identical content again yields identical rejection; The per-turn path uses the same returns and can also encounter a held lock. Previously lock contention was swallowed as “not enough content,” indistinguishable from an unmet threshold, leaving an unwritten turn without any report.
Node write markers and live-branch enumeration at session boundaries are implemented. Remaining issues concern mutually exclusive branch semantics, cross-session spawn relationships, and compaction-summary deduplication, not positional bookkeeping.
Diagram details: Wait until the session is idle.
Diagram details: Branch B: switch to FastAPI. Two adjacent paragraphs. they are mutually exclusive.
Diagram details: No duplication or omission. What is missing is the link saying this branch continues that conversation.
The page uses OpenProgram’s existing top title bar, secondary sidebar, and content area. Clear hierarchy, consistent row heights, readable content width, and stable narrow-window behavior improve the UI without new data layers or decorative dashboards.
The four sections, context lists, and Topic rows have similar visual weight; before selection, the right side is nearly empty. Core explanation/view switching sits on the left while content/token status sits on the right, obscuring their relationship.
The top bar provides page identity and settings only. The secondary sidebar selects a section, then lists its items or filters. The content header consolidates name, metadata, and actions; body text has a controlled reading width.
Retain Topics, Timeline, Recent, Core, and existing APIs. No writer status bar, Commitments, Scheduler status, statistics dashboard, new dependency, or new persisted UI state.
Click the four left-hand sections to inspect their lists and content states.
Current data: 0 Topics / 3 Core records / 2 Timeline dates
Durable memory created by conversations and the background writer will appear here. Core rules are managed separately.
Open Memory settingsTimeline remains a derived chronological view. It presents the Markdown generated for the selected date without adding event times or change metadata.
Never fabricate tool calls or tool results. Report only observed results from available tools.
Use plain “you” with no invented honorific unless the owner explicitly chooses one.
Never fabricate tool calls or tool results. Report observed results and state when no available tool returns a result.
Use plain “you” with no invented honorific unless the owner explicitly chooses one.
Use Chinese by default when discussing Memory and other operational work.
Generated from Source records and inserted into new system prompts. Edit the source records to change this preview.
Use Chinese by default when discussing Memory and other operational work.
Use plain “you” with no invented honorific unless the owner explicitly chooses one.
Never fabricate tool calls or tool results. Report observed results and state when no available tool returns a result.
Below 720px, do not stack section navigation, list, and detail vertically. Click a Topic for details; Back returns to the list.
New durable topics appear here.
Use Chinese by default when discussing Memory and other operational work.
Read-only prompt text generated from editable Core records.
Durable decisions and current context.
Section navigation appears only in list mode. Detail mode uses the full content width and an explicit Back button. Core, Timeline, and Recent enter content mode directly when there is no file list. Window width never rewrites desktop-sidebar expansion state.
| Area | Target structure | Problem avoided |
|---|---|---|
| Top title bar | Page title and Memory settings | No totals that existing APIs cannot accurately calculate; no writer/refresh status or duplicate explanations |
| Secondary sidebar | Folder, Topic, Timeline year/month/day, Recent, and Core entries all share 36px row height, 10px horizontal padding, 8px radius, 13px text, and selection background. Folder and Timeline year/month reuse the same 14×14 linear ChevronRight SVG/path/rotation and numeric badges. Timeline expresses hierarchy through indentation only. Topics does not duplicate topics/core.md | managed by Core. Expansion controls avoid tiny filled triangles, and Timeline uses the same count presentation as Topics |
| Content header | Name, type, path, modification time, view, and editing actions in one 76px area | Remove a separate metadata strip and scattered Core status |
| Body | Preview/Timeline/Recent/Core use a maximum 780px reading area; Edit remains a full-width monospace editor | Avoid excessively wide text and large unstructured empty regions |
| Markdown preview | Topics, Timeline, and Core interpret Memory’s Obsidian Flavored Markdown through existing marked and HTML sanitization. Named [^id] become sequential [1] superscripts; definitions form a separated ordered list at the end, supporting two-space continuation and ^[inline footnote]. Paragraph-end or standalone ^block-id becomes an invisible anchor only, accepting Latin letters, digits, and hyphens in IDs. Fenced code, four-space-indented code, and backslash-escaped footnote syntax remain literal | Do not expose internal evidence/block IDs in prose; do not change editable disk source |
| Narrow width | Section icons plus single-page list/detail switching | Do not auto-collapse desktop sidebars, stack three panes vertically, or allow overlap |
apps/web/components/memory/index.tsx, parts.tsx and memory-page.module.css . Memory-specific reading transformations live in a dedicated Markdown helper, reusing existing marked and HTML sanitization without changing editable content, APIs, writer, or disk format./memory shows a fixed secondary sidebar and complete content area at desktop width. Recent/Core do not repeat title, count, view switch, or injection status in the middle sidebar; Core controls/status appear once in the right content header. Recent’s header shows current records and live memory.recent.limit, for example 7 / 50 records, never confusing record count with token budget. The top bar does not call truncated Recent counts total Memory. Preview, Timeline, Recent, and Core use centered document areas; empty Topics has its own empty-state card. Defined named and inline footnotes render in occurrence order as [1] superscripts and an end-of-document list. Two-space continuations belong to the preceding footnote; body text hides evidence IDs and valid paragraph-end block IDs. Links, repeated refs, and undefined refs behave predictably. Inline/fenced/list-fenced/four-space-indented code stays literal, as do escaped references. Arbitrary input cannot collide with internal placeholders. Timeline collapses by year/month/day and never uses derived-file rebuild times as record times. Core distinguishes read-only Prompt preview from editable Source records; Topics excludes core.md. Below 720px, no horizontal overlap; editor actions occupy a separate wrappable row. The page neither requests nor renders writer status, pending counts, Commitments, or Scheduler state.memory.recent.limit from Recent API response headers and showing “current records / configured limit.” Core Prompt preview / Source records, injection status, and tokens also appear once on the right. Timeline year/month/day collapse remains. Structured transactions removed survey-test project records from production Memory; normal background writers may create new Topics. Centered documents, card empty states, a single 76px header, 780px reading width, and wrapping narrow-window edit actions are in production code. Topic rows and Timeline date entries share 36px height, 10px horizontal padding, 8px radius, and 13px text; Folder and Timeline year/month share 14×14 linear ChevronRight and numeric badges, with indentation-only hierarchy. Memory Preview converts named/inline footnotes to Obsidian Reading/Publish-style numbered superscripts and ordered end lists, hiding valid trailing block IDs while preserving editor/disk source. Complete mutually exclusive list/detail modes below 720px remain outside this iteration’s production scope.5f1f06c4 implements document areas, empty states, and compact context sidebars; 1c770a4e merges path metadata into the 76px header and standardizes 36px/780px dimensions; b583833d removes inaccurate top totals and adds narrow-window action wrapping; 7ef440c6 unifies secondary-sidebar rows for Topics, Timeline, Recent, and Core.check-memory-status changed from RED on the parent commit to GREEN. Memory settings, Markdown sanitization, TypeScript, production build, HTML, links, and diff checks pass. Memory Markdown tests cover named/repeated/inline footnotes, two-space continuations, literal inline/fenced/list-fenced/four-space code, escapes, placeholder collisions, hidden block IDs, and sanitization. A real Topic in the default port-18100 App displays [1]–[4] and an end footnote list with 1.–4. markers, without internal IDs in the body.Memory supports both direct Topic-file edits and structured calls by models or other programs. Neither replaces the other; they differ only in how callers express changes. All modifications use the same Runtime staging, validation, derived views, atomic installation, and Git process.
topics/**/*.md inside Runtime-provided staging. Each Source in new footnotes is written as [short keyword](source_ref). The model chooses keywords and an existing Source, not the final URL. After editing it must not overwrite production Memory directly; it calls commit_edits so Runtime allocates/preserves IDs, resolves source_ref into archive links, validates Source/block links, rebuilds Timeline/Recent/Relations/Core, and installs everything. Web/CLI file edits use the same staged_edit entry.memory_changes[]. Content operations are create_record, update_record, delete_record; structural operations are move_records. Runtime renders them into Topic Markdown and uses the same installation path as direct edits.source is immutable and must be verified. Runtime uses it to generate links, inspect trust state, and restore derived views. Display text cannot change the target.label is local display text for this citation, generated in the same Memory write call, not a permanent Source title. Memory-agent prompts and tool schemas require short context-relevant keywords and discourage Markdown, URLs, Source IDs, speaker names, or time templates. Apart from a nonempty-string Markdown requirement, Runtime does not assess keyword quality, truncate/rewrite labels, or add a model call for them.{"content":"...","sources":[{"source":"provider/thread/message","label":"research scope"}]}. Topic files retain standard Markdown: [research scope](../../sources/...#source-...). The parser recovers Source identity from the link target, never from bracketed display text.| Reference interface | Reusable convention | Project choice |
|---|---|---|
| RFC 6902 JSON Patch | add/remove/replace/move are separate; no semantic merge/split. | Preserve content CRUD versus structural move, targeting stable memory_id rather than exposing Markdown path pointers. |
| Notion block children | Content creation also specifies insertion position, owned by the parent structure. | create_record.destination describes Topic, heading chain, and position together. |
| ProseMirror Transform | High-level join/split intentions decompose into verifiable transaction steps. | Models plan merges/splits as update/create/delete/move; do not add ambiguous Runtime primitives. |
| Mem0 / Letta | Public Memory write APIs primarily create/update/delete records or blocks. | Retain record-level CRUD and add batch moves required by multi-file Topics. |
| Structured operation | Required data | Runtime behavior |
|---|---|---|
create_record | Final content, time, nonempty sources[{source,label}], and destination: topic_path, headings, position; before/after additionally requires anchor_memory_id. Model schema uses undated to represent no date; owner REST also accepts null. | Validate Topic, position, and every Source; retain label verbatim as display text, write canonical Markdown, and allocate block/evidence IDs. |
update_record | Existing memory_id plus complete final content/time/sources. | Update only record content, preserving block ID and position; replace the paragraph and evidence definition with final values. |
delete_record | Existing memory_id. | Delete the record and unused evidence definition; reject the whole transaction if block links would dangle. |
move_records | Ordered, nonempty memory_ids, and the same destination. | as creation. Move records in the specified order, preserving IDs, content, time, Sources, and creation order. Reorder within a section or move across sections/Topics. |
memory_committed and git_committed.memory_update and POST /api/memory/changes Add memory_changes. Existing whole-Topic changes[path/action/content] and legacy unified diff patch remain supported. Each request selects exactly one of the three formats; all eventually call MemoryWorkspace.update.memory_changes[].sources[].source may reference an existing verifiable archived Source, or request-level sources using a new-source-* label; Runtime converts the latter to a formal Source ID first. Callers cannot submit provenance, trust state, or owner identity. Prerelease source_refs: [string] remains accepted, using the original string as a compatibility display label; new requests expose only sources.destination.position Accept only start, end, before, after. before/after anchors must belong to the destination Topic/heading chain and cannot belong to the move set. Final order exactly matches memory_ids. Each ID accepts at most one content operation and one move; delete and move cannot target the same ID.update_record, several delete_record , and any required move_records; a semantic split consists of one update, several creates, and required moves. Section/file merges and splits require only batch move. Runtime provides neither merge_records, split_record, place_records nor reorder_records.mem_* records retain their IDs. Structural operations cannot split multiple old IDs or canonical aliases sharing one physical paragraph. Moves must include all IDs of that paragraph contiguously in original paragraph order in one move_records.INVALID_ARGUMENT . Identical semantics with different relative link paths are not conflicts.create/update/delete inputs are converted to new names by Runtime. Model schemas and this document expose only four official operations; legacy aliases are not extended.memory_status, memory_get and memory_update to maintain long-term records linked by MemoryRef; Scheduler service does not implicitly dual-write. Deleted records leave no placeholder or ID forwarding; querying an old memory_id returns NOT_FOUND. External token capabilities and non-owner mutation scope remain deferred.| Acceptance entry | Observable results |
|---|---|
After direct file editing, commit_edits | [research scope](source_ref) normalizes to a real archive link with the same keyword. Production Topic, Timeline, Recent, Relations, and Core reflect the edits together; the parser recovers full Source IDs. Validation failure leaves production files unchanged. |
MemoryWorkspace.update(memory_changes=...) | create_record/update_record accept sources[{source,label}] and preserve caller labels; legacy source_refs still works. The four operations work independently or together; any failure leaves revision and all Memory bytes unchanged. |
memory_update | memory_changes, whole-file changes , and legacy unified diff share transaction behavior and return fields. |
POST /api/memory/changes | accepts the same memory_changes and returns revision, changed_files, block/evidence IDs, and Git commit information. |
memory_changes, Topic moves, and Timeline derived display. Source archives, retrieval identity, Preview renderer, database, and model-call count stay unchanged. Public-entry RED covers direct file editing and MemoryWorkspace.update(memory_changes=...); affected validation covers Topic parsing, structured modifications, Writer prompt, derived views, owner REST, HTML, and documentation links.sources[{source,label}]; legacy source_refs, pre-v2 provider archives, and Source filenames with encoded characters remain readable and writable. Each Source archive is scanned once per Topic-tree parse. Runtime no longer classifies, truncates, or substitutes labels semantically.trusted/pending from every Source reference before returning a Topic; pending-backed Topics never enter automatic context.changes and legacy unified diff remain compatible. Direct edits and structured operations share staging, validation, empty-structure cleanup, Timeline/Recent/Core derivation, atomic installation, and Git history. Public-entry regression tests cover batch moves, position/shared-paragraph checks, Recent creation-order preservation on first structural change, and cross-Topic evidence conflict rejection. Scheduler’s internal Agent lifecycle writes follow its design document; deleted records leave old memory_id returns NOT_FOUND, without retained records or old-ID mappings. External token capability remains deferred as specified here.topics/core.md; background writers use the default chat agent with live memory.writer.model updates. Unclassified exceptions are non-retryable by default.memory.backend=none disables prompts, recall, automatic writing, organization, and memory threads, and rejects all CLI memory verbs and /api/memory/* routes before workspace creation. Tools, CLI, and Web expose recent writer success, failure category, retryability, and pending counts. One-shot memory backfill is implemented for trusted Sources not referenced by Topics, excluding pending Sources and reusing normal writer transactions by batch. Component tests cover SessionDB, default-model resolution, managed tools, staging installation, Topics, markers, and watcher state. Production-workspace acceptance parsed all 23 old v1 Source files (154 frames) through read-only compatibility from origin_scope to authority_tier. Two pending messages produced three Topic files and five unique blocks; six Source refs and relation targets validated; both node markers were written. A second scan processed zero items without changing revision. Historical backfill was not run, leaving 152 unreferenced frames. Still unimplemented: unauthorized-request hold queue, requester-level filtering, mutually exclusive branch semantics, cross-session spawn relationships, and direct head-change-triggered writing. Failure reason codes are a closed enum; restricted writer phases cannot see Source archives.