memory_write
Persist and manage long-term memories: write, import files, capture missed sessions, rebuild indexes, and promote or archive memories in the markdown corpus.
Instructions
Writes for the persistent memory corpus — the companion to the read-only memory tool. import (bring files in; CREATES new memories and never overwrites an existing one), capture (write this session's exchanges to the staging store — use when the memory connector was OFF while the work happened and you have realised it mattered; sinceMinutes limits it to the last N minutes and is measured against each exchange's last activity, re-running is safe, and a run that only rewrote the still-open turn with its newer text reports exchangesRefreshed rather than exchangesCaptured), index (rebuild), demote/promote (move a memory between tiers by rewriting ONE frontmatter field). There is no delete action. MEMORY_CURATED_READ_ONLY=1 refuses every write to the memory folder outright. Reads — search, latest, get, neighbors, thread, verify, sessions, index_status, probe_status — are on memory. USE import TO BRING IN SOMEONE ELSE'S MEMORIES — give it an ABSOLUTE path to a file or folder and it reads md/txt/rtf/doc/docx/odt/html/pdf/csv/json/zip, including a ChatGPT export. It never overwrites, so re-running is safe; it REFUSES any item containing a credential and names it; and dry:true reports without writing. Afterwards it tells you what KIND of corpus it derived, because the query advice depends on that. THE LIBRARY: import REFERENCE material (books, manuals, policies) that is big (>200KB) or book-shaped (PDF) WITH category:'' — the directory is created for you, and an import without it is refused, naming the fix. Structure is recovered at import: PDF pages become ## p.N anchors (cite them — a human can open the page), document headings become real sections, CHAPTER lines are promoted. replace:true supersedes a re-issued document (old version to /archive/, stamped, never deleted). Rebuild one with index scope:""; a search never rebuilds a library index inline — a memory search never does. INDEXING IS ASYNC: index returns a jobId immediately and builds off the request (a blocking index used to TIME OUT through MCP); poll index_status on the memory tool with that jobId. One build per index file at a time, so a second concurrent index for the same scope reports that it is already running. demote/promote move a memory between the hot and archive tiers by setting metadata.tier — content is never deleted or moved; archived memories stay searchable, they just lose the boost.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dry | No | import: report exactly what WOULD be imported and write nothing. | |
| run | No | probe_status: execute the sweep now instead of reading the last sidecar. Probes never run on the search path. | |
| back | No | thread: how many exchanges BEFORE the anchor to return (default 0). | |
| name | No | get/neighbors/demote/promote: the memory name (filename without .md). | |
| near | No | search: SOFT time anchor (ISO). Tilts ranking toward that period without hiding anything outside it. | |
| path | No | import: ABSOLUTE path to a file or folder of memories to bring in. Reads md/txt/rtf/doc/docx/odt/html/pdf/csv/json/zip; a ChatGPT export (conversations.json or its .zip) is recognised and its conversation tree walked in order. | |
| text | No | verify: raw text to scan for commit SHAs, instead of naming a memory. | |
| wait | No | index: block until the build finishes instead of returning a jobId. Only for callers that can wait minutes — the default is async because a blocking index TIMED OUT through MCP. | |
| after | No | search: HARD lower bound on a memory's date (ISO). Excludes — use only when you mean exclusion. | |
| brief | No | get: return only the text and where it came from (name, path, body, and what was truncated) — not the ~25 provenance and freshness fields. Use it when you already decided to read this memory and just want the content, e.g. following an absence note that told you to open bestWeak[0]. search/latest: return only name/corpus/score/snippet/provenance and the timestamp per ROW — no path, type, description, per-leg scores, thread position or verified commits. The ENVELOPE is unchanged: indexStale, staleWarning, recencyVoid, recentUnindexed, uncapturedSessions, captureHealth, configWarning and guidance are all still returned, because brief trims rows and never caveats. Use it for a wide scope:'all' sweep where you intend to open one or two results with `get` afterwards. | |
| force | No | index: ignore the incremental cache and re-embed every file. | |
| jobId | No | index_status: the job id returned by index. | |
| limit | No | search: max results (default 8). latest: default 5, or 10 in browse mode (no query). sessions: how many conversations (default 20). | |
| query | No | search: the natural-language query. latest: the terms to filter by — OMIT IT for a browse of the newest documents in the scope. | |
| scope | No | search/latest: which corpus or corpora — a name, or an ARRAY mixing names freely. Work corpora: 'curated' (default) = hand-written memories from THIS project's memory folder; 'projects' = hand-written memories from OTHER projects' memory folders (same kind of content, hot tier, writable — reached by default via scope-widening whenever another project has memories); 'staging' = auto-ingested conversation exchanges; 'handoff' = institutional HANDOFF/PHASE documents, indexed read-only from outside the memory folders (use this for 'what was the state of X when it was handed over'). 'all' = the WORK corpora only, returned as SEPARATE sections — ranked sections under .groups for search, and for latest one time-ordered section PER CORPUS under .sections, each declaring its own `orderedBy`, because curated files carry no timestamp and their mtime order is bookkeeping, not chronology. LIBRARY categories (imported reference material — books, manuals, policies — each a directory under memory-library/) are searched ONLY when named ('books', ['all','books']) or via 'everything' (= work + every category); they never enter 'all' or the automatic routing, by design, so imported content can never dilute work retrieval. Unknown names error and list what exists. Each corpus has its OWN index and its own statistics — never blended, because blending measurably costs recall. Omit to let phrasing choose (reported in scopeHint). On the `index` action this selects which index to rebuild; the default there is curated + projects + handoff (staging is ingest-driven and expensive; library categories rebuild when named or via 'everything'). | |
| action | Yes | Which write operation to perform. These change your memories or their derived state; reads live on the `memory` tool. | |
| before | No | search: HARD upper bound on a memory's date (ISO). Excludes. | |
| domain | No | OPTIONAL, and worth passing when you know: what KIND of work these memories are about. Counting can only separate code from not-code — a novel, a business plan and a day-planner are statistically identical — so this is the only way to get writing/business/research/planning advice apart. Trusted when given; derived from the corpus when not. | |
| offset | No | get: start the body at this character offset, to continue a previous slice. | |
| account | No | search: restrict to memories written by an account. 'mine' means this surface's own MEMORY_ACCOUNT. Pass an array to read across several. Memories with no account label are ALWAYS returned, so nothing written before labelling existed disappears. | |
| forward | No | thread: how many exchanges AFTER the anchor to return (default 8). Named forward, not after, because `after` is search's DATE filter. | |
| outline | No | get: return ONLY the heading outline with sizes and offsets, no body. The cheapest way to navigate a large memory before reading any of it. | |
| project | No | search: restrict to memories from a project folder. 'this' means the server's canonical project. Memories with no project are always returned. | |
| replace | No | import (with category): a re-import of the SAME name supersedes the old version — the old file moves to <category>/archive/ stamped metadata.supersededAt (out of the index, never deleted), and the new one takes its place. For re-issued policies/statutes. Without it, an existing name is skipped, as always. | |
| section | No | get: return only this heading's block (to the next heading of the same or higher level). THE primary read path for a large memory — prefer it over paging. | |
| category | No | import: which LIBRARY category to file this under (e.g. 'books', 'manuals', 'policy', 'legal' — any name; the directory memory-library/<category>/ is created if new). REQUIRED for anything big (>200KB of text) or book-shaped (PDF): without it such an import is refused rather than silently diluting the curated corpus. Category content is read-only reference material, indexed separately, and searched only when named or via scope:'everything'. Structure is recovered on the way in: PDF page breaks become '## p.N' anchors, docx/html headings become real markdown headings, plain-text CHAPTER lines are promoted — so the section splitter chapters the document and citations carry page anchors. | |
| keyFacts | No | import: OPTIONAL 1-3 short atomic facts per SECTION of the document being imported, keyed by section name ('<doc>#<section-slug>', or just the section slug for a single-document import). Written from the section's own text, they are indexed as a high-weight keys field so a section can be found by what it IS about rather than only by the words it happens to contain. They never become content: the body, the snippets and the returned text are unchanged. Stored in a sidecar <file>.keyfacts.json. Read only when MEMORY_KEY_FACTS is on. | |
| maxChars | No | get: cap the body (default 20000). A capped response always carries totalChars + truncated so a slice is never mistaken for the whole document. search with scope:'everything': raises that compact view's snippet budget (its default is deliberately small; this is the override). | |
| sessionId | No | search/latest/sessions: restrict to one conversation (the transcript session id stamped on ingested exchanges). | |
| sinceMinutes | No | capture: remember only the last N minutes of this session. Omit for the whole session. The window is measured against each exchange's LAST ACTIVITY, not the moment its question was asked, so a long turn still in progress stays inside a short window. Use when the memory connector was OFF while the work happened and you have realised afterwards that it mattered — the transcript is on disk regardless, so nothing was lost. Captured exchanges go to the STAGING corpus at archive tier and never outrank a hand-written memory; re-running is safe because already-captured exchanges are skipped: a re-run that finds the current turn has grown reports exchangesRefreshed, not exchangesCaptured. | |
| includeSummaries | No | latest: context-compaction summary exchanges are DEMOTED by default — still returned, but sorted after ordinary sources, because they restate a whole conversation (matching almost any query) while carrying a recent timestamp for old content. Pass false to exclude them entirely. |