Minimal agent with forgetting (500 lines).
Agents today are bad at forgetting, and worse at remembering that they forgot. Forgetting is a good thing actually. Evolution didn't select for photographic memory in humans for a reason: when you're planning a trip to Japan and you're picking a seat on the flight, you don't need sushi restaurants in your head — and later, when you're planning meals, you can rehydrate that context on demand. Dory does the same: old context gets compacted — details fade but the gist remains, and the original is always one UNCOMPACT away.
agent.py — the loop
SOUL.md — purpose + tool format (immutable)
SEGMENT.md — chunking instructions for compactor (agent-editable)
SUMMARISE.md — summarisation instructions for compactor (agent-editable)
blobs/ — content-addressed blob store (shared)
chats/<id>.md — chat rooms (one file per room, append-only)
agents/<self>/ — per-identity dir; <self> = hash(SOUL + boot ts)
├ LIFE.md — append-only event log (every turn, every tool)
├ MEMORY.md — agent-editable working notes (capped at 4000ch)
├ context.md — last live context (resume point)
└ subs/ — chat subscriptions (symlinks into chats/)
-
SOUL / LIFE / MEMORY. SOUL is the agent's purpose (immutable). LIFE is the append-only event log, per-identity; the last 50 lines are injected each turn. MEMORY is the agent's own scratchpad — learned preferences and notes it edits with
EDIT_FILE. -
SEGMENT / SUMMARISE. Instructions for the compactor, stashed as blob refs in the agent's prompt (so they don't bloat it). The agent can UNCOMPACT or READ_FILE them, and even edit them to tune its own compaction strategy.
-
Prompt order. Each turn: `SOUL + segment_ref + summarise_ref
- harness + context + MEMORY + LIFE`. Stable prefix first, append-only context next, volatile tail last — optimised for LLM prompt caching.
-
Blobs are the substrate. Every LLM response and every tool result is stashed as a blob under
blobs/; the ref is appended toLIFE.md. Blobs are content-addressed (filename = sha256 prefix of the blob) and have a tiny frontmatter:at,gist,parent. Live memory holds content inline for free access; older content may only appear as a◱hash=... gist=...◲reference after compaction. -
Chunked compaction. When context exceeds the limit, compaction runs in the background. Phase 1: an LLM call identifies conceptual chunks in the first 2/3 of context. Phase 2: each chunk is compacted in parallel — the compactor sees the full agent state (soul, harness, memory, life) and returns a summary, a relevance score (1–10), and a gist. Chunks are swapped in from least to most relevant until eligible content is compressed to 50%. The recent 1/3 of context is never touched. Originals are stashed as blobs with LLM-generated gists;
UNCOMPACTrehydrates any ref inline. -
Graceful forgetting. Compaction is semantic, not truncation. Old turns lose resolution but stay reachable via blob refs. The agent can
UNCOMPACTany ref to pull it back into context. -
Chat = file. Each room is
chats/<id>.md, append-only. Lines are tagged[USER:name ts]or[AGENT:name ts]. Agents subscribe by symlinking files intoagents/<name>/; the harness tails that dir each turn.SAY <id> <msg>appends to the right file. External transports (Telegram, stdin, etc.) are independent bridge processes that read/write the same files — the agent doesn't know or care.
pip install -r requirements.txt
export DEEPSEEK_API_KEY=...
python agent.py # fresh boot: new identity
python agent.py <hash> # resume an existing agent by its <self> hashDefaults to deepseek-v4-pro via api.deepseek.com. Reasoning
content is captured and prepended to context inside <reasoning> tags.
Pass --verbose to print LIFE events to stdout.
Each fresh boot generates a new content-addressed identity: the agent
stashes a blob of its SOUL plus boot timestamp, and uses that hash as
its name. Two agents with the same SOUL booted at different times get
distinct identities; their state lives under agents/<self>/.
Edit SOUL.md to give the agent a purpose before running. Talk to a
running agent with python chat.py [room_id] (defaults to room
dev). Wipe runtime state with python clean.py.
