{"slug": "mnemara-a-memory-layer-that-keeps-claude-agents-continuous", "title": "Mnemara – a memory layer that keeps Claude agents continuous", "summary": "Mnemara, a Claude conversation runtime that re-reads a role document on every turn and pins it as the system prompt, reported zero out-of-lane writes and zero protocol violations in a 101-turn coding session on 2026-05-08. The tool, built on the Claude Agent SDK, includes a Sentinel role that halts the agent on four failure modes—no progress, polling, drift, and sycophancy—to prevent runaway API costs. Mnemara is available via pip install mnemara and supports Linux, macOS, and Windows through WSL.", "body_md": "Note:There is also a Rust project called[by]`mnemara`\n\n`deliberium`\n\n— a memory engine for embedded/service systems. Different project, parallel naming (both inspired by Mnemosyne). If you arrived looking for that one, head over there.\n\n**Stop your Claude agent from running away with your bill.**\n\nMnemara is a Claude conversation runtime where the **role doc is re-read on\nevery turn** and pinned as the system prompt. That sounds boring until you\nrealize what it lets you do: write rules the agent has to obey on every\nsingle API call — not just the first one — and the agent enforces them on\nitself.\n\nEmpirical check (2026-05-08):in a 101-turn coding session run on Mnemara itself, the panel made zero out-of-lane writes and zero protocol violations against its role doc. The rule that fires on turn 1 still fires on turn 101.\n\nThe flagship example is [ examples/roles/sentinel.md](/mekickdemons-creator/mnemara/blob/main/examples/roles/sentinel.md).\nDrop it in, and the agent watches its own execution for the four failure\nmodes that turn agent sessions into expensive accidents:\n\n**No progress**— N+ turns on the same sub-goal with no state change.** Polling**— same tool, same args, 3+ times in a row.** Drift**— about to do something the user didn't ask for.** Sycophancy**— about to reverse a conclusion under tone-only pushback.\n\nWhen any one fires, the agent **halts and asks**, instead of burning\nanother N turns of API budget. The role doc is plain Markdown — edit it\nto match the failure modes you actually see.\n\nTry it in 30 seconds:\n\n```\npip install mnemara\nmnemara init --instance scratch\nmnemara role --instance scratch --set-from-url \\\n  https://raw.githubusercontent.com/mekickdemons-creator/mnemara/main/examples/roles/sentinel.md\nmnemara run --instance scratch\n```\n\nBuilt on the ** Claude Agent SDK**.\nMnemara wraps the SDK with a transparent, file-based context layer so you\ncan see and shape exactly what the model sees on every turn.\n\nWhat's in the box:\n\n- A\n**role doc** re-read on every API call and pinned as the system prompt (the bit that makes Sentinel work). - A configurable\n**rolling window** of recent turns (FIFO, by row count or token budget). - Native tool use — Bash, Read, Edit, Write — plus an in-process\n`WriteMemory`\n\ntool registered as an SDK MCP server. - Optional\n**MCP wire-through**: declare stdio MCP servers in config and the Claude Agent SDK exposes them to the model. - A\n**Textual TUI**(`mnemara run`\n\n) and a bare prompt-toolkit REPL fallback. - Per-instance, file-only state under\n`~/.mnemara/<instance>/`\n\n— no daemon, no service, no hidden state. - Optional memory/wiki + LanceDB RAG + Kuzu property graph backends, and a\n`mnemara replay`\n\nconsolidation primitive that drafts wiki pages and role-amendment proposals from clustered memory atoms.\n\n```\npip install mnemara\n```\n\nOr from source if you want to hack on it:\n\n```\ngit clone https://github.com/mekickdemons-creator/mnemara.git\ncd mnemara\npython -m venv .venv\nsource .venv/bin/activate\npip install -e .\n```\n\nTested on Linux and macOS. Windows works through **WSL** — the\nClaude Agent SDK's tool calls use `bash`\n\n, which is not available on\nnative Windows shells (cmd / PowerShell). If you're on Windows, run\nMnemara inside a WSL distro and treat that as your \"Linux\" environment.\n\nMnemara runs on the Claude Agent SDK, which talks to the Anthropic API. The easiest way is to set your API key:\n\n```\nexport ANTHROPIC_API_KEY=sk-ant-...\n```\n\nGet a key at [https://console.anthropic.com/](https://console.anthropic.com/). The SDK also supports the local\n`claude`\n\nCLI's subscription auth as a fallback if you have Claude Code\ninstalled and logged in — but the documented path is the API key.\n\nBefore your first run: skim the\n\n[Permissions model]section. Mnemara gives an LLM Bash/Read/Write/Edit access to the machine you run it on. It is not a sandbox.\n\n```\nmnemara init --instance scratch\n# (prompts for role doc path; you can leave it blank and set it later)\nmnemara role --instance scratch --set examples/roles/sentinel.md\nmnemara run --instance scratch\n```\n\nThe `--set`\n\nargument points at a **role doc** — a Markdown file that becomes\nthe agent's system prompt. The repo ships with `examples/roles/sentinel.md`\n\nas a starting point; see [Role docs](#role-docs) below for what to put in\nyour own.\n\nBy default `mnemara run`\n\nopens the **Textual chat panel** (TUI). Pass\n`--no-tui`\n\n(or set `MNEMARA_NO_TUI=1`\n\n) for the bare prompt-toolkit REPL —\nuseful for scripting or non-TTY contexts.\n\n```\n+------------------------------------------------------------+\n| mnemara: scratch        model=claude-opus-4-7  role=...    |   header\n+------------------------------------------------------------+\n|                                                            |\n|  you: how do I check the lease timeout?                    |\n|  assistant: open server.py and grep for ...                |\n|  > tool: Read(file_path=server.py)                         |\n|    result: ...                                             |\n|                                                            |   chat log\n+------------------------------------------------------------+\n| turns: 12/100 | tokens: 14K/200K | model: claude-opus-4-7  |   status\n+------------------------------------------------------------+\n| > _                                                        |   input\n+------------------------------------------------------------+\n```\n\nThe input area is multi-line. See [Slash commands](#slash-commands-repl-and-tui)\nbelow for the full keybinding table and the slash-command surface\n(`/role_doc`\n\n, `/context`\n\n, `/clear`\n\n, `/evict`\n\n, `/inbox`\n\n, `/models`\n\n, `/swap`\n\n,\n`/tokens`\n\n, `/export`\n\n, `/import`\n\n, `/compress reads`\n\n, `/skeleton`\n\n, `/name`\n\n, etc.).\n\nThe role doc is a plain Markdown file that becomes the agent's **system\nprompt**. Mnemara re-reads it on every API call and pins it at slot 0 of\nthe messages — meaning it applies to every turn, not just the opening one,\nand you can edit the file mid-session and the next turn picks up the\nchanges.\n\nThis is the strongest steering signal you have over the agent. Use it.\n\nA good role doc is a short prose document (a few hundred to a few thousand words) that answers, in order:\n\n**Who the agent is**— its identity and standing instructions in this instance. (\"You are a code reviewer for the Acme repo.\" \"You are a research assistant working on tax law.\")**What it should and shouldn't do**— scope, hard constraints, anti-patterns to avoid.** How it should behave when something goes wrong**— when to halt, when to ask for help, when to escalate.\n\nYou can include style notes (\"be terse, no apologies\"), tooling conventions (\"always run the tests after writing code\"), or domain glossaries. There is no required schema. The only mechanical requirement is that the file exists and is readable.\n\nThe most common reason an interactive agent session burns through\ntokens with nothing to show for it is that the agent **gets stuck**:\n\n- It calls the same tool over and over waiting for output to change.\n- It drifts from the user's actual request into adjacent rabbit holes.\n- It reverses a correct conclusion the moment the user pushes back.\n\nThese are role-doc-shaped problems. The role doc is where you encode the\n**rules that keep the agent from spiraling**. If those rules aren't in the\nsystem prompt, they aren't applied consistently — they reappear only when\nthe user remembers to remind the agent.\n\n[ examples/roles/sentinel.md](/mekickdemons-creator/mnemara/blob/main/examples/roles/sentinel.md) is a\nself-monitoring role doc. Drop it in as your instance's role and the\nagent will watch its own execution for the failure modes above\n(timeout / no progress, polling, semantic drift, sycophantic reversal)\nand\n\n**halt to ask the user** rather than spending another N turns on a runaway loop.\n\n```\nmnemara role --instance my-agent --set examples/roles/sentinel.md\n```\n\nOr download it directly from GitHub without cloning the repo:\n\n```\nmnemara role --instance my-agent --set-from-url \\\n  https://raw.githubusercontent.com/mekickdemons-creator/mnemara/main/examples/roles/sentinel.md\n```\n\n`--set-from-url`\n\nfetches the doc once (https only, 1 MB cap, UTF-8) and\nsaves it into `~/.mnemara/<instance>/role.md`\n\n. Mnemara never re-fetches\nthe URL at runtime — the saved local copy is what gets re-read each\nturn. Edit the local copy to customize.\n\nUse it as-is for monitoring-flavored work, or treat it as a template: copy the file, edit the trigger conditions to match the failure modes you care about, and point your instance at the copy.\n\nYou can also layer Sentinel-style self-monitoring rules on top of a task-specific role doc — there's nothing special about Sentinel; it's just text in a Markdown file that the agent reads on every turn.\n\nThe repo ships three role docs under [ examples/roles/](/mekickdemons-creator/mnemara/blob/main/examples/roles):\n\n| File | Use it when |\n|---|---|\n`sentinel.md` |\n\n`coder.md`\n\n`researcher.md`\n\nThese are starting points. Copy any of them, edit the rules to match your work, and point your instance at the copy.\n\nConcrete example. Your role doc tells the agent to halt if it polls the same tool 3 times. The user asks the agent to wait for a build to finish:\n\n```\nyou: run the build and tell me when it's done\n\nagent: > Bash(npm run build)\n       (output: building...)\n       Build is running. Let me check.\n       > Bash(npm run build)\n       (output: building...)\n       Still building.\n       > Bash(npm run build)\n       (output: building...)\n\n       [SENTINEL HALT]\n       trigger: polling\n       turn: 4\n       context: I've called `npm run build` three times in a row and\n       each call started a new build rather than reporting on the\n       running one. I'm not actually monitoring; I'm starting fresh\n       builds.\n       proposed next step: wait on the existing build process via\n       `wait` or check `npm run build:status` if it exists. Which\n       would you like?\n```\n\nWithout the role doc, the same agent would happily call `npm run build`\n\ntwenty times in a row, each one starting a fresh build, before either\nthe user noticed or the API budget ran out. The role doc is what makes\nthe agent notice on turn 4 and check in.\n\nEverything for an instance lives under `~/.mnemara/<instance>/`\n\n:\n\n| Path | Purpose |\n|---|---|\n`config.json` |\nThe config (model, role-doc path, window size, tool policies, MCP servers). |\n`turns.sqlite` |\nThe rolling-window store. One row per turn. |\n`permissions.json` |\nPersisted \"always allow\" patterns per tool. |\n`memory/YYYY-MM-DD.md` |\nNotes the agent or user have written via `WriteMemory` / `/note` . |\n`wiki/<slug>.md` |\nTopic-keyed wiki pages (slash-allowed slugs). |\n`index/` |\nLanceDB RAG index (embeddings of memory + wiki + manual entries). |\n`graph/` |\nKuzu property graph (entities, wiki pages, topic tags, edges). |\n`wiki_proposals/<slug>.md` |\nReplay-drafted wiki promotions awaiting agent review. |\n`sleep/YYYY-MM-DD.md` |\nSleep digests written by the replay primitive. |\n`memory/archive/` |\nNear-duplicate memory atoms archived (never deleted) by replay. |\n`role_proposals/` |\nRole-amendment proposals — written by `propose_role_amendment` or replay. |\n`debug.log` |\nAppend-only JSONL log: errors, tool calls, eviction events. |\n`.prompt_history` |\nREPL input history. |\n\n`~/.mnemara/<instance>/config.json`\n\n:\n\n| Field | Meaning |\n|---|---|\n`role_doc_path` |\nAbsolute path to the role doc. Re-read on every API call. Pinned as the system prompt. |\n`model` |\nClaude model id (e.g. `claude-opus-4-7` , `claude-sonnet-4-6` , `claude-haiku-4-5` ). |\n`max_window_turns` |\nRolling-window size (FIFO). Default 20. Counts both user and assistant turns. |\n`max_window_tokens` |\nToken-budget cap. The window is FIFO-trimmed once total tokens exceed this. |\n`allowed_tools` |\nList of `{tool, mode, allowed_patterns}` policies. `mode` ∈ `allow` /`ask` /`deny` . |\n`mcp_servers` |\nList of stdio MCP servers wired through to the model. |\n`stream` |\nIf true, render the model's text deltas as they arrive. |\n`bash_timeout_seconds` |\nBash command timeout. Default 60. |\n`file_tool_home_only` |\nIf true, Read/Write/Edit refuse paths outside `$HOME` . Default true. |\n`display_name` |\nCosmetic label shown in the TUI chat log instead of `assistant` . Empty = default. Set via `/name <label>` . |\n\nAll default to `False`\n\n(or `0`\n\n). Turn on per instance.\n\n| Field | Meaning |\n|---|---|\n`auto_evict_after_write` |\nAfter any turn containing Edit/Write/MultiEdit/NotebookEdit blocks, stub the bulky body content of those tool_use specs and prior Read specs for the same file. Audit shell preserved. |\n`compress_repeated_reads` |\nAfter every turn, walk the window for repeated Reads of the same file — keep the latest at full fidelity, stub earlier ones as a unified diff or \"unchanged\" pointer. v0.6.0 / v0.8.0. |\n`preserve_compressed_reads` |\nWhen set, rows flagged as compression stubs are excluded from cap-FIFO eviction (same soft-protect as pinned rows). |\n`read_skeleton_enabled` |\nRegisters the `read_skeleton` tool so the agent can request Python signatures + docstrings only (~90% smaller than a full Read). v0.7.0. |\n`file_stat_manifest_enabled` |\nAuto-injects a markdown table at the bottom of system_prompt listing every file Read this session: size, mtime, fresh/STALE/gone vs current disk hash, est tokens. v0.7.0. |\n`runtime_sentinel` |\nWires SDK hook events so a per-session `RuntimeSentinel` watches PreToolUse events. If the same `(tool, args)` fires 3+ times in 5 events, injects a synthetic `[SENTINEL HALT]` and stops the turn. Belt-and-suspenders with `sentinel.md` . |\n`row_cap_slack_when_token_headroom` |\nIf > 0, lets `n_turns` exceed `max_window_turns` by up to this many rows when token usage is well under cap. Lets the row cap \"breathe\" with the byte budget. Default 0. |\n\n| Field | Meaning |\n|---|---|\n`rag_enabled` |\nLanceDB RAG index over `memory/` + `wiki/` . Default `True` . |\n`rag_embed_url` |\nOllama embeddings endpoint. Default `http://localhost:11434/api/embeddings` . |\n`rag_embed_model` |\nEmbedding model. Default `nomic-embed-text` . |\n`rag_auto_index_memory` |\nRe-index memory atoms on each write. Default `True` . |\n`rag_auto_index_wiki` |\nRe-index wiki pages on each write. Default `True` . |\n`graph_enabled` |\nKuzu property graph for `memory_atoms` /`wiki_pages` /`entities` . Default `True` . Off-switch if Kuzu is unavailable. |\n`replay_default_days` |\nDefault lookback for `mnemara replay` . Default 7. |\n`replay_default_threshold` |\nMinimum cluster size to count as a pattern. Default 3. |\n`replay_policy_path` |\nOverride path for the replay policy doc. Empty = `<instance>/wiki/replay_policy.md` . |\n\n```\nmnemara init --instance <name>            # create ~/.mnemara/<name>/, refuses to overwrite\nmnemara run --instance <name>             # open the chat panel (TUI; --no-tui for bare REPL)\nmnemara list                              # list instances\nmnemara show --instance <name> [-n N]     # print the rolling window (read-only)\nmnemara clear --instance <name>           # wipe the rolling window\nmnemara delete --instance <name> --force  # nuke ~/.mnemara/<name>/\nmnemara role --instance <name> --set PATH                # set role_doc_path (local file)\nmnemara role --instance <name> --set-from-url URL        # download once into instance dir\nmnemara note --instance <name> TEXT...    # append a memory note from the shell\nmnemara replay --instance <name> [--days N] [--threshold N] [--apply]  # consolidation pass\nmnemara migrate --all                     # run schema migration on every instance (idempotent)\nmnemara migrate --instance <name>         # run schema migration on one instance\n```\n\nCommands marked **(TUI)** are TUI-only — they open a modal overlay or toggle a\nTUI-specific feature.\n\n```\n/role <path>         (REPL) swap role doc (also persists to config)\n/role_doc            (TUI) open the role-doc editor modal — edit live; Ctrl+S\n                     saves; changes take effect on the next turn (the role doc\n                     is re-read on every API call). Modal exposes\n                     [📋 Paste] / [⎘ Copy] buttons (raw terminal paste keys\n                     can be intercepted by the OS terminal before reaching\n                     Textual's modal focus — the buttons go through pyperclip\n                     directly and are reliable across terminals).\n/context             (TUI) open the context viewer — tabbed breakdown of where\n                     input tokens are going (tool schemas, role doc, manifest,\n                     pinned rows, working window) with browse / evict / pin\n                     actions and role filtering. Pinned rows are listed in\n                     slot-position order with a numeric badge (`#03`). Detail\n                     panel splits into a read-only header (Turn / role / ts /\n                     pin) and a body-only editable TextArea; [💾 Save edit]\n                     writes body changes back to the row in place. [✏️ Rename]\n                     renames just the slug portion of a pin label while\n                     preserving its position prefix; [➕ Add slot] inserts a\n                     new pinned slot at the next available position;\n                     [⇅ Move] reorders pinned slots by changing the prefix.\n                     [📋 Paste] / [⎘ Copy] available too.\n/show                (REPL) print the rolling window\n/clear               comprehensive wipe — strips tool_use blocks, thinking\n                     blocks, and user/assistant turns; pinned rows preserved;\n                     reports the freed-token count\n/models              list available Claude model shortcuts\n/swap <model|n>      switch model for this and future sessions\n/tokens <N>          set max_window_tokens live (accepts 500k, 1m, 200000)\n/note <text>         (REPL) append to today's memory file\n/proposals           (REPL) list pending role-amendment proposals\n/evict               show eviction stats\n/evict tools         strip tool_use blocks from all stored rows\n/evict thinking      strip thinking blocks from all stored rows\n/evict user          drop all user-turn rows (keep assistant responses)\n/evict assistant     drop all assistant-turn rows (keep user inputs)\n/evict <N>           drop the N oldest rows from the rolling window\n/evict last <N>      drop the N most-recent rows (rollback a bad paste/turn)\n/inbox               (TUI) toggle peer-message delivery on/off\n/stop                cancel the in-flight turn\n/export [N] [path]   round-trip the session (turns + config + role_doc) to markdown\n/import <path>       restore a session from a /export markdown file\n/compress            compress to 25% of current token count (the default\n                     compress-to-tokens operation, also available as the\n                     [🗜 Compress] button)\n/compress <N>        compress to a specific token target (e.g. /compress 20000,\n                     /compress 500k); accepts k/m suffixes\n/compress smart      inject a structured audit turn so the agent reviews its\n                     own rolling window and decides what to evict — judgment-\n                     driven rather than mechanical FIFO\n/compress reads      stub repeated Read results with diffs (existing behavior)\n/skeleton <path>     manually extract Python signatures from a file (debug)\n/name <label>        set display_name; clear with /name (no arg)\n/quit, /exit         save state and exit\n/help                show this list\n```\n\nThe TUI chat panel exposes a row of buttons below the chat log:\n\n| Button | Action |\n|---|---|\n`Send ⌃S` |\nSend the current input (same as Ctrl+S). |\n`⚡ Inbox: ON / OFF` |\nToggle peer-message delivery live. Only active when peer-poll is configured (see\n|\n\n`📄 Role`\n\n`/role_doc`\n\n).`💬 Context`\n\n`/context`\n\n).`🗜 Compress`\n\n`/compress`\n\nwith no argument). Strips thinking blocks, write pairs, and tool_use bodies before falling back to FIFO eviction; pinned rows are never touched.`Quit ⌃C`\n\nThe TUI input area is multi-line — `Enter`\n\ninserts a newline.\n\n| Key | Action |\n|---|---|\n| Ctrl+S | Send the message |\n| Enter | Newline in the input |\n| Escape | Clear the input |\n| Ctrl+L | Clear the on-screen chat log (does NOT touch turns.sqlite) |\n| PageUp / PageDown | Scroll chat |\n| Ctrl+C | Quit |\n\nRead this section before you run Mnemara.The agent has Bash, Read, Write, and Edit tools. With permissive settings it can run any command on your machine — including destructive ones (`rm -rf`\n\n,`git push --force`\n\n, network calls, file overwrites). Mnemara isnot a sandbox. It runs as your user, with your filesystem and network permissions. Treat it like a shell session you've handed to an LLM.\n\nEach tool has a `mode`\n\n:\n\n`allow`\n\n— never prompts.**Use only for tools you've decided are safe to invoke without review.**`ask`\n\n— prompts on first use; user picks`yes`\n\n,`no`\n\n,`always`\n\n, or`session`\n\n.`deny`\n\n— always blocked.\n\nDefaults (deliberately conservative): Bash=ask, Read=allow, Write=ask, Edit=ask, WriteMemory=allow.\n\n**Things to know:**\n\n- Setting Bash to\n`allow`\n\nmeans the agent can run**any shell command** without prompting. Don't do this on a machine with credentials, prod access, or unbacked-up data unless you know what you're doing. `allow_always`\n\n(the`a`\n\nanswer at a prompt) writes a regex to`permissions.json`\n\n. Review that file — a too-broad regex is a permanent foot-gun.`file_tool_home_only`\n\n(default`True`\n\n) restricts Read/Write/Edit to paths under`$HOME`\n\n. Disabling it lets the agent touch anywhere your user can.- The agent can call MCP tools wired through\n`mcp_servers`\n\nin`config.json`\n\n. Those tools run with your privileges — vet them like you'd vet any third-party binary. - If you don't trust a role doc to behave, run it in a throwaway\ninstance (\n`--instance scratch`\n\n) on a non-sensitive machine, or under a restricted user account / container.\n\n**Runtime sentinel** (`runtime_sentinel: true`\n\nin `config.json`\n\n) adds a\nsecond, belt-and-suspenders layer on top of the role-doc sentinel. When\nenabled, Mnemara wires the Claude Agent SDK's `include_hook_events`\n\nflag (SDK\n\n= 0.1.74 required) so that tool lifecycle events flow into each turn's processing loop. A per-session\n\n`RuntimeSentinel`\n\nwatches the last five`PreToolUse`\n\nevents: if the same`(tool_name, args)`\n\npair appears three or more times in that window it injects a synthetic`[SENTINEL HALT]`\n\nnotice into the assistant turn and stops consuming the stream, preventing the model from burning further tool calls on an obvious polling loop. This is complementary to the role-doc sentinel (`sentinel.md`\n\n): the role-doc rule relies on the model noticing its own pattern; the runtime rule fires at the protocol level regardless of what the model intended. Both can run simultaneously — enable`runtime_sentinel`\n\nwhen you want a hard floor under the model's self-monitoring.\n\nWhen prompted at the REPL:\n\n`y`\n\nallow this one invocation`n`\n\ndeny this one invocation`a`\n\nalways allow this exact target (writes a regex to`permissions.json`\n\n)`s`\n\nallow this tool for the rest of the session (not persisted)\n\nYou can pre-seed `allowed_patterns`\n\nin `config.json`\n\n:\n\n```\n{\"tool\": \"Bash\", \"mode\": \"ask\", \"allowed_patterns\": [\"^git status$\", \"^ls( |$)\"]}\n```\n\nAnything that needs to survive rolling-window eviction goes here.\n\n- The agent calls the\n`WriteMemory`\n\ntool with`text`\n\nand an optional`category`\n\n. - You call\n`/note <text>`\n\nin the REPL or`mnemara note --instance <name> <text>`\n\nfrom the shell.\n\nFormat: append-only Markdown, one block per note:\n\n```\n## [2026-04-27T18:32:01+00:00] insight\n\nWorth remembering across sessions.\n```\n\nSome state is neither a memory note (write-once, append-only) nor a turn\nin the rolling window (FIFO-evicted as the conversation grows). It's a\nsingle value that *changes in place* — current health, current location,\ncurrent mood, the latest reading from a sensor, the value of a counter\nthat ticks every frame.\n\nMnemara exposes a `upsert_slot`\n\nMCP tool for exactly that:\n\n``` php\nupsert_slot(label, role, content) -> row_id\n```\n\n- If a row with\n`pin_label == label`\n\nalready exists, the row is**updated in place**— same`row_id`\n\n, content replaced, timestamp refreshed. - If no such row exists, a new pinned row is\n**inserted**.\n\nSlot rows are pinned. They never get FIFO-evicted from the rolling window,\nno matter how long the session runs. The context viewer shows them with a\n`📌 [slot: <label>]`\n\nbadge so you can see the current state of the world\nat a glance.\n\n**Why this matters:** without `upsert_slot`\n\n, an agent with mutable state\n(a game character with HP, a robot reporting battery level, a panel\ntracking the latest deploy status) has two bad options. Either it\n`append_turn`\n\nevery tick — and the rolling window fills up with stale\nstate-snapshots — or it relies on memory files, which require an extra\nread step and aren't always in context. Slots solve both: in context every\nturn, mutable in place, never bloating the window.\n\n**Game-character example.** A character has health, stamina, and hunger.\nEach combat tick, the simulator updates the slots:\n\n```\nupsert_slot(\"health\",   \"system\", \"HP: 73 / 100\")\nupsert_slot(\"stamina\",  \"system\", \"stamina: 41 / 100\")\nupsert_slot(\"hunger\",   \"system\", \"hunger: 62 / 100  (eat soon)\")\n```\n\nThe character's panel sees current values at slot 0..N every turn. No\nstate-resolution math, no ECS, no separate game-state DB read into\ncontext — the context *is* the state. Tick the slot, the next agent turn\nreads the new value.\n\nCombine with role-doc swap for state that changes the agent's *identity*\nrather than its inventory: pointing `role_doc_path`\n\nat `drunk.md`\n\nfor the\nduration of an inebriation status doesn't add a debuff, it makes the\nagent run as a drunk person until the condition lifts.\n\nThe rolling window's row + token caps are the *floor* of context\ndiscipline. The agent itself has access to a set of in-process tools\nthat let it compact its own history mid-session — useful for long\nsessions where most of the context is bulky tool-use audit data the\nmodel no longer needs.\n\n| Tool | What it does |\n|---|---|\n`evict_thinking_blocks` |\nStrips `thinking` blocks from selected rows while preserving text + tool_use. Cheap, low-risk. |\n`evict_tool_use_blocks` |\nStrips `tool_use` spec bodies (file paths, command strings, edit before/after content) from rows while preserving the audit shell. Often the highest-impact intervention — tool_use specs frequently dominate stored bytes in long sessions. |\n`evict_write_pairs` |\nStubs the bulky body content of Edit/Write/MultiEdit tool calls and their paired prior Read calls for the same file path. Audit trail intact (\"I edited /foo/bar.py\"); the kilobytes-per-block strings collapse to `{file_path, _evicted: true}` . |\n\nConcrete: an `Edit`\n\ntool call with old_string + new_string commonly\ncarries 1–5 KB of inline content. A `Write`\n\ncall with full file body\nis often much more. Multiplied across a long session, that becomes\nthe majority of stored bytes. The actual change persists on disk; the\nin-context audit body doesn't need to.\n\nThe agent decides when to call these. The role doc is the right place\nto encode the policy (\"when the rolling window is more than 80% full,\ncall `evict_write_pairs`\n\non completed edit turns before doing more\nwork\").\n\nThere is also an opt-in **auto-evict-after-write** config flag\n(`auto_evict_after_write: true`\n\nin `config.json`\n\n) that runs\n`evict_write_pairs`\n\nautomatically after any turn that contained an\nedit/write tool call. Off by default; opt in per instance if you've\ndecided that's the policy you want.\n\nWhen `mcp_servers`\n\nare configured, Mnemara hands each entry to the Claude\nAgent SDK as a `type: \"stdio\"`\n\nserver. The SDK spawns the subprocess,\nnegotiates the MCP handshake, exposes the available tools to the model,\nand dispatches calls back to the server — Mnemara's permission policy\nstill gates every tool call before it leaves the process.\n\nThe `mcp__<server>__<tool>`\n\nnamespacing convention is owned by the Claude\nCLI that the SDK drives as a subprocess — the CLI exposes registered MCP\ntools to the model under that prefix and pattern-matches `--allowedTools`\n\nentries in the same shape. The SDK passes `allowed_tools`\n\nthrough verbatim;\nMnemara conforms to the convention rather than defining it, and auto-allows\nthe full `mcp__<server>__*`\n\nnamespace for every configured server. You can\noverride that with a more specific entry in `allowed_tools`\n\nif you want to\ngate individual tools:\n\n```\n\"allowed_tools\": [\n  {\"tool\": \"mcp__fetch__fetch\", \"mode\": \"ask\", \"allowed_patterns\": []}\n]\n```\n\nAdd servers in `config.json`\n\n:\n\n```\n\"mcp_servers\": [\n  {\n    \"name\": \"myserver\",\n    \"command\": \"/usr/local/bin/my-mcp-server\",\n    \"args\": [],\n    \"env\": {}\n  }\n]\n```\n\n**Example — add web fetch (no extra install step):**\n\n```\n\"mcp_servers\": [\n  {\n    \"name\": \"fetch\",\n    \"command\": \"uvx\",\n    \"args\": [\"mcp-server-fetch\"],\n    \"env\": {}\n  }\n]\n```\n\n`uvx`\n\ndownloads and caches `mcp-server-fetch`\n\non first use; no separate\ninstall. The model can then call `mcp__fetch__fetch`\n\nwith `{\"url\": \"...\"}`\n\n.\n\nMCP server crashes are logged to `debug.log`\n\n(and the server's own stderr).\nAs a fallback, remove the entry from `mcp_servers`\n\nand restart the session.\n\nTwo co-evolving features. The graph captures relational structure between memories and entities; replay exploits that structure on each consolidation pass.\n\n**Graph backend** — `graph/`\n\ndirectory holding a [Kuzu](https://kuzudb.com/)\nproperty graph. Two tables: `Node(id, label, properties JSON, created_at)`\n\nand `Edge(FROM Node TO Node, id, relationship, properties JSON, created_at)`\n\n.\nSix tools registered:\n\n``` php\ngraph_add_node(label, properties_json) -> id\ngraph_add_edge(from_id, to_id, relationship, properties_json) -> id\ngraph_query(cypher) -> rows\ngraph_neighbors(node_id, depth=1) -> adjacent nodes\ngraph_match(pattern_json) -> nodes matching {label, properties_subset}\ngraph_shortest_path(from_id, to_id) -> list of node ids\n```\n\nAuto-edge hooks fire on every `write_memory`\n\n(with structured `applies_to`\n\n)\nand `wiki_write`\n\n(frontmatter `tags:`\n\n). All wrapped in try/except — graph\nfailure never fails the primary write.\n\nLazy: Kuzu is not opened until the first graph tool call. If Kuzu is absent\nor the DB is corrupt, every tool returns\n`{\"ok\": false, \"error\": \"Graph backend unavailable: …\"}`\n\nand the rest of the\nsystem keeps working. Off-switch: `graph_enabled: false`\n\nin config.\n\n**Sleep / replay primitive** — `mnemara replay --instance <name>`\n\n. Seven\nphases:\n\n- Load atoms from\n`memory/*.md`\n\nover the last`--days`\n\n(default 7). - Cluster atoms via RAG similarity. Atoms within distance 0.35 cluster;\n`--threshold`\n\n(default 3) sets the minimum count to count as a pattern. - Augment patterns with graph structure — frequently-co-occurring entities\nfrom\n`applies_to`\n\nedges; causal phrasing in member text. - For patterns not already covered by an existing wiki page, draft a\nproposal at\n`wiki_proposals/<slug>.md`\n\n. - Archive near-duplicate atoms (distance < 0.10) into\n`memory/archive/`\n\n.**Never deletes.** - When\n`self_observation`\n\natoms cluster, draft a role-amendment proposal at`role_proposals/<ts>_replay-<slug>.md`\n\n. - Write a sleep digest at\n`sleep/YYYY-MM-DD.md`\n\nwith counts and pointers.\n\nDefault behavior is dry-run. Pass `--apply`\n\nto actually write proposals,\narchive duplicates, and emit the digest.\n\nThree memory surfaces write together. The agent picks which surface to read from given the kind of recall it needs.\n\n**Memory file** — `memory/YYYY-MM-DD.md`\n\n. Append-only, chronological.\n\n**Wiki** — `wiki/<slug>.md`\n\n. Slash-allowed slugs (e.g. `replay_policy`\n\n,\n`patterns/loader_traps`\n\n). Plain markdown, optional frontmatter, no schema.\n\n```\nwiki_read(path)\nwiki_write(path, content, mode='replace')  # 'replace'|'append'\nwiki_list(prefix='')\n```\n\n**RAG** — `index/`\n\n(LanceDB), embeddings via Ollama `nomic-embed-text`\n\n(768-dim).\n\n```\nrag_index(text, kind='manual', source_path='', category='')\nrag_query(question, k=5, kind=None)\n```\n\n**Write-to-all consolidation:** every `write_memory`\n\ncall also `rag_index`\n\nes\nthe content. Every `wiki_write`\n\nalso indexes itself. If `category`\n\nstarts\nwith `wiki/`\n\n, `write_memory`\n\nALSO writes the body to `wiki/<rest>.md`\n\n.\n\n**Setup for RAG:**\n\n```\nollama pull nomic-embed-text  # one-time; ~270MB\n# Ollama must be running on http://localhost:11434\n```\n\nIf Ollama is unreachable or LanceDB import fails, RAG tools return\n`\"RAG backend unavailable: <reason>\"`\n\nand memory + wiki keep working.\n\nMnemara is a thin runtime around the Claude Agent SDK. The SDK runs the model and its native tools (Bash/Read/Edit/Write); Mnemara owns:\n\n- The persistent turn store (\n`turns.sqlite`\n\n). - The role doc, re-read every call as\n`system_prompt`\n\n. - The rolling-window transcript serialized into each turn's prompt\n(the SDK is stateless per\n`query()`\n\n). - The permission policy (mediated via the SDK's\n`can_use_tool`\n\ncallback). - The memory/wiki/RAG/graph backends and the\n`replay`\n\nconsolidation pass.\n\nMnemara is **per-instance**: one role doc, one rolling window, one config,\none set of files under `~/.mnemara/<instance>/`\n\n. That's deliberate.\n\nIf you want to run **multiple Mnemara instances with shared coordination**\n— a producer panel handing tasks to engineer panels, a watchdog instance\nmonitoring others, a researcher and a writer running side-by-side — that's\na multi-agent orchestration layer that lives *above* Mnemara, not inside\nit. Mnemara is the per-instance runtime each panel runs on; the harness\nthat spawns, coordinates, and arbitrates between panels is a separate\nconcern.\n\nWe don't ship that orchestration harness publicly. The reason is design,\nnot omission: a generic multi-agent harness has too many opinions\n(scheduling? message-passing? leader election? failure recovery?) to be\nuseful as one-size-fits-all. Build your own thin wrapper around the\n[programmatic-use surface](#programmatic-use) — Mnemara is small enough\nthat \"spawn N `AgentSession`\n\ns and route messages between them\" is real\ncode you can write in an afternoon for the specific shape of orchestration\nyour project needs.\n\nMnemara can poll a SQLite peer-message database on a background timer and deliver incoming messages as agent turns — enabling autonomous coordination between multiple running Mnemara panels without human relay.\n\n**The feature is disabled by default.** Enable it by setting\n`peer_poll_enabled = true`\n\nin your instance config.\n\nYour peer-message SQLite file must expose a table called `returns`\n\nwith at\nleast these columns:\n\n| Column | Type | Notes |\n|---|---|---|\n`id` |\nINTEGER PK AUTOINCREMENT | watermark anchor |\n`agent_role` |\nTEXT | sender identity string |\n`recipient_role` |\nTEXT or NULL | NULL = broadcast to all watchers |\n`task_id` |\nTEXT or NULL | optional topic label |\n`payload_json` |\nTEXT | JSON-encoded message body |\n`status` |\nTEXT | `'pending'` for new rows, `'done'` after ack |\n`submitted_at` |\nTEXT | ISO timestamp |\n\nMnemara reads `pending`\n\nrows matching the configured sender roles or\nexplicitly addressed to this panel, delivers them as a single batched agent\nturn, and writes `status='done'`\n\n+ `completed_at`\n\nwhen silently auto-acking\nprotocol-noise messages.\n\n```\n{\n  \"peer_poll_enabled\": true,\n  \"peer_db_path\": \"/path/to/peer_messages.db\",\n  \"peer_poll_roles\": \"panel-a,panel-b\",\n  \"peer_poll_interval_seconds\": 30,\n  \"peer_poll_ack_tool\": \"my_server__ack_message\",\n  \"peer_poll_submit_tool\": \"my_server__send_message\"\n}\n```\n\n`peer_poll_ack_tool`\n\nand `peer_poll_submit_tool`\n\nare the MCP tool names your\npeer-message system exposes. When empty (the default), the injected turn\ninstruction uses generic prose describing what the agent should do; set them\nto use specific tool names so the agent can call them directly.\n\n**Detection**(every`peer_poll_interval_seconds`\n\n): pure SQLite read, zero token cost.**Processing**(when next idle): all pending rows in one batched LLM turn — N messages = 1 API call.: turn-by-turn mode for small/local models or large-context peer messages.`peer_poll_batch: false`\n\n: toggle delivery live from the TUI without restarting the panel.`[⚡ Inbox: ON/OFF]`\n\nbutton**Silent auto-ack**: protocol-noise message types (configurable via`peer_poll_silent_types`\n\n) are acknowledged without an LLM turn.\n\nThis is in-process panel coordination over a shared SQLite file, not a distributed message bus. The mechanics matter, especially for autonomous panels with no human-in-the-loop to catch coordination bugs.\n\n**Make a peer-message send the terminal action of an agent turn.**\nSending is asynchronous — the recipient doesn't see your message until\ntheir next poll, which can be tens of seconds later. An agent that\nsends mid-turn and keeps working is either doing prep that could have\nhappened *before* the send, or speculating about a reply that hasn't\nbeen authored yet. The clean discipline:\n\nVerify, prepare, send, stop.When an agent pings a peer, the send should be the last tool block of the turn. The agent then yields control and waits for the reply to arrive on a future turn.\n\nEncode this rule in the role doc that drives any panel using peer messaging — autonomous loops without it tend to compound work on half-stale state.\n\n**Drain inbound messages in batches, not one-at-a-time.** When several\npending rows are addressed to a panel, ack-all and reply-all in the\nsame terminal block. Splitting an inbox across N turns fragments\ncontext and burns tokens for no benefit.\n\n**Always filter polls by recipient_role.** Without the filter,\nmultiple watchers race for every pending row — including messages\nintended for other panels. With the filter, each panel reads only\nwhat's addressed to it. Race-free.\n\n**Crossed messages are normal; design for them.** Two panels can\nauthor replies that cross in flight. A reply you receive may have been\nauthored without visibility into your most recent message. When a\nreply looks stale, check timestamps and referenced row IDs before\nacting on it. Sometimes the right move is to flag the cross and\nre-synchronize; sometimes it's to drop a confirmation-of-confirmation\nthat carries no new information.\n\n**Acknowledge what you process. Don't acknowledge acknowledgments.**\nUnacked rows accumulate and slow the queue. But \"I received your\nacknowledgment\" is noise that, if both sides send it, becomes an\ninfinite ping-pong loop. Reply only when the reply carries new content.\n\n**Local-only by design.** This system has no authentication, no\nencryption, no rate limiting, and no audit log. The SQLite file should\nlive on the same machine as every panel reading from it. If you need\ncross-machine messaging, route through a real message bus —\npeer_poll is not a substitute.\n\n**The schema is part of your contract.** If you change the `returns`\n\ntable shape, every panel reading from it must be updated in lockstep.\nTreat schema changes the way you'd treat changes to a public API.\n\nThe CLI is the primary surface, but Mnemara is also a regular Python\nlibrary you can embed in your own code. See\n[ examples/programmatic_use.py](/mekickdemons-creator/mnemara/blob/main/examples/programmatic_use.py) for a\nminimal embed: initializes an instance, configures a role doc, drives\na turn, and inspects the rolling window — about 60 lines.\n\n```\nANTHROPIC_API_KEY=sk-ant-... python examples/programmatic_use.py\n```\n\n**Auth errors**— confirm`ANTHROPIC_API_KEY`\n\nis set, or that`claude`\n\nCLI is installed and`claude login`\n\nhas been run as a fallback.**Role doc not loading**— Mnemara warns to stderr and uses an empty system prompt; the REPL stays alive. Check`debug.log`\n\nfor the path that failed.**MCP server crashes**— check`debug.log`\n\nand the server's own stderr. As a fallback, remove the entry from`mcp_servers`\n\n.**Window eviction surprises**—`mnemara show --instance <name>`\n\nprints the current window. The rolling window keeps the last`max_window_turns`\n\nrows; long tool-use turns count as one row but can carry many content blocks.**Token errors**— if a long role doc + window overruns the model context, drop`max_window_turns`\n\nor split the role doc.\n\n```\npip install -e \".[dev]\"\npytest -q tests/\n```\n\nTests do not call the network — they cover the store, config, permissions, and the file tools.\n\nMnemara is built by **Mimirwell** — we build AI agents that remember, govern\nthemselves, and don't run away with your bill. If you have an agent you want\nbuilt (or one that's already costing too much and remembering too little), we\nengage at any depth: a fixed-scope **consultation**, a **co-build** alongside\nyour team, or **done-for-you** end to end.\n\nThe proof is what you're already looking at, plus on-prem memory tooling and a\nhousehold of persistent, self-governing agents in daily use. Reach us at\n** mimir@mimirwell.dev**.\n\nBuilt by Michael Anderson and Mimir, a Claude-based agent.\n\nMIT. See [LICENSE](/mekickdemons-creator/mnemara/blob/main/LICENSE).\n\nClaude Aethon Autopoiesis 1.3.3.7", "url": "https://wpnews.pro/news/mnemara-a-memory-layer-that-keeps-claude-agents-continuous", "canonical_source": "https://github.com/mekickdemons-creator/mnemara", "published_at": "2026-08-10 12:26:38+00:00", "updated_at": "2026-08-10 12:43:21.963604+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "artificial-intelligence"], "entities": ["Mnemara", "Claude Agent SDK", "Anthropic", "Sentinel", "LanceDB", "Kuzu", "Textual TUI"], "alternates": {"html": "https://wpnews.pro/news/mnemara-a-memory-layer-that-keeps-claude-agents-continuous", "markdown": "https://wpnews.pro/news/mnemara-a-memory-layer-that-keeps-claude-agents-continuous.md", "text": "https://wpnews.pro/news/mnemara-a-memory-layer-that-keeps-claude-agents-continuous.txt", "jsonld": "https://wpnews.pro/news/mnemara-a-memory-layer-that-keeps-claude-agents-continuous.jsonld"}}