docs/MEMORY.md
Note: This design is currently an experiment in the latest source code version and is planned to officially ship in
v0.1.5.
nanobot's memory is built on a simple belief: memory should feel alive, but it should not feel chaotic.
Good memory is not a pile of notes. It is a quiet system of attention. It notices what is worth keeping, lets go of what no longer needs the spotlight, and turns lived experience into something calm, durable, and useful.
That is the shape of memory in nanobot.
nanobot does not treat memory as one giant file.
It separates memory into layers, because different kinds of remembering deserve different tools:
session.messages holds the living short-term conversation.memory/history.jsonl is the running archive of compressed past turns.SOUL.md, USER.md, and memory/MEMORY.md are the durable knowledge files.GitStore records how those durable files change over time.This keeps the system light in the moment, but reflective over time.
Memory moves through nanobot in two stages.
When a conversation grows large enough to pressure the context window, nanobot does not try to carry every old message forever.
Instead, the Consolidator summarizes the oldest safe slice of the conversation and appends that summary to memory/history.jsonl.
This file is:
Each line is a JSON object:
{"cursor": 42, "timestamp": "2026-04-03 00:02", "content": "- User prefers dark mode\n- Decided to use PostgreSQL"}
It is not the final memory. It is the material from which final memory is shaped.
Dream is the slower, more thoughtful layer. It runs on a cron schedule by default and can also be triggered manually.
Dream reads:
memory/history.jsonlSOUL.mdUSER.mdmemory/MEMORY.mdThen it works in two phases:
This is why nanobot's memory is not just archival. It is interpretive.
workspace/
├── SOUL.md # The bot's long-term voice and communication style
├── USER.md # Stable knowledge about the user
└── memory/
├── MEMORY.md # Project facts, decisions, and durable context
├── history.jsonl # Append-only history summaries
├── .cursor # Consolidator write cursor
├── .dream_cursor # Dream consumption cursor
└── .git/ # Version history for long-term memory files
These files play different roles:
SOUL.md remembers how nanobot should sound.USER.md remembers who the user is and what they prefer.MEMORY.md remembers what remains true about the work itself.history.jsonl remembers what happened on the way there.history.jsonlThe old HISTORY.md format was pleasant for casual reading, but it was too fragile as an operational substrate.
history.jsonl gives nanobot:
You can still search it with familiar tools:
# grep
grep -i "keyword" memory/history.jsonl
# jq
cat memory/history.jsonl | jq -r 'select(.content | test("keyword"; "i")) | .content' | tail -20
# Python
python -c "import json; [print(json.loads(l).get('content','')) for l in open('memory/history.jsonl','r',encoding='utf-8') if l.strip() and 'keyword' in l.lower()][-20:]"
The difference is philosophical as much as technical:
history.jsonl is for structureSOUL.md, USER.md, and MEMORY.md are for meaningMemory is not hidden behind the curtain. Users can inspect and guide it.
| Command | What it does |
|---|---|
/dream | Run Dream immediately |
/dream-log | Show the latest Dream memory change |
/dream-log <sha> | Show a specific Dream change |
/dream-restore | List recent Dream memory versions |
/dream-restore <sha> | Restore memory to the state before a specific change |
These commands exist for a reason: automatic memory is powerful, but users should always retain the right to inspect, understand, and restore it.
After Dream changes long-term memory files, nanobot can record that change with GitStore.
This gives memory a history of its own:
That turns memory from a silent mutation into an auditable process.
Dream is configured under agents.defaults.dream:
{
"agents": {
"defaults": {
"dream": {
"intervalH": 2,
"modelOverride": null,
"maxBatchSize": 20,
"maxIterations": 10
}
}
}
}
| Field | Meaning |
|---|---|
intervalH | How often Dream runs, in hours |
modelOverride | Optional Dream-specific model override |
maxBatchSize | How many history entries Dream processes per run |
maxIterations | The tool budget for Dream's editing phase |
In practical terms:
modelOverride: null means Dream uses the same model as the main agent. Set it only if you want Dream to run on a different model.maxBatchSize controls how many new history.jsonl entries Dream consumes in one run. Larger batches catch up faster; smaller batches are lighter and steadier.maxIterations limits how many read/edit steps Dream can take while updating SOUL.md, USER.md, and MEMORY.md. It is a safety budget, not a quality score.intervalH is the normal way to configure Dream. Internally it runs as an every schedule, not as a cron expression.Legacy note:
dream.cron. nanobot continues to honor it for backward compatibility, but new configs should use intervalH.dream.model. nanobot continues to honor it for backward compatibility, but new configs should use modelOverride.What this means in daily use is simple:
Memory should not feel like a dump. It should feel like continuity.
That is what this design is trying to protect.