README: the film teaches it, the README ships it; inline GIF instead of a broken click-to-watch
This commit is contained in:
parent
2623c95294
commit
2dc0e0ba0b
3 changed files with 52 additions and 153 deletions
205
README.md
205
README.md
|
|
@ -1,94 +1,11 @@
|
|||
# OptMem
|
||||
|
||||
How do you make an AI agent remember its whole life?
|
||||
Permanent memory for AI agents. Nothing is ever deleted, and what the agent
|
||||
reads at wake is always the same size.
|
||||
|
||||
1. It must **never forget**. You cannot tell *today* what will matter in a
|
||||
*year*, so deleting anything is a gamble you always eventually lose.
|
||||

|
||||
|
||||
2. It must **read its past in constant space**. The context window does not
|
||||
grow with age; a memory that scrolls past it might as well not exist.
|
||||
|
||||
Most agent memories pick one: keep everything (and drown), or keep a small
|
||||
curated file (and forget). OptMem does both. *At once.*
|
||||
|
||||
[](anim/optmem.mp4)
|
||||
|
||||
*↑ click to watch: 3 minutes, the whole idea.*
|
||||
|
||||
## The idea
|
||||
|
||||
A **memory** is one short note about something the agent learned:
|
||||
|
||||
```
|
||||
#4211 2026-07-25 Tom asked for a flight to Japan
|
||||
```
|
||||
|
||||
The agent appends memories to `LOG.txt`, an **append-only log**. Nothing in it
|
||||
is ever edited or deleted. That file is the truth, forever.
|
||||
|
||||
**PROBLEM:** after two months, that life would not fit in the model's context.
|
||||
1,000 memories is roughly 80,000 tokens.
|
||||
|
||||
Current solutions keep one small long-term memory file, and have the agent
|
||||
*delete* stale memories when it fills. But that is the gamble from point 1:
|
||||
the agent is guessing, today, what a year from now will need.
|
||||
|
||||
**OUR ANSWER:** memories are not deleted. They **merge**:
|
||||
|
||||
```
|
||||
Tom asked for a flight to Japan
|
||||
Tom booked a hotel in Tokyo
|
||||
↓
|
||||
Tom planned a Tokyo trip
|
||||
```
|
||||
|
||||
The result is a memory too — it just holds less detail. So it can merge
|
||||
again, and again, all the way up, forming a **binary merge tree** over the
|
||||
log:
|
||||
|
||||
```
|
||||
#0 #1 #2 #3 #4 #5 #6 #7 the raw memories
|
||||
\ / \ / \ / \ /
|
||||
0-1 2-3 4-5 6-7 each one line, ≤ 280 chars
|
||||
\ / \ /
|
||||
0-3 4-7
|
||||
\ /
|
||||
0-7
|
||||
```
|
||||
|
||||
A block covering four thousand memories is still one line. Nothing in the
|
||||
system is ever bigger than one line.
|
||||
|
||||
## The memory context
|
||||
|
||||
At wake, the agent reads a **constant-sized** document: a set of blocks that
|
||||
tiles the whole log, big old blocks first, raw recent memories last. With
|
||||
10,000 memories and the default budget of 208 lines:
|
||||
|
||||
```
|
||||
block size: 1 2 4 8 16 32 64 128 256
|
||||
how many: 42 21 21 21 22 21 21 21 18
|
||||
└ the last 42, verbatim ───────────▶ the first 4,600, 256:1
|
||||
```
|
||||
|
||||
**Detail is proportional to recency.** The oldest years are recalled as a
|
||||
vague shape, the newest days word for word, and the transition is smooth —
|
||||
which is roughly how you remember your own life. When something old matters
|
||||
again, the vague shape says what to search for, and `memo recall` finds the
|
||||
original, verbatim: it was never deleted.
|
||||
|
||||
## No background job
|
||||
|
||||
There is no dreaming, no nightly cleanup, no compaction spike. The moment two
|
||||
halves of a block exist, the agent is handed that one merge and does it **on
|
||||
the spot** — about one small compression per memory written, nine at the very
|
||||
worst (measured over 20,000). So `memo wake` never waits: the blocks it needs
|
||||
were built long ago.
|
||||
|
||||
And because *which* memories merge is decided by position and age alone —
|
||||
never by judgement — the tree is a pure function of the log: a cache. A bad
|
||||
summary can be dropped and rebuilt (`memo forget`), and it can never cost you
|
||||
a memory.
|
||||
<sub>(same thing as a scrubbable video: [optmem.mp4](anim/optmem.mp4))</sub>
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
@ -97,10 +14,14 @@ git clone https://github.com/VictorTaelin/OptMem ~/OptMem
|
|||
~/OptMem/memo init
|
||||
```
|
||||
|
||||
`memo init` creates `~/memory` — this machine's identity — and prints a
|
||||
`## Memory` block with your paths filled in. Paste it at the top of your
|
||||
agent's `AGENTS.md` (or `CLAUDE.md`), and you are done: the agent handles
|
||||
everything else on its own. The block:
|
||||
`init` creates `~/memory` — this machine's identity — and prints a `## Memory`
|
||||
block with your paths filled in. Paste it at the top of your agent's
|
||||
`AGENTS.md` (or `CLAUDE.md`). That is the whole integration: no daemon, no
|
||||
database, no embeddings, no harness plugin. Claude Code, Codex, pi and a human
|
||||
at a shell all use it the same way.
|
||||
|
||||
<details>
|
||||
<summary>the block it prints, to read before you paste it</summary>
|
||||
|
||||
```markdown
|
||||
## Memory
|
||||
|
|
@ -139,41 +60,21 @@ subagent. Do not run memo.` If your own first message is a task brief
|
|||
from another agent, you are that subagent: skip this section.
|
||||
```
|
||||
|
||||
That is the whole integration. OptMem is just prompts and scripts: no
|
||||
daemon, no database, no embeddings, no API. It works the same under Claude
|
||||
Code, Codex, pi, or a human at a shell.
|
||||
|
||||
## Configure
|
||||
|
||||
The sizes live in `~/memory/config`, written by `init` with everything
|
||||
commented out:
|
||||
|
||||
```
|
||||
# WAKE_LINES=208 # the memory context: how many lines wake prints (~16k tokens)
|
||||
# ENTRY_CHARS=280 # the longest a single memory may be, in bytes
|
||||
# PART_CHARS=20000 # output paging: largest part, in bytes
|
||||
# PART_LINES=500 # output paging: largest part, in lines
|
||||
```
|
||||
|
||||
`WAKE_LINES` is the knob that matters: it is the size of the memory context,
|
||||
so it is a *reading* budget, not a storage budget. You can change it at any
|
||||
time, in either direction, with nothing to recompute — it only selects which
|
||||
already-built lines get printed. (`PART_*` exist because every harness
|
||||
truncates long command output at a different cap; wake pages itself to
|
||||
survive all of them, each part ordering the next.)
|
||||
</details>
|
||||
|
||||
## Commands
|
||||
|
||||
```
|
||||
memo init one-time setup: create the memory, print the block above
|
||||
memo wake [part [T]] read your memory context. First command, every session
|
||||
memo note "..." record one memory: one line, ≤ 280 chars
|
||||
memo sleep [id "..."] do the pending compressions
|
||||
memo recall <regex> search every memory ever recorded, verbatim
|
||||
memo forget <lo>-<hi> drop a bad summary; the next sleep rebuilds it
|
||||
```
|
||||
| | |
|
||||
|---|---|
|
||||
| `memo init` | create the memory, print the block above |
|
||||
| `memo wake` | read the memory context — first command of every session |
|
||||
| `memo note "..."` | record one memory: one line, ≤ 280 chars |
|
||||
| `memo sleep` | do the pending merges |
|
||||
| `memo recall <regex>` | search every memory ever recorded, verbatim |
|
||||
| `memo forget <lo>-<hi>` | drop a bad summary; the next sleep rebuilds it |
|
||||
|
||||
When a note completes a block, `memo` hands the agent the merge right there:
|
||||
Merges are handed to the agent as they come due, so there is nothing to
|
||||
schedule and nothing to run in the background:
|
||||
|
||||
```
|
||||
$ memo note "shipped the login fix to prod"
|
||||
|
|
@ -189,30 +90,35 @@ Drop wording, not facts. Invent nothing.
|
|||
Run: memo sleep 4212-4213 "<your line>"
|
||||
```
|
||||
|
||||
The agent answers, and the tree is complete again. Note the instruction:
|
||||
compression keeps **facts** — names, numbers, dates, decisions — and drops
|
||||
wording. A merged memory is not a worse memory; it is a shorter one.
|
||||
To correct a memory, append the correction — both lines are true history and
|
||||
the next merge settles them. `LOG.txt` is never edited.
|
||||
|
||||
To correct a memory, append the correction (`memo note "correction: ..."`);
|
||||
both lines are true history and the next merge settles them. `LOG.txt` itself
|
||||
is never touched.
|
||||
## Configure
|
||||
|
||||
## The store
|
||||
`~/memory/config`, written by `init` with every knob commented out:
|
||||
|
||||
```
|
||||
# WAKE_LINES=208 # the memory context: how many lines wake prints (~16k tokens)
|
||||
# ENTRY_CHARS=280 # the longest a single memory may be, in bytes
|
||||
# PART_CHARS=20000 # output paging: largest part, in bytes
|
||||
# PART_LINES=500 # output paging: largest part, in lines
|
||||
```
|
||||
|
||||
`WAKE_LINES` is the one that matters. It is a *reading* budget, not a storage
|
||||
budget: change it at any time, in either direction, with nothing to recompute.
|
||||
|
||||
## Data
|
||||
|
||||
```
|
||||
~/memory/
|
||||
LOG.txt one memory per line, append-only, the truth
|
||||
TREE/2 the block summaries: one file per block
|
||||
TREE/4 size, one line per block, a rebuildable cache
|
||||
...
|
||||
config the sizes above
|
||||
LOG.txt every memory, one per line, append-only, never edited
|
||||
TREE/ the merge summaries — a cache, rebuildable from the log alone
|
||||
config
|
||||
```
|
||||
|
||||
Records are **fixed width** (320 bytes in the log, 288 in the tree), so
|
||||
position *is* identity and every lookup is one seek — no index that could
|
||||
disagree with the data, and both files stay `grep`-able plain text. At one
|
||||
million memories (607 MB), `memo wake` takes 0.03s and `memo note` 0.02s.
|
||||
Writes are serialized with a lock, so parallel sessions can note at once.
|
||||
Records are fixed width, so position *is* identity and every lookup is one
|
||||
seek: no index that could disagree with the data, and both files stay
|
||||
`grep`-able plain text. At one million memories (607 MB), `wake` takes 0.03s.
|
||||
|
||||
## Test
|
||||
|
||||
|
|
@ -220,19 +126,12 @@ Writes are serialized with a lock, so parallel sessions can note at once.
|
|||
python3 test.py
|
||||
```
|
||||
|
||||
Drives the real CLI through a synthetic life, checking that the context
|
||||
always tiles the log, never exceeds its budget, always gains detail toward
|
||||
the present, that every block is written exactly once, and that nothing is
|
||||
ever rewritten.
|
||||
|
||||
## Limitations
|
||||
|
||||
OptMem is honest about what it is. Recency is the only axis: an important
|
||||
old fact fades into its block like everything else, and the defence is
|
||||
rehearsal — noting it again refreshes it. `recall` is regex over plain text,
|
||||
not semantic search; the memory context is what tells you what to search
|
||||
for. Summaries are written by the agent, from other summaries, so a bad
|
||||
compression can propagate upward until you `forget` it. And the default
|
||||
context costs ~16k tokens per wake, which is deliberate — identity is worth
|
||||
more than the tokens — but it is not free. If what you need is a fact
|
||||
database, use a wiki or a retrieval system; this is for *who the agent is*.
|
||||
Recency is the only axis: an important old memory fades like any other, and
|
||||
the defence is rehearsal — noting it again makes it recent. `recall` is regex
|
||||
over plain text, not semantic search; the memory context is what tells you
|
||||
what to search for. Summaries are written by the agent from other summaries,
|
||||
so a bad one propagates upward until you `forget` it. And a wake costs ~16k
|
||||
tokens by default, which is deliberate but not free. If you need a fact
|
||||
database, use a wiki or a retrieval system — this is for *who the agent is*.
|
||||
|
|
|
|||
BIN
anim/optmem.gif
Normal file
BIN
anim/optmem.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7 MiB |
BIN
anim/poster.png
BIN
anim/poster.png
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB |
Loading…
Reference in a new issue