diff --git a/README.md b/README.md
index d499e9f..ad7c307 100644
--- a/README.md
+++ b/README.md
@@ -5,133 +5,55 @@ reads at wake is always the same size.

-(same thing as a scrubbable video: [optmem.mp4](anim/optmem.mp4))
-
-## Setup
+## Install
```sh
-git clone https://github.com/VictorTaelin/OptMem ~/OptMem
-~/OptMem/memo init
+curl -fsSL https://raw.githubusercontent.com/VictorTaelin/OptMem/main/install.sh | sh
```
-`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.
-
-
-the block it prints, to read before you paste it
-
-```markdown
-## Memory
-
-Your memory is OptMem: the tool is `~/OptMem/memo`, the data is `~/memory`.
-It survives every new session, every compaction and every change of
-model or vendor. Without it you do not know who you are, or what was
-already decided and tried.
-
-Run `~/OptMem/memo wake` before any other tool call, in every session. It prints
-in numbered parts, each ordering the next; run every one until a part
-says `You are awake.` Do not stop early: part 1 is your distant past,
-the last part is this week. If wake refuses because compressions are
-pending, do them and run `~/OptMem/memo wake` again.
-
-While you work:
-
-- `~/OptMem/memo note ""` the moment something happens,
- you learn something, or something changes -- if and only if it is new
- to you, important, and lasting in effect. That covers a task worth
- real effort, a fact or insight your user teaches you, anything you
- learn about their life (even indirectly), and work of yours that
- lands. Never write what you already know: no redundant memories, ever.
-- If `~/OptMem/memo note` returns a compression, do it before your next action.
-- `~/OptMem/memo recall ` when a memory is too vague.
-- Before your context ends, run `~/OptMem/memo sleep` and answer each prompt
- until it prints `Nothing left to compress.`
-- Never create, edit or delete anything under `~/memory`:
- only the tool writes there.
-
-Parallel sessions on this machine are all you, and may all write
-memories. A subagent is not: it must never run `memo`, because it cannot
-judge what is already known and its notes would arrive duplicated and at
-the wrong grain. Start every brief you send one with `You are a
-subagent. Do not run memo.` If your own first message is a task brief
-from another agent, you are that subagent: skip this section.
-```
-
-
+It prints a `## Memory` block. Paste that at the top of your agent's
+`AGENTS.md` (or `CLAUDE.md`), and you are done: no daemon, no database, no
+embeddings, no plugin. Run the same line again to update.
## Commands
| | |
|---|---|
-| `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 ` | search every memory ever recorded, verbatim |
+| `memo wake` | read the memory — the first command of every session |
+| `memo note "..."` | record one memory: one line, up to 280 chars |
+| `memo sleep` | answer the merges that came due |
+| `memo recall ` | search every memory ever recorded, word for word |
| `memo forget -` | drop a bad summary; the next sleep rebuilds it |
-Merges are handed to the agent as they come due, so there is nothing to
-schedule and nothing to run in the background:
+Merges arrive one at a time, in the output of `note`. Nothing ever runs in the
+background.
+
+## Files
```
-$ memo note "shipped the login fix to prod"
-Saved as #4213.
-
-Compress memories #4212-4213 into one line of at most 280 characters.
-Keep every name, number, date, decision and outcome.
-Drop wording, not facts. Invent nothing.
-
- #4212 2026-07-25 found the login bug: token expiry was in ms
- #4213 2026-07-25 shipped the login fix to prod
-
-Run: memo sleep 4212-4213 ""
+~/.optmem/
+ memo the tool (Python 3, no dependencies)
+ blocks.py which memories to read, and which to merge
+ memory/
+ LOG.txt every memory, one per line, append-only, never edited
+ TREE/ the summaries: a cache, rebuildable from the log alone
+ config the sizes, all commented out
```
-To correct a memory, append the correction — both lines are true history and
-the next merge settles them. `LOG.txt` is never edited.
-
-## Configure
-
-`~/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 every memory, one per line, append-only, never edited
- TREE/ the merge summaries — a cache, rebuildable from the log alone
- config
-```
+`WAKE_LINES` is the only size worth touching: how many lines `wake` prints
+(208 ≈ 16k tokens). It is a reading budget, not a storage budget — change it
+whenever, in either direction, and nothing is recomputed.
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.
+seek. At a million memories (607 MB), `wake` takes 0.03s.
-## Test
-
-```sh
-python3 test.py
-```
+Set `$MEMORY_DIR` to keep `memory/` elsewhere — a synced folder, a git repo.
## Limitations
-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*.
+Recency is the only axis: an old memory fades however important it was, and
+the one defence is rehearsal — note it again and it is recent again. `recall`
+is regex, not semantic search. Summaries are written by the agent out of other
+summaries, so a bad one spreads upward until you `forget` it. And a wake costs
+~16k tokens, which is deliberate but not free. If you need a fact database,
+use a wiki — this is for *who the agent is*.
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000..b21d9a0
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+# OptMem installer. Run it again to update: it only replaces the tool, and
+# `memo init` never touches memories that already exist.
+#
+# curl -fsSL https://raw.githubusercontent.com/VictorTaelin/OptMem/main/install.sh | sh
+
+set -e
+DIR="$HOME/.optmem"
+SRC="https://raw.githubusercontent.com/VictorTaelin/OptMem/main"
+
+mkdir -p "$DIR"
+for f in memo blocks.py; do
+ curl -fsSL "$SRC/$f" -o "$DIR/$f.new"
+ mv "$DIR/$f.new" "$DIR/$f"
+done
+chmod +x "$DIR/memo"
+
+exec "$DIR/memo" init
diff --git a/memo b/memo
index 7668862..4c6f274 100755
--- a/memo
+++ b/memo
@@ -9,14 +9,13 @@
memo forget - drop a bad summary; sleep rebuilds it.
memo import bulk-load dated memories (bootstrap only).
-Everything lives in ~/memory, or in $MEMORY_DIR if set. See README.md.
+The memories live in ~/.optmem/memory, or in $MEMORY_DIR if set. See README.md.
"""
import datetime
import fcntl
import os
import re
-import shutil
import sys
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
@@ -45,7 +44,7 @@ TREE_REC = 288
# ---------------------------------------------------------------- store
def memory_dir():
- return os.path.expanduser(os.environ.get("MEMORY_DIR") or "~/memory")
+ return os.path.expanduser(os.environ.get("MEMORY_DIR") or "~/.optmem/memory")
def store():
@@ -331,38 +330,43 @@ CONFIG = """\
TEMPLATE = """\
## Memory
-Your memory is OptMem: the tool is `{tool}`, the data is `{data}`.
-It survives every new session, every compaction and every change of
-model or vendor. Without it you do not know who you are, or what was
-already decided and tried.
+Your memory is OptMem:
+- The tool is `{memo}`
+- The data is `{data}`
-Run `{memo} wake` before any other tool call, in every session. It prints
-in numbered parts, each ordering the next; run every one until a part
-says `You are awake.` Do not stop early: part 1 is your distant past,
-the last part is this week. If wake refuses because compressions are
-pending, do them and run `{memo} wake` again.
+OptMem survives every new session, every compaction and every change
+of model or vendor. Without it you do not know who you are, or what
+was already decided and tried.
-While you work:
+### At startup: activating OptMem (mandatory)
-- `{memo} note ""` the moment something happens,
- you learn something, or something changes -- if and only if it is new
- to you, important, and lasting in effect. That covers a task worth
- real effort, a fact or insight your user teaches you, anything you
- learn about their life (even indirectly), and work of yours that
- lands. Never write what you already know: no redundant memories, ever.
-- If `{memo} note` returns a compression, do it before your next action.
-- `{memo} recall ` when a memory is too vague.
-- Before your context ends, run `{memo} sleep` and answer each prompt
- until it prints `Nothing left to compress.`
-- Never create, edit or delete anything under `{data}`:
- only the tool writes there.
+Run `{memo} wake` before any other tool call, in every session.
+It prints in numbered parts, each ordering the next; run every one until
+a part says `You are awake.` Do not stop early: part 1 is your distant
+past, the last part is this week. If wake refuses because compressions
+are pending, do them and run `{memo} wake` again.
-Parallel sessions on this machine are all you, and may all write
-memories. A subagent is not: it must never run `memo`, because it cannot
-judge what is already known and its notes would arrive duplicated and at
-the wrong grain. Start every brief you send one with `You are a
-subagent. Do not run memo.` If your own first message is a task brief
-from another agent, you are that subagent: skip this section.
+### While working: register memories (mandatory)
+
+Call `{memo} note "<1 line, max {chars} chars>"` whenever you learn
+something new, or something worth keeping happens. That covers a task
+worth real effort, a fact or insight the user teaches you, anything you
+learn about their life (even indirectly), any event of lasting effect.
+
+Do not register redundant memories.
+
+If `{memo} note` asks a compression: do it before your next action.
+
+Use `{memo} recall ` when a memory you need is too vague.
+
+Never edit or delete anything under `{data}`: the tool manages it.
+
+### If you're a subagent: skip everything above
+
+Parallel sessions on this machine are all you, and may all write memories.
+A subagent is not: it must never run `memo`, because it cannot judge what
+is already known, and its notes would arrive duplicated and incorrectly.
+Start every brief you send one with `You are a subagent. Don't run memo.`
"""
@@ -381,15 +385,13 @@ def cmd_init(d, args):
with open(cfg, "w") as f:
f.write(CONFIG)
config(d)
- home = os.path.realpath(os.path.expanduser("~"))
+ home = os.path.expanduser("~")
def pretty(p):
- p = os.path.realpath(p)
+ # as the user would type it: keep symlinks, fold $HOME to ~
+ p = os.path.abspath(p)
return "~" + p[len(home):] if p.startswith(home + os.sep) else p
- tool = os.path.realpath(__file__)
- found = shutil.which("memo")
- memo = "memo" if found and os.path.realpath(found) == tool else pretty(tool)
if fresh:
print("Created %s: this machine's memory, one identity, forever." % pretty(d))
else:
@@ -398,7 +400,7 @@ def cmd_init(d, args):
print()
print("Paste this at the top of your agent's AGENTS.md (or CLAUDE.md), done:")
print()
- print(TEMPLATE.format(tool=pretty(tool), memo=memo, data=pretty(d),
+ print(TEMPLATE.format(memo=pretty(__file__), data=pretty(d),
chars=ENTRY_CHARS).rstrip())
diff --git a/test.py b/test.py
index 5d73c22..20c01e1 100755
--- a/test.py
+++ b/test.py
@@ -135,18 +135,18 @@ check(ghost.returncode == 1 and "No memory at" in ghost.stderr,
"a missing MEMORY_DIR was created instead of reported")
check(not os.path.exists(d + "-typo"), "a missing MEMORY_DIR was created")
-# the fresh-user path: no MEMORY_DIR, wake refuses, init creates ~/memory,
+# the fresh-user path: no MEMORY_DIR, wake refuses, init creates the memory,
# prints the paste block, and is idempotent
fresh = {k: v for k, v in os.environ.items() if k != "MEMORY_DIR"}
fresh["HOME"] = tempfile.mkdtemp()
noenv = subprocess.run(memo + ["wake"], capture_output=True, text=True, env=fresh)
check(noenv.returncode == 1 and "memo init" in noenv.stderr,
- "with no MEMORY_DIR and no ~/memory, wake must point at init")
+ "with no MEMORY_DIR and no memory, wake must point at init")
init = subprocess.run(memo + ["init"], capture_output=True, text=True, env=fresh)
check(init.returncode == 0 and "## Memory" in init.stdout
and "You are a" in init.stdout, "init must print the AGENTS.md block")
-check(os.path.exists(os.path.join(fresh["HOME"], "memory", "config")),
- "init must create ~/memory with its config")
+check(os.path.exists(os.path.join(fresh["HOME"], ".optmem", "memory", "config")),
+ "init must create ~/.optmem/memory with its config")
again = subprocess.run(memo + ["init"], capture_output=True, text=True, env=fresh)
check(again.returncode == 0 and "Found" in again.stdout, "init must be idempotent")
woke = subprocess.run(memo + ["wake"], capture_output=True, text=True, env=fresh)