Sharing a project I've been building on top of the Claude Agent SDK in case
it's useful to anyone here. Curious about feedback from people running into
the same failure modes.
The thing I actually wanted to figure out was: where do you put rules that
keep an agent from looping, drifting, or sycophantically reversing? The
SDK gives you tool definitions, can_use_tool, system_prompt, etc. — but
once a session starts, the only surface that's guaranteed to apply on
every turn is the system prompt itself. Anything you put in user
messages gets evicted as the conversation grows.
So Mnemara treats the system_prompt as the steering layer it actually is:
- It's a Markdown file on disk (the "role doc"), re-read on every API call
- Re-read means you can edit it mid-session and the next turn picks it up
- That makes it the right place to encode self-monitoring rules: "if you notice you've called the same tool 3 times with no progress, stop and ask the user" — applied every turn, not just the opening one.
The repo ships examples/roles/sentinel.md as a working example: detects
timeout / polling / semantic drift / sycophantic reversal and makes the
agent halt to ask the user instead of spending another N turns spiraling.
Where most of the engineering went is context management on both ends
of the prompt:
- Front of prompt — the role doc is reloaded from disk every call and pinned at slot 0. Strongest steering signal you have, and edits to the file take effect on the next turn with no restart.
- Back of prompt — a rolling-window store (turns.sqlite) FIFO-trimmed by both row count and token budget. The window is serialized into each query() prompt, since the SDK is stateless per query.
- Middle of prompt — the part that actually saves money — opt-in block surgery for completed turns. After a turn that contained Edit/Write/MultiEdit/NotebookEdit tool_use blocks, the bulky body content (old_string/new_string/full file contents — often 1–5KB per Edit, far more on Write) gets stubbed in the stored row. The block itself is preserved as audit trail ("I edited /foo/bar.py") but collapses to {file_path, _evicted: true}. The actual change persists on disk; only the in-context audit body goes. Same idea for paired Read tool_use specs on the same path. In long sessions tool_use specs often dominate stored bytes — stripping them is the highest-impact context budget intervention I've found.
The agent also has access to the surgery tools directly (evict_thinking_
blocks, evict_tool_use_blocks, evict_write_pairs) so it can decide when
to compact its own history — "primitive stays clean, agent decides
when."
Around that, the runtime adds:
- can_use_tool routed through a per-instance permissions.json with allow/ask/deny modes and a regex allowlist.
- An in-process WriteMemory tool registered as an SDK MCP server.
- Per-instance file-only state under ~/.mnemara// — turns.sqlite, config.json, memory/*.md. Editable, greppable, no daemon.
- Optional MCP wire-through, LanceDB RAG, Kuzu property graph backends, and a sleep/replay consolidation pass over recent memory atoms.
MIT.
pip install mnemara
https://github.com/mekickdemons-creator/mnemara
A few things I'd genuinely like input on if anyone has thoughts:
- Has anyone tried encoding similar self-monitoring rules in their own role docs? What worked, what didn't?
- The rolling-window-as-prompt-prefix pattern works but feels like a workaround for query() being stateless. Is there a more idiomatic way I'm missing?
- The block-surgery tools — anyone else compacting tool_use audit bodies to save context budget, or is everyone just letting the SDK's compaction handle it?
Top comments (0)