memhouse 0.18.12 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +22 -151
- package/CHANGELOG.md +263 -29
- package/LICENSE +202 -22
- package/LICENSES/agentlytics-MIT.txt +21 -0
- package/LICENSES/memhouse-MIT-until-0.18.11.txt +22 -0
- package/LICENSES/models.dev-MIT.txt +22 -0
- package/NOTICE +28 -0
- package/README.md +42 -20
- package/SECURITY.md +21 -8
- package/bin/memhouse.js +2223 -311
- package/docs/README.md +11 -9
- package/docs/guides/admin-password.md +37 -10
- package/docs/guides/configuration.md +13 -6
- package/docs/guides/daemons.md +64 -3
- package/docs/guides/import-claude-ai.md +204 -0
- package/docs/guides/install.md +11 -6
- package/docs/guides/invite.md +21 -7
- package/docs/guides/operate.md +114 -7
- package/docs/guides/search-and-resume.md +72 -3
- package/docs/guides/share.md +91 -52
- package/docs/guides/storage.md +44 -4
- package/docs/guides/troubleshooting.md +121 -14
- package/docs/guides/uninstall.md +4 -3
- package/docs/guides/update.md +16 -0
- package/docs/guides/upgrade-to-0.20.md +188 -0
- package/docs/releases/README.md +2 -1
- package/docs/releases/v0.18.10.md +5 -5
- package/docs/releases/v0.18.11.md +3 -3
- package/docs/releases/v0.20.0.md +627 -0
- package/docs/releases/v0.20.1.md +114 -0
- package/docs/tutorials/01-first-house.md +10 -7
- package/docs/tutorials/02-join-a-house.md +24 -15
- package/docs/tutorials/03-share-your-memory.md +40 -31
- package/editors/antigravity.js +1 -80
- package/editors/base.js +463 -9
- package/editors/bridge.js +119 -0
- package/editors/claude.js +273 -105
- package/editors/codex.js +278 -52
- package/editors/copilot.js +1 -70
- package/editors/cursor.js +1 -75
- package/editors/derive-fingerprints.json +18 -0
- package/editors/harness-paths.js +69 -0
- package/editors/index.js +1 -20
- package/editors/normalise/agy.js +910 -0
- package/editors/normalise/copilot-cli.js +321 -0
- package/editors/normalise/cursor-agent.js +852 -0
- package/editors/normalise/cursor.js +474 -0
- package/editors/normalise/devin.js +575 -0
- package/editors/normalise/gemini-cli.js +602 -0
- package/editors/normalise/goose.js +673 -0
- package/editors/normalise/opencode.js +633 -0
- package/editors/normalise/vscode.js +549 -0
- package/editors/normalise/zed.js +473 -0
- package/editors/opencode-v4.js +119 -0
- package/editors/scope.js +14 -3
- package/editors/sessions-log.js +349 -0
- package/editors/sqlite.js +28 -1
- package/editors/vscode.js +1 -72
- package/editors/windsurf.js +1 -143
- package/examples/01-local-one-machine/README.md +24 -19
- package/examples/01-local-one-machine/run.sh +1 -1
- package/examples/02-existing-clickhouse/README.md +30 -22
- package/examples/02-existing-clickhouse/run.sh +5 -4
- package/examples/03-team-house/README.md +44 -37
- package/examples/03-team-house/run.sh +35 -18
- package/examples/04-self-hosted-team/README.md +29 -14
- package/examples/04-self-hosted-team/compose.yml +5 -2
- package/examples/04-self-hosted-team/smoke.sh +26 -12
- package/examples/README.md +8 -4
- package/examples/fixtures/README.md +4 -1
- package/examples/fixtures/make-sessions.js +8 -5
- package/memhouse/admin-secret.js +89 -24
- package/memhouse/argv-secrets.js +64 -0
- package/memhouse/channel.js +49 -9
- package/memhouse/codex-plugin.js +194 -0
- package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +2 -1
- package/memhouse/delivery/AGENT-INSTALL.md +22 -11
- package/memhouse/delivery/INVITATION.md.tpl +44 -45
- package/memhouse/delivery/PROMPT.md +53 -5
- package/memhouse/delivery/UPGRADE.md.tpl +1 -1
- package/memhouse/delivery/codex/AGENTS-BLOCK.md +11 -0
- package/memhouse/delivery/codex/SKILL.md +50 -0
- package/memhouse/delivery/kernel-install.md +17 -9
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
- package/memhouse/delivery/plugin/reference/HOUSE.md +168 -41
- package/memhouse/delivery/plugin/skills/access/SKILL.md +13 -5
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +14 -6
- package/memhouse/delivery/plugin/skills/house/SKILL.md +25 -8
- package/memhouse/delivery/plugin/skills/recall/SKILL.md +66 -8
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +66 -7
- package/memhouse/deploy.js +1 -1
- package/memhouse/flags.js +35 -18
- package/memhouse/help.js +37 -0
- package/memhouse/host.js +82 -2
- package/memhouse/house/HOUSE.md +2 -2
- package/memhouse/house/convert-profile.js +638 -0
- package/memhouse/house/convert-v3.js +742 -0
- package/memhouse/house/doctor-v3.js +324 -0
- package/memhouse/house/host-alias.js +85 -0
- package/memhouse/house/house.js +3 -3
- package/memhouse/house/migrate.js +2 -2
- package/memhouse/house/profile-dedupe.js +183 -0
- package/memhouse/house/profile-edges.js +554 -0
- package/memhouse/house/profile-house.js +430 -0
- package/memhouse/house/profile-rollup.js +190 -0
- package/memhouse/house/q-cli.js +863 -0
- package/memhouse/house/queries-v3.js +1013 -0
- package/memhouse/house/reconcile-v3.js +192 -0
- package/memhouse/house/records-to-profile.js +174 -0
- package/memhouse/house/records-to-rows.js +169 -0
- package/memhouse/house/relocate.js +2 -1
- package/memhouse/house/schema-choice.js +121 -0
- package/memhouse/house/schema-profile.js +290 -0
- package/memhouse/house/schema-profile.sql.tpl +398 -0
- package/memhouse/house/schema-v3.js +267 -0
- package/memhouse/house/schema-v3.sql.tpl +213 -0
- package/memhouse/import/claude-ai.js +420 -0
- package/memhouse/import/import.js +372 -0
- package/memhouse/import/pyjson.js +206 -0
- package/memhouse/import/records.js +64 -0
- package/memhouse/import/writer-profile.js +192 -0
- package/memhouse/import/writer-v3.js +240 -0
- package/memhouse/import/zip.js +112 -0
- package/memhouse/invitation.js +102 -0
- package/memhouse/invitefile.js +5 -2
- package/memhouse/plugin-copies.js +176 -0
- package/memhouse/provision.js +7 -2
- package/memhouse/release-hint.js +24 -0
- package/memhouse/resume.js +4 -4
- package/memhouse/server/api-v3.js +632 -0
- package/memhouse/server/dashboard-auth.js +181 -0
- package/memhouse/server/dashboard-port.js +91 -0
- package/memhouse/server/queries.js +30 -0
- package/memhouse/server/server.js +118 -20
- package/memhouse/service.js +1 -1
- package/memhouse/share.js +687 -1
- package/memhouse/shipper/adapter-capture.js +756 -0
- package/memhouse/shipper/capture.js +2571 -0
- package/memhouse/shipper/dedupe-profile.js +237 -0
- package/memhouse/shipper/dedupe-v3.js +1027 -0
- package/memhouse/shipper/forks.js +233 -0
- package/memhouse/shipper/log-adapter.js +219 -0
- package/memhouse/shipper/maintenance.js +49 -0
- package/memhouse/shipper/normalise-run.js +148 -0
- package/memhouse/shipper/passlog.js +68 -5
- package/memhouse/shipper/profile-capture.js +903 -0
- package/memhouse/shipper/ship.js +91 -8
- package/memhouse/shipper/snapshots.js +193 -0
- package/memhouse/shipper/source-roots.js +111 -0
- package/memhouse/version-notice.js +66 -0
- package/package.json +14 -7
- package/public/THIRD_PARTY_NOTICES.txt +2 -2
- package/public/assets/index-CcNnhfM3.js +78 -0
- package/public/index.html +1 -1
- package/ui/package-lock.json +56 -37
- package/ui/package.json +1 -0
- package/ui/src/App.jsx +29 -14
- package/ui/src/components/ChatSidebar.jsx +69 -12
- package/ui/src/lib/api.js +76 -0
- package/ui/src/lib/dashboard-token.js +41 -0
- package/ui/src/main.jsx +22 -5
- package/ui/src/pages/Dashboard.jsx +22 -36
- package/ui/third-party-notices.js +119 -0
- package/ui/vite.config.js +5 -1
- package/docs/releases/v0.18.12.md +0 -59
- package/memhouse/COMPETITION.md +0 -221
- package/memhouse/DESIGN.md +0 -222
- package/memhouse/RESUME-AND-UPDATE.md +0 -182
- package/public/assets/index-CnWzrjF3.js +0 -78
package/memhouse/DESIGN.md
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
# memhouse — design
|
|
2
|
-
|
|
3
|
-
**memhouse** is agent conversation memory as a product: every coding-agent session
|
|
4
|
-
on your machines — across **all 17 editors** agentlytics supports — parsed locally,
|
|
5
|
-
shipped to a typed ClickHouse store, shareable with a team, installable on the
|
|
6
|
-
ultimagent kernel as an **agency**, and visible through the agentlytics dashboard.
|
|
7
|
-
|
|
8
|
-
**A house is a database; its rooms are tables** — `sessions`, `messages`, `tool_calls`.
|
|
9
|
-
The server-stamped `user_id` says who wrote a row, the install fingerprint `host` says
|
|
10
|
-
which machine.
|
|
11
|
-
|
|
12
|
-
**What a member owns inside a house is the layout's whole question, and it has three
|
|
13
|
-
answers, two of them retired.** Shared tables with row policies fell in 0.4.0 (policies
|
|
14
|
-
are permissive and OR'd — a catch-all fails OPEN). Per-member suffixed rooms fell in
|
|
15
|
-
0.8.0, on the argument that isolation between housemates solved an adversarial problem
|
|
16
|
-
the product did not have. That argument was wrong, and the counter-example is measured:
|
|
17
|
-
with everyone holding `ALL ON db.* WITH GRANT OPTION`, one member ran
|
|
18
|
-
`GRANT SELECT ON db.* TO <outsider>` and handed over a housemate's transcripts — no
|
|
19
|
-
admin, no notification.
|
|
20
|
-
|
|
21
|
-
So the layout today is per-member rooms again, prefixed rather than suffixed, and this
|
|
22
|
-
time there is only one of it: **every member's rooms are named for them, and one wildcard
|
|
23
|
-
grant — `ON mem.<name>_*` — is the whole of what they hold.** Nobody is granted the
|
|
24
|
-
database. A house with one member is a house of one, not a different layout; standalone,
|
|
25
|
-
team and kernel are the same statements. `roomNames()` in `house/house.js` is the only
|
|
26
|
-
place a table name is produced and `provision.js` the only description of the grant.
|
|
27
|
-
|
|
28
|
-
The two objections that retired the 0.8.0 layout were real and are paid again here:
|
|
29
|
-
clients must resolve names before querying (`memhouse rooms`, and the `q()` helper the
|
|
30
|
-
skills route through), and reading across members needs a `UNION ALL` rather than one
|
|
31
|
-
table. Both are cheaper than the leak.
|
|
32
|
-
|
|
33
|
-
**Why grants and not policies.** A grant is something a colleague can verify for
|
|
34
|
-
themselves — `SHOW GRANTS` lists their rooms, and `SHOW TABLES` does not show them what
|
|
35
|
-
they were not granted. "Trust the row policy protecting your rows" is a sentence you
|
|
36
|
-
cannot ask a teammate to accept when "here are your grants, check them" is available.
|
|
37
|
-
|
|
38
|
-
**Agency, precisely.** In constellation terms (`../TERMINOLOGY.md`) memhouse is a
|
|
39
|
-
**house** — the `mem` database — **plus a resident**: the shipper. That pairing
|
|
40
|
-
is what the word *agency* means; a house on its own only holds.
|
|
41
|
-
|
|
42
|
-
The test is mechanical — **residents write, routines read**. The shipper fires on a
|
|
43
|
-
trigger no query supplies (its own loop) and produces writes that outlive the call:
|
|
44
|
-
|
|
45
|
-
```toml
|
|
46
|
-
[[resident]]
|
|
47
|
-
kind = "worker" # deterministic code, no LLM
|
|
48
|
-
on = "loop" # a daemon — not an insert trigger, not a schedule
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The session rollup is a **routine** — house machinery, not a second resident. Since
|
|
52
|
-
0.4.0 it is not even an object: it is SQL text (`house/house.js`), computed during your
|
|
53
|
-
query, for your query, writing nothing. memhouse has **no materialized views at all** — in fact no stored views at all
|
|
54
|
-
— so the shipper is not merely a resident, it is the only candidate in the tree. Strip
|
|
55
|
-
it and every remaining moving part is a routine over rows nobody is writing any more.
|
|
56
|
-
|
|
57
|
-
Note that `user_id MATERIALIZED currentUser()` is a materialized *column*: it computes
|
|
58
|
-
during your own insert and is part of the table's definition. It is what tells housemates'
|
|
59
|
-
rows apart in the shared tables — provenance, stamped by the server, with async_insert
|
|
60
|
-
pinned so it cannot be skipped. An identity property, not what makes this an agency.
|
|
61
|
-
|
|
62
|
-
**Positioning:** an alternative agency **competing with memory-house**. It borrows
|
|
63
|
-
memory-house's proven ideas (server-stamped identity, idempotent shipping,
|
|
64
|
-
skills/plugin delivery) and agentlytics' proven assets (adapters, UI, cost engine) —
|
|
65
|
-
but **not** its row-policy machinery: memhouse's tables are shared by people who chose
|
|
66
|
-
one house, and the boundary between houses is the database itself. If it wins, it can become memory-house v4; until then the
|
|
67
|
-
two run side by side as separate agencies, each in its own house. Naming: the product is
|
|
68
|
-
**memhouse**; the agency is **`memhouse`** and its house is the **`mem`** database
|
|
69
|
-
(identifiers can't carry a dash; the owner kept the longer name, `memhouse_root`).
|
|
70
|
-
|
|
71
|
-
## The four bets
|
|
72
|
-
|
|
73
|
-
`COMPETITION.md` grades each of these against the field as surveyed 2026-08-07.
|
|
74
|
-
Short version: bets 1, 2 and 4 are occupied by shipping competitors, several of
|
|
75
|
-
them free and further along; **bet 3 is the one nobody else attempts.**
|
|
76
|
-
|
|
77
|
-
1. **Parse on the client.** memory-house ships raw transcript lines and parses in
|
|
78
|
-
views — workable only because its 4 agents store sessions as JSON/JSONL files.
|
|
79
|
-
agentlytics' breadth includes sqlite-backed editors (Cursor, VS Code, Zed,
|
|
80
|
-
Devin…) where no "raw line" exists; the adapter must crack a DB. So client-side
|
|
81
|
-
parsing is not a style choice — it is what unlocks 17-editor coverage. The
|
|
82
|
-
shipper runs the adapters (`editors/`) and ships **typed rows**. On the canon's
|
|
83
|
-
write axis this bet is precisely *moving work from routine to resident*:
|
|
84
|
-
memory-house parses when you query, memhouse parses before anyone asks and
|
|
85
|
-
writes the result down.
|
|
86
|
-
2. **Typed common schema** (`house/schema.sql.tpl` — the rooms, rendered per member).
|
|
87
|
-
Physical typed columns (what memory-house derives in views) + `tool_calls`
|
|
88
|
-
(memory-house has no tool table) + one `extra JSON` escape hatch per room so
|
|
89
|
-
unnormalized adapter fields are never lost. `ReplacingMergeTree(ingested_at)`;
|
|
90
|
-
`messages` keyed `(session_id, user_id, origin, epoch, seq)` so a re-ship of a
|
|
91
|
-
grown session replaces stale rows (latest-wins), two housemates' rows never
|
|
92
|
-
collapse into one, and a re-parse that SHRANK or was rewritten lands under a new
|
|
93
|
-
epoch instead of over what it replaces. FTS text indexes built in (CH ≥ 26.2).
|
|
94
|
-
3. **Kernel-installable agency.** Joining any ClickHouse — a kernel's included — is a
|
|
95
|
-
database plus a credential: `CREATE USER`, `GRANT ALL ON <house>.* `, done. The
|
|
96
|
-
shipper is the resident that lands with it and creates its own rooms. Identity is
|
|
97
|
-
`user_id MATERIALIZED currentUser()` with `async_insert = 0 CONST` pinned on the
|
|
98
|
-
user; machines are told apart by the `host` fingerprint. The boundary between
|
|
99
|
-
agencies on one server is the database — a house's ALL reaches nothing outside it.
|
|
100
|
-
4. **Borrowed UI, zero fork.** The React SPA consumes REST JSON, not tables. The
|
|
101
|
-
memhouse server implements the **same `/api/*` contract** as agentlytics'
|
|
102
|
-
`server.js` (same routes, same response shapes) over the memhouse schema, and
|
|
103
|
-
serves the same built `public/` bundle. UI changes: none.
|
|
104
|
-
|
|
105
|
-
## Components (this directory)
|
|
106
|
-
|
|
107
|
-
| Path | What | Notes |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| `house/house.js` | room names + the session rollup + the user pin | one rule for both transports |
|
|
110
|
-
| `house/schema.sql.tpl` | the rooms (typed) | applied by the member's shipper, or by the operator at invite in a shared house |
|
|
111
|
-
| `house/HOUSE.md` | the model, and what it replaced | |
|
|
112
|
-
| `shipper/ship.js` | parse-on-client shipper CLI — the resident (`worker`) | reuses `../../editors`; incremental; idempotent |
|
|
113
|
-
| `server/server.js` | REST API + dashboard | same API contract as agentlytics; serves `../../public` |
|
|
114
|
-
| `delivery/` | delivery kit | installer, skills, plugin, AGENT-INSTALL.md, prompt |
|
|
115
|
-
| `COMPETITION.md` | the competitive landscape, graded against the four bets | surveyed 2026-08-07; figures decay, re-run before citing |
|
|
116
|
-
|
|
117
|
-
## Environment contract
|
|
118
|
-
|
|
119
|
-
| Var | Default | Meaning |
|
|
120
|
-
|---|---|---|
|
|
121
|
-
| `MEMHOUSE_URL` | `http://localhost:8123` | ClickHouse HTTP(S) endpoint |
|
|
122
|
-
| `MEMHOUSE_USER` | `memhouse_root` | CH user (owner, or a member's own credential) |
|
|
123
|
-
| `MEMHOUSE_PASSWORD` | *(empty)* | credential |
|
|
124
|
-
| `MEMHOUSE_DB` | `mem` | the house |
|
|
125
|
-
| `MEMHOUSE_PORT` | `4640` | dashboard/API port |
|
|
126
|
-
|
|
127
|
-
Config file: `~/.memhouse/config.json` (`hiddenProjects`, future prefs).
|
|
128
|
-
|
|
129
|
-
## Data-plane rules (binding for the shipper and server)
|
|
130
|
-
|
|
131
|
-
- Inserts: `JSONEachRow`, batches ≤ 2000 rows, **`async_insert=0` always** (else
|
|
132
|
-
`user_id` stamping breaks). Reads: set `final: 1` so ReplacingMergeTree collapses.
|
|
133
|
-
- All Int64-bound values coerced to integers (some adapters emit fractional ms).
|
|
134
|
-
- Per-message `ts`: interpolated across the session's `[createdAt, lastUpdatedAt]`
|
|
135
|
-
(adapters don't expose per-message timestamps; monotonic by `seq`; documented
|
|
136
|
-
approximation, refine per-adapter later).
|
|
137
|
-
- `seq` is the message's place in the session. The session's own turns are dense from 0;
|
|
138
|
-
a folded subagent's turns are numbered from their own block,
|
|
139
|
-
`1,000,000,000 + slot × 100,000 + turn`, where `slot` is the subagent's rank by start
|
|
140
|
-
time. `tool_calls.idx` is laid out the same way (the session's own calls dense from 0,
|
|
141
|
-
a subagent's counted within its block). A row's key therefore never depends on how long
|
|
142
|
-
the parent or any other subagent is, which is what lets a session with subagents ship
|
|
143
|
-
tail-only; positional numbering moved every subagent row whenever the parent grew. A
|
|
144
|
-
parse that does not fit the blocks falls back to positional numbering. Readers order by
|
|
145
|
-
`seq` and count with `count()`; nothing may treat `seq` as dense. `is_subagent` = message content begins with
|
|
146
|
-
`[subagent]` (the fold marker from the Claude adapter).
|
|
147
|
-
- Incremental shipping: read existing `(session_id, last_updated_at, message_count)`
|
|
148
|
-
(FINAL) and skip chats that haven't grown/changed; a full re-ship must remain
|
|
149
|
-
safe (ReplacingMergeTree collapses).
|
|
150
|
-
- **A session that is re-shipped sends only its tail.** Collapsing at FINAL makes a
|
|
151
|
-
re-ship *safe*, not *free*: a growing session that re-sent its whole transcript every
|
|
152
|
-
pass accumulated one full copy of itself per pass, and because every read carries
|
|
153
|
-
`final: 1`, readers pay to collapse all of them. Measured on a real 1,230-session
|
|
154
|
-
house: 1,226,770 stored message rows for 685,649 real ones. So once `decideEpoch` has
|
|
155
|
-
reused the epoch — which already proves nothing in the overlap changed — `tailRows`
|
|
156
|
-
drops every row whose stored `line_hash` matches the incoming one, and every tool call
|
|
157
|
-
matching on `(tool_name, args)`. A seq the house is missing has no stored hash, so a
|
|
158
|
-
pass that died mid-flush still repairs itself.
|
|
159
|
-
`tailSafe` withholds the tail path for the three shipped columns `line_hash` does not
|
|
160
|
-
cover and cannot derive: `ts` when the adapter supplies none (it is interpolated as
|
|
161
|
-
`seq / (total - 1)`, so every stored row's ts moves as the session grows), `host`, and
|
|
162
|
-
`folder`/`project`. Those sessions re-ship whole, as does `--full`, which stays the
|
|
163
|
-
repair hammer.
|
|
164
|
-
- **The shipper is insert-only, and nothing may reintroduce a destructive verb.**
|
|
165
|
-
`ReplacingMergeTree` collapses same-key rows but cannot remove a row the new parse no
|
|
166
|
-
longer produces, so a shrunken re-parse leaves a stale `seq` tail. The shipper used to
|
|
167
|
-
clear the session's rows first; that destroyed content held nowhere else, because
|
|
168
|
-
Claude Code compacts transcripts and deletes them after `cleanupPeriodDays` (30 by
|
|
169
|
-
default), and a shorter re-parse cannot be told apart from a fixed adapter bug. Instead
|
|
170
|
-
`decideEpoch` bumps the session's `epoch` when the new parse is shorter than the stored
|
|
171
|
-
one, or diverges from it at an overlapping `seq` (compared on the stored `line_hash`),
|
|
172
|
-
and writes underneath the old parse rather than over it.
|
|
173
|
-
- **memhouse is an accumulator, not a mirror.** Absence of a session on disk is never a
|
|
174
|
-
signal: retention cleanup, a manual delete, a second laptop, an unmounted folder, a
|
|
175
|
-
locked SQLite file and a thrown adapter all produce the identical observation. A
|
|
176
|
-
session that disappears is never cleared and never re-inserted. **No `sync` and no
|
|
177
|
-
`prune` feature may be built here** — either would destroy the archive by design.
|
|
178
|
-
- Reads see one parse per session. `roomNames()` resolves `messages`/`tool_calls` to a
|
|
179
|
-
current-epoch subquery and the bare tables only as `*_raw`, so retention cannot silently
|
|
180
|
-
inflate a rollup, and a read path written by someone who has never heard of epochs is
|
|
181
|
-
still correct. Measured cost of the filter: **+10 ms** across the dashboard's queries on
|
|
182
|
-
a real 66k-message house (all of them under 200 ms), and **+13–37 ms** on a synthetic
|
|
183
|
-
1M-message / 20k-session one. The single shape that grows is a one-session read
|
|
184
|
-
(5 ms → 18 ms at 1M rows): the `IN` aggregates the whole room regardless of the outer
|
|
185
|
-
session filter. If that ever matters, the answer is a small materialized current-epoch
|
|
186
|
-
table maintained by the shipper — never a filter each caller has to remember.
|
|
187
|
-
- **A mutation predicate must bind the user, never call `currentUser()`.** No mutation
|
|
188
|
-
survives in the shipper, but the finding outlives it — `migrate-rooms` and `reset` still
|
|
189
|
-
run statements where it applies. A mutation does not evaluate `currentUser()` in the
|
|
190
|
-
caller's context: measured on ClickHouse 25.11 and chdb, the same `DELETE` matched 0
|
|
191
|
-
rows with `user_id = currentUser()` and 2000 with the value bound — silently, both
|
|
192
|
-
times. Read the identity once (`SELECT currentUser()`) and pass it as a query parameter.
|
|
193
|
-
The same trap bites the other way in a rebuild: `user_id` is `MATERIALIZED
|
|
194
|
-
currentUser()`, so an `INSERT SELECT` that does not carry it explicitly (under
|
|
195
|
-
`insert_allow_materialized_columns=1`) restamps every copied row with whoever ran the
|
|
196
|
-
migration.
|
|
197
|
-
- Costs are computed in the server from `pricing.js` (repo root) over per-model
|
|
198
|
-
token sums; `<synthetic>` and empty models excluded from model lists.
|
|
199
|
-
|
|
200
|
-
## Delivery kit (mirrors memory-house)
|
|
201
|
-
|
|
202
|
-
`AGENT-INSTALL.md` (agent-facing install steps), `plugin/` (the Claude Code plugin
|
|
203
|
-
wrapping the three skills), `PROMPT.md` (system-prompt snippet teaching an agent to
|
|
204
|
-
use memhouse as memory), `kernel-install.md` (provision as agency `memhouse`).
|
|
205
|
-
|
|
206
|
-
**`install.sh` is retired.** It was a second implementation of `memhouse install`,
|
|
207
|
-
maintained separately and drifting apart from it: by the end it still defaulted to
|
|
208
|
-
`http://localhost:8123` as `memhouse_root` — precisely the guessed house the CLI now
|
|
209
|
-
refuses on purpose, because on many machines that credential reaches a real house
|
|
210
|
-
belonging to someone else. One installer, and it is the one the acceptance suite
|
|
211
|
-
drives.
|
|
212
|
-
|
|
213
|
-
## Non-goals (v0)
|
|
214
|
-
|
|
215
|
-
Relay/team-server (per-room grants plus the `Merge` rooms already cover sharing),
|
|
216
|
-
migration tooling from memory-house's `memory` db, and per-adapter true message
|
|
217
|
-
timestamps.
|
|
218
|
-
|
|
219
|
-
OS-level daemonization **was** a non-goal — "ship `--loop` instead" — and is no longer:
|
|
220
|
-
`memhouse service install` writes a systemd `--user` unit or a launchd LaunchAgent. The
|
|
221
|
-
reason the non-goal did not survive is that `--loop` outlives the shell and nothing else,
|
|
222
|
-
so a machine that reboots stops shipping until somebody notices.
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
# resume, update, and a daemon that notices
|
|
2
|
-
|
|
3
|
-
Three things that were missing, specified together because they share one premise: the
|
|
4
|
-
pilot's memory is only useful if they can *get back into* it, and only trustworthy if the
|
|
5
|
-
process holding it is the version they installed.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. `memhouse resume <session-id>`
|
|
10
|
-
|
|
11
|
-
### The gap
|
|
12
|
-
|
|
13
|
-
memhouse was a read-only archive. You could find the session that solved this problem
|
|
14
|
-
six weeks ago and not return to it. Every rival that does this is praised for it
|
|
15
|
-
(`COMPETITION.md`: Agent Sessions copies resume commands for six CLIs, claude-history
|
|
16
|
-
resumes *and* forks worktree-aware).
|
|
17
|
-
|
|
18
|
-
### What it needs
|
|
19
|
-
|
|
20
|
-
Nothing new. A stored session already carries both halves:
|
|
21
|
-
|
|
22
|
-
| Column | Supplies |
|
|
23
|
-
|---|---|
|
|
24
|
-
| `session_id` | `<source>:<native-id>` — which CLI, and which session in it (`ship.js:575`) |
|
|
25
|
-
| `folder` | the directory the session ran in |
|
|
26
|
-
|
|
27
|
-
So resume is a **read-time computation over two columns**. No schema change, no
|
|
28
|
-
migration, no new room.
|
|
29
|
-
|
|
30
|
-
### It prints; it does not run
|
|
31
|
-
|
|
32
|
-
The command is written to stdout for the pilot to paste.
|
|
33
|
-
|
|
34
|
-
Running it would mean guessing a terminal, inheriting this process's cwd, and assuming the
|
|
35
|
-
id is still live. When any of those is wrong, the CLI **does not fail** — it opens a new
|
|
36
|
-
session. The transcript the pilot asked for is still lost, and the tool reports success.
|
|
37
|
-
That is the exact failure shape this repo has been bitten by fifteen times: a wrong answer
|
|
38
|
-
delivered confidently.
|
|
39
|
-
|
|
40
|
-
### The flag table is evidence, not recall
|
|
41
|
-
|
|
42
|
-
`memhouse/resume.js` holds one entry per resumable editor, and each entry records the date
|
|
43
|
-
its syntax was read out of that CLI's own `--help`.
|
|
44
|
-
|
|
45
|
-
| Source | Command | Read from |
|
|
46
|
-
|---|---|---|
|
|
47
|
-
| `claude` | `claude --resume <id>` | `claude --help`, 2026-08-12 |
|
|
48
|
-
| `codex` | `codex resume <id>` | `codex resume --help`, 2026-08-12 |
|
|
49
|
-
| `opencode` | `opencode --session <id>` | `opencode --help`, 2026-08-12 |
|
|
50
|
-
|
|
51
|
-
**An editor whose flag has not been read that way is absent from the table**, and an absent
|
|
52
|
-
entry prints a refusal. Round 10 is why: a field name was asserted from a failed search,
|
|
53
|
-
written into a comment, a PR and the task file, and was simply wrong. `goose`, `gemini-cli`
|
|
54
|
-
and `cursor-agent` are plausibly resumable and are *not* listed, because they are not
|
|
55
|
-
installed on the machine the table was written on.
|
|
56
|
-
|
|
57
|
-
The refusal distinguishes two cases, for the same reason `discover` separates a skipped
|
|
58
|
-
adapter from an editor you do not have:
|
|
59
|
-
|
|
60
|
-
- **No CLI at all** (`cursor`, `vscode`, `zed`, `kiro`, `copilot-jetbrains`, `antigravity`,
|
|
61
|
-
`devin`, `devin-next`) — nothing takes a session id, and no amount of checking will
|
|
62
|
-
produce something that does. Opening the folder is not resuming the session and is not
|
|
63
|
-
dressed up as it; the folder is printed as the one actionable fact memhouse holds.
|
|
64
|
-
- **Unverified** (`goose`, `gemini-cli`, `cursor-agent`, `copilot-cli`, `codebuff`,
|
|
65
|
-
`commandcode`, `gsd`) — a CLI that may well support it; nobody has read the flag yet.
|
|
66
|
-
|
|
67
|
-
Both sets are **explicit**, and a test derives the full source inventory from `editors/`
|
|
68
|
-
and asserts every source lands in exactly one of the three buckets. That test exists because
|
|
69
|
-
the same mistake was made twice within a day of each other: `RESUMERS` was first keyed on
|
|
70
|
-
`claude` when rows carry `claude-code`, and `windsurf` was listed as a GUI editor when the
|
|
71
|
-
adapter emits its VARIANTS ids, `devin` and `devin-next` — so every Devin session fell
|
|
72
|
-
through to the *unverified* message, which says the opposite of the truth. A source that
|
|
73
|
-
matches nothing must be a test failure, not a default.
|
|
74
|
-
|
|
75
|
-
And `copilot-cli` was in the GUI set while being, self-evidently, a command line.
|
|
76
|
-
|
|
77
|
-
### Two more decisions
|
|
78
|
-
|
|
79
|
-
- **`FINAL` on the read.** `sessions` is a `ReplacingMergeTree`; without `FINAL` a
|
|
80
|
-
re-shipped session can resolve against its older row and print the folder the project
|
|
81
|
-
*used* to live in.
|
|
82
|
-
- **Ambiguity refuses.** A bare native id can match more than one source. The command lists
|
|
83
|
-
the matches and exits non-zero rather than picking, because resuming the wrong editor's
|
|
84
|
-
session of the same name is precisely the silent-wrong-answer it exists to prevent.
|
|
85
|
-
|
|
86
|
-
### Explicitly not built: write-back
|
|
87
|
-
|
|
88
|
-
Restoring an archived transcript into an editor's own store. Every editor's format is
|
|
89
|
-
private and different, and a bad write corrupts the pilot's live history — risking the
|
|
90
|
-
original to duplicate the copy.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 2. `memhouse update`
|
|
95
|
-
|
|
96
|
-
### The gap
|
|
97
|
-
|
|
98
|
-
`npm i -g memhouse@latest` does half the job, and the other half has cost a real machine
|
|
99
|
-
real sessions:
|
|
100
|
-
|
|
101
|
-
1. **The daemons keep running the old code.** See §3.
|
|
102
|
-
2. **The house can be older than the shipper.** A newer shipper may need a column an
|
|
103
|
-
existing room lacks; it detects that and prints the rebuild, but only if something runs
|
|
104
|
-
it.
|
|
105
|
-
|
|
106
|
-
And from a checkout there is a third: `git pull` updates `ui/src` and leaves the built
|
|
107
|
-
bundle in `public/`, so the dashboard serves the previous release however often it is
|
|
108
|
-
restarted. A published tarball is immune — `prepack` builds `public/` before publish.
|
|
109
|
-
|
|
110
|
-
### Install kinds, because the wrong upgrade is not a no-op
|
|
111
|
-
|
|
112
|
-
| Kind | Detected by | Action |
|
|
113
|
-
|---|---|---|
|
|
114
|
-
| `global` | under `npm prefix -g` | `npm i -g memhouse@latest` |
|
|
115
|
-
| `checkout` | a `.git` at the root | `git pull --ff-only` → `npm install` → `npm run build` |
|
|
116
|
-
| `local-dep` | `node_modules/memhouse`, not under the global prefix | refuse; print the right command for where it lives |
|
|
117
|
-
| `npx` | `_npx` in the resolved path | refuse — the next `npx` resolves the registry anyway |
|
|
118
|
-
|
|
119
|
-
Running `npm i -g` from a checkout would install the **published** version over the branch
|
|
120
|
-
the pilot was testing. Running `git pull` in a global install does nothing and says it
|
|
121
|
-
worked.
|
|
122
|
-
|
|
123
|
-
### Order of operations
|
|
124
|
-
|
|
125
|
-
Read the daemon state **first** (`stop` erases the evidence), upgrade, then restart, then
|
|
126
|
-
ensure the schema. A failed upgrade restarts nothing and says so — the running version is
|
|
127
|
-
left intact rather than half-replaced.
|
|
128
|
-
|
|
129
|
-
A service-managed shipper is not restarted behind the supervisor's back; the command prints
|
|
130
|
-
the supervisor's own restart line.
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 3. A daemon that notices
|
|
135
|
-
|
|
136
|
-
### The gap
|
|
137
|
-
|
|
138
|
-
Nothing tells a running daemon that its files were replaced. `status` prints the version of
|
|
139
|
-
the CLI you just typed, not the version the daemon is executing. On one machine a shipper ran
|
|
140
|
-
**1d16h** out of a directory that had been moved.
|
|
141
|
-
|
|
142
|
-
### Why not `postinstall`
|
|
143
|
-
|
|
144
|
-
npm blocks install scripts by default — the very condition this product already works
|
|
145
|
-
around — so a `postinstall` hook is unreliable exactly where it matters. And a package
|
|
146
|
-
install that restarts a user's daemons is a surprise even when it works.
|
|
147
|
-
|
|
148
|
-
### The mechanism
|
|
149
|
-
|
|
150
|
-
`memhouse/self-update.js`. Each daemon snapshots at boot: the version **read off disk**
|
|
151
|
-
(never `require`d — require caches the first read, which is the value we are trying to
|
|
152
|
-
detect a change in), plus the realpath and mtime of its own entry script. Then:
|
|
153
|
-
|
|
154
|
-
- **shipper** — checks once per loop pass, *after* the sleep and never mid-pass, so an
|
|
155
|
-
upgrade landing during the interval is acted on at the top of the next pass and a
|
|
156
|
-
half-shipped session is never abandoned.
|
|
157
|
-
- **dashboard** — has no pass, so it polls on an unref'd 60s timer. It must be covered: the
|
|
158
|
-
static middleware bound to the old `public/` at boot, so its stale bundle is what the
|
|
159
|
-
pilot actually *sees*.
|
|
160
|
-
|
|
161
|
-
### Two modes, and getting them backwards is expensive
|
|
162
|
-
|
|
163
|
-
| Mode | Signal | Behaviour |
|
|
164
|
-
|---|---|---|
|
|
165
|
-
| supervised | `MEMHOUSE_SUPERVISED=1`, written into the unit by `service install` | **exit 0** — the supervisor starts the new version |
|
|
166
|
-
| unsupervised | `memhouse start` pidfiles | **re-exec** detached, adopt the pidfile, exit |
|
|
167
|
-
|
|
168
|
-
A process that re-execs under a supervisor races the unit's own restart, and two shippers
|
|
169
|
-
on one house clear each other's rows. A process that merely exits with no supervisor stops
|
|
170
|
-
collecting memory entirely.
|
|
171
|
-
|
|
172
|
-
### Guards
|
|
173
|
-
|
|
174
|
-
- **Re-exec chains are counted in the environment** (`MEMHOUSE_REEXEC_CHAIN`) and capped at
|
|
175
|
-
3, so a mis-detection cannot spin.
|
|
176
|
-
- **A 60s floor** from boot before any handover.
|
|
177
|
-
- **The pidfile is only rewritten if it names us.** A daemon started by hand has no pidfile;
|
|
178
|
-
one naming another process belongs to that process, and overwriting it would point
|
|
179
|
-
`memhouse stop` at ours and orphan theirs forever.
|
|
180
|
-
- **A vanished entry is reported, not acted on.** If the directory is gone there is nothing
|
|
181
|
-
to exec and no supervisor to resolve a fresh path, so the daemon keeps running and says so
|
|
182
|
-
every pass. A stale daemon still collects sessions; a dead one collects nothing.
|