| 1 | # Memory in nanobot |
| 2 | |
| 3 | nanobot's memory is built on a simple belief: memory should feel alive, but it should not feel chaotic. |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | That is the shape of memory in nanobot. |
| 8 | |
| 9 | ## The Design |
| 10 | |
| 11 | nanobot does not treat memory as one giant file. |
| 12 | |
| 13 | It separates memory into layers, because different kinds of remembering deserve different tools: |
| 14 | |
| 15 | - `session.messages` holds the living short-term conversation. |
| 16 | - `memory/history.jsonl` is the running archive of compressed past turns. |
| 17 | - `SOUL.md`, `USER.md`, and `memory/MEMORY.md` are the durable knowledge files. |
| 18 | - `GitStore` records how those durable files change over time. |
| 19 | |
| 20 | This keeps the system light in the moment, but reflective over time. |
| 21 | |
| 22 | ## The Flow |
| 23 | |
| 24 | Memory moves through nanobot in two stages. |
| 25 | |
| 26 | ### Stage 1: Consolidator |
| 27 | |
| 28 | When a conversation grows large enough to pressure the context window, nanobot does not try to carry every old message forever. |
| 29 | |
| 30 | Instead, the `Consolidator` summarizes the oldest safe slice of the conversation and appends that summary to `memory/history.jsonl`. |
| 31 | |
| 32 | This file is: |
| 33 | |
| 34 | - append-only |
| 35 | - cursor-based |
| 36 | - optimized for machine consumption first, human inspection second |
| 37 | |
| 38 | Each line is a JSON object: |
| 39 | |
| 40 | ```json |
| 41 | {"cursor": 42, "timestamp": "2026-04-03 00:02", "content": "- User prefers dark mode\n- Decided to use PostgreSQL"} |
| 42 | ``` |
| 43 | |
| 44 | It is not the final memory. It is the material from which final memory is shaped. |
| 45 | |
| 46 | ### Stage 2: Dream |
| 47 | |
| 48 | `Dream` is the slower, more thoughtful layer. It runs on a cron schedule by default and can also be triggered manually. |
| 49 | |
| 50 | Dream reads: |
| 51 | |
| 52 | - new entries from `memory/history.jsonl` |
| 53 | - the current `SOUL.md` |
| 54 | - the current `USER.md` |
| 55 | - the current `memory/MEMORY.md` |
| 56 | |
| 57 | Then it works in two phases: |
| 58 | |
| 59 | 1. It studies what is new and what is already known. |
| 60 | 2. It edits the long-term files surgically, not by rewriting everything, but by making the smallest honest change that keeps memory coherent. |
| 61 | |
| 62 | This is why nanobot's memory is not just archival. It is interpretive. |
| 63 | |
| 64 | ## The Files |
| 65 | |
| 66 | ```text |
| 67 | workspace/ |
| 68 | ├── SOUL.md # The bot's long-term voice and communication style |
| 69 | ├── USER.md # Stable knowledge about the user |
| 70 | └── memory/ |
| 71 | ├── MEMORY.md # Project facts, decisions, and durable context |
| 72 | ├── history.jsonl # Append-only history summaries |
| 73 | ├── .cursor # Consolidator write cursor |
| 74 | ├── .dream_cursor # Dream consumption cursor |
| 75 | └── .git/ # Version history for long-term memory files |
| 76 | ``` |
| 77 | |
| 78 | These files play different roles: |
| 79 | |
| 80 | - `SOUL.md` remembers how nanobot should sound. |
| 81 | - `USER.md` remembers who the user is and what they prefer. |
| 82 | - `MEMORY.md` remembers what remains true about the work itself. |
| 83 | - `history.jsonl` remembers what happened on the way there. |
| 84 | |
| 85 | ## Why `history.jsonl` |
| 86 | |
| 87 | The old `HISTORY.md` format was pleasant for casual reading, but it was too fragile as an operational substrate. |
| 88 | |
| 89 | `history.jsonl` gives nanobot: |
| 90 | |
| 91 | - stable incremental cursors |
| 92 | - safer machine parsing |
| 93 | - easier batching |
| 94 | - cleaner migration and compaction |
| 95 | - a better boundary between raw history and curated knowledge |
| 96 | |
| 97 | You can still search it with familiar tools: |
| 98 | |
| 99 | ```bash |
| 100 | # grep |
| 101 | grep -i "keyword" memory/history.jsonl |
| 102 | |
| 103 | # jq |
| 104 | cat memory/history.jsonl | jq -r 'select(.content | test("keyword"; "i")) | .content' | tail -20 |
| 105 | |
| 106 | # Python |
| 107 | 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:]" |
| 108 | ``` |
| 109 | |
| 110 | The difference is philosophical as much as technical: |
| 111 | |
| 112 | - `history.jsonl` is for structure |
| 113 | - `SOUL.md`, `USER.md`, and `MEMORY.md` are for meaning |
| 114 | |
| 115 | ## Commands |
| 116 | |
| 117 | Memory is not hidden behind the curtain. Users can inspect and guide it. |
| 118 | |
| 119 | | Command | What it does | |
| 120 | |---------|--------------| |
| 121 | | `/dream` | Run Dream immediately | |
| 122 | | `/dream-log` | Show the latest Dream memory change | |
| 123 | | `/dream-log <sha>` | Show a specific Dream change | |
| 124 | | `/dream-restore` | List recent Dream memory versions | |
| 125 | | `/dream-restore <sha>` | Restore memory to the state before a specific change | |
| 126 | |
| 127 | These commands exist for a reason: automatic memory is powerful, but users should always retain the right to inspect, understand, and restore it. |
| 128 | |
| 129 | ## Versioned Memory |
| 130 | |
| 131 | After Dream changes long-term memory files, nanobot can record that change with `GitStore`. |
| 132 | |
| 133 | This gives memory a history of its own: |
| 134 | |
| 135 | - you can inspect what changed |
| 136 | - you can compare versions |
| 137 | - you can restore a previous state |
| 138 | |
| 139 | That turns memory from a silent mutation into an auditable process. |
| 140 | |
| 141 | ## Configuration |
| 142 | |
| 143 | Dream is configured under `agents.defaults.dream`: |
| 144 | |
| 145 | ```json |
| 146 | { |
| 147 | "agents": { |
| 148 | "defaults": { |
| 149 | "dream": { |
| 150 | "intervalH": 2, |
| 151 | "modelOverride": null, |
| 152 | "maxBatchSize": 20, |
| 153 | "maxIterations": 10 |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | ``` |
| 159 | |
| 160 | | Field | Meaning | |
| 161 | |-------|---------| |
| 162 | | `intervalH` | How often Dream runs, in hours | |
| 163 | | `modelOverride` | Optional Dream-specific model override | |
| 164 | | `maxBatchSize` | How many history entries Dream processes per run | |
| 165 | | `maxIterations` | The tool budget for Dream's editing phase | |
| 166 | |
| 167 | In practical terms: |
| 168 | |
| 169 | - `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. |
| 170 | - `maxBatchSize` controls how many new `history.jsonl` entries Dream consumes in one run. Larger batches catch up faster; smaller batches are lighter and steadier. |
| 171 | - `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. |
| 172 | - `intervalH` is the normal way to configure Dream. Internally it runs as an `every` schedule, not as a cron expression. |
| 173 | |
| 174 | Legacy note: |
| 175 | |
| 176 | - Older source-based configs may still contain `dream.cron`. nanobot continues to honor it for backward compatibility, but new configs should use `intervalH`. |
| 177 | - Older source-based configs may still contain `dream.model`. nanobot continues to honor it for backward compatibility, but new configs should use `modelOverride`. |
| 178 | |
| 179 | ## In Practice |
| 180 | |
| 181 | What this means in daily use is simple: |
| 182 | |
| 183 | - conversations can stay fast without carrying infinite context |
| 184 | - durable facts can become clearer over time instead of noisier |
| 185 | - the user can inspect and restore memory when needed |
| 186 | |
| 187 | Memory should not feel like a dump. It should feel like continuity. |
| 188 | |
| 189 | That is what this design is trying to protect. |
| 190 |