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.
Files changed (169) hide show
  1. package/AGENTS.md +22 -151
  2. package/CHANGELOG.md +263 -29
  3. package/LICENSE +202 -22
  4. package/LICENSES/agentlytics-MIT.txt +21 -0
  5. package/LICENSES/memhouse-MIT-until-0.18.11.txt +22 -0
  6. package/LICENSES/models.dev-MIT.txt +22 -0
  7. package/NOTICE +28 -0
  8. package/README.md +42 -20
  9. package/SECURITY.md +21 -8
  10. package/bin/memhouse.js +2223 -311
  11. package/docs/README.md +11 -9
  12. package/docs/guides/admin-password.md +37 -10
  13. package/docs/guides/configuration.md +13 -6
  14. package/docs/guides/daemons.md +64 -3
  15. package/docs/guides/import-claude-ai.md +204 -0
  16. package/docs/guides/install.md +11 -6
  17. package/docs/guides/invite.md +21 -7
  18. package/docs/guides/operate.md +114 -7
  19. package/docs/guides/search-and-resume.md +72 -3
  20. package/docs/guides/share.md +91 -52
  21. package/docs/guides/storage.md +44 -4
  22. package/docs/guides/troubleshooting.md +121 -14
  23. package/docs/guides/uninstall.md +4 -3
  24. package/docs/guides/update.md +16 -0
  25. package/docs/guides/upgrade-to-0.20.md +188 -0
  26. package/docs/releases/README.md +2 -1
  27. package/docs/releases/v0.18.10.md +5 -5
  28. package/docs/releases/v0.18.11.md +3 -3
  29. package/docs/releases/v0.20.0.md +627 -0
  30. package/docs/releases/v0.20.1.md +114 -0
  31. package/docs/tutorials/01-first-house.md +10 -7
  32. package/docs/tutorials/02-join-a-house.md +24 -15
  33. package/docs/tutorials/03-share-your-memory.md +40 -31
  34. package/editors/antigravity.js +1 -80
  35. package/editors/base.js +463 -9
  36. package/editors/bridge.js +119 -0
  37. package/editors/claude.js +273 -105
  38. package/editors/codex.js +278 -52
  39. package/editors/copilot.js +1 -70
  40. package/editors/cursor.js +1 -75
  41. package/editors/derive-fingerprints.json +18 -0
  42. package/editors/harness-paths.js +69 -0
  43. package/editors/index.js +1 -20
  44. package/editors/normalise/agy.js +910 -0
  45. package/editors/normalise/copilot-cli.js +321 -0
  46. package/editors/normalise/cursor-agent.js +852 -0
  47. package/editors/normalise/cursor.js +474 -0
  48. package/editors/normalise/devin.js +575 -0
  49. package/editors/normalise/gemini-cli.js +602 -0
  50. package/editors/normalise/goose.js +673 -0
  51. package/editors/normalise/opencode.js +633 -0
  52. package/editors/normalise/vscode.js +549 -0
  53. package/editors/normalise/zed.js +473 -0
  54. package/editors/opencode-v4.js +119 -0
  55. package/editors/scope.js +14 -3
  56. package/editors/sessions-log.js +349 -0
  57. package/editors/sqlite.js +28 -1
  58. package/editors/vscode.js +1 -72
  59. package/editors/windsurf.js +1 -143
  60. package/examples/01-local-one-machine/README.md +24 -19
  61. package/examples/01-local-one-machine/run.sh +1 -1
  62. package/examples/02-existing-clickhouse/README.md +30 -22
  63. package/examples/02-existing-clickhouse/run.sh +5 -4
  64. package/examples/03-team-house/README.md +44 -37
  65. package/examples/03-team-house/run.sh +35 -18
  66. package/examples/04-self-hosted-team/README.md +29 -14
  67. package/examples/04-self-hosted-team/compose.yml +5 -2
  68. package/examples/04-self-hosted-team/smoke.sh +26 -12
  69. package/examples/README.md +8 -4
  70. package/examples/fixtures/README.md +4 -1
  71. package/examples/fixtures/make-sessions.js +8 -5
  72. package/memhouse/admin-secret.js +89 -24
  73. package/memhouse/argv-secrets.js +64 -0
  74. package/memhouse/channel.js +49 -9
  75. package/memhouse/codex-plugin.js +194 -0
  76. package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +2 -1
  77. package/memhouse/delivery/AGENT-INSTALL.md +22 -11
  78. package/memhouse/delivery/INVITATION.md.tpl +44 -45
  79. package/memhouse/delivery/PROMPT.md +53 -5
  80. package/memhouse/delivery/UPGRADE.md.tpl +1 -1
  81. package/memhouse/delivery/codex/AGENTS-BLOCK.md +11 -0
  82. package/memhouse/delivery/codex/SKILL.md +50 -0
  83. package/memhouse/delivery/kernel-install.md +17 -9
  84. package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
  85. package/memhouse/delivery/plugin/reference/HOUSE.md +168 -41
  86. package/memhouse/delivery/plugin/skills/access/SKILL.md +13 -5
  87. package/memhouse/delivery/plugin/skills/admin/SKILL.md +14 -6
  88. package/memhouse/delivery/plugin/skills/house/SKILL.md +25 -8
  89. package/memhouse/delivery/plugin/skills/recall/SKILL.md +66 -8
  90. package/memhouse/delivery/plugin/skills/sql/SKILL.md +66 -7
  91. package/memhouse/deploy.js +1 -1
  92. package/memhouse/flags.js +35 -18
  93. package/memhouse/help.js +37 -0
  94. package/memhouse/host.js +82 -2
  95. package/memhouse/house/HOUSE.md +2 -2
  96. package/memhouse/house/convert-profile.js +638 -0
  97. package/memhouse/house/convert-v3.js +742 -0
  98. package/memhouse/house/doctor-v3.js +324 -0
  99. package/memhouse/house/host-alias.js +85 -0
  100. package/memhouse/house/house.js +3 -3
  101. package/memhouse/house/migrate.js +2 -2
  102. package/memhouse/house/profile-dedupe.js +183 -0
  103. package/memhouse/house/profile-edges.js +554 -0
  104. package/memhouse/house/profile-house.js +430 -0
  105. package/memhouse/house/profile-rollup.js +190 -0
  106. package/memhouse/house/q-cli.js +863 -0
  107. package/memhouse/house/queries-v3.js +1013 -0
  108. package/memhouse/house/reconcile-v3.js +192 -0
  109. package/memhouse/house/records-to-profile.js +174 -0
  110. package/memhouse/house/records-to-rows.js +169 -0
  111. package/memhouse/house/relocate.js +2 -1
  112. package/memhouse/house/schema-choice.js +121 -0
  113. package/memhouse/house/schema-profile.js +290 -0
  114. package/memhouse/house/schema-profile.sql.tpl +398 -0
  115. package/memhouse/house/schema-v3.js +267 -0
  116. package/memhouse/house/schema-v3.sql.tpl +213 -0
  117. package/memhouse/import/claude-ai.js +420 -0
  118. package/memhouse/import/import.js +372 -0
  119. package/memhouse/import/pyjson.js +206 -0
  120. package/memhouse/import/records.js +64 -0
  121. package/memhouse/import/writer-profile.js +192 -0
  122. package/memhouse/import/writer-v3.js +240 -0
  123. package/memhouse/import/zip.js +112 -0
  124. package/memhouse/invitation.js +102 -0
  125. package/memhouse/invitefile.js +5 -2
  126. package/memhouse/plugin-copies.js +176 -0
  127. package/memhouse/provision.js +7 -2
  128. package/memhouse/release-hint.js +24 -0
  129. package/memhouse/resume.js +4 -4
  130. package/memhouse/server/api-v3.js +632 -0
  131. package/memhouse/server/dashboard-auth.js +181 -0
  132. package/memhouse/server/dashboard-port.js +91 -0
  133. package/memhouse/server/queries.js +30 -0
  134. package/memhouse/server/server.js +118 -20
  135. package/memhouse/service.js +1 -1
  136. package/memhouse/share.js +687 -1
  137. package/memhouse/shipper/adapter-capture.js +756 -0
  138. package/memhouse/shipper/capture.js +2571 -0
  139. package/memhouse/shipper/dedupe-profile.js +237 -0
  140. package/memhouse/shipper/dedupe-v3.js +1027 -0
  141. package/memhouse/shipper/forks.js +233 -0
  142. package/memhouse/shipper/log-adapter.js +219 -0
  143. package/memhouse/shipper/maintenance.js +49 -0
  144. package/memhouse/shipper/normalise-run.js +148 -0
  145. package/memhouse/shipper/passlog.js +68 -5
  146. package/memhouse/shipper/profile-capture.js +903 -0
  147. package/memhouse/shipper/ship.js +91 -8
  148. package/memhouse/shipper/snapshots.js +193 -0
  149. package/memhouse/shipper/source-roots.js +111 -0
  150. package/memhouse/version-notice.js +66 -0
  151. package/package.json +14 -7
  152. package/public/THIRD_PARTY_NOTICES.txt +2 -2
  153. package/public/assets/index-CcNnhfM3.js +78 -0
  154. package/public/index.html +1 -1
  155. package/ui/package-lock.json +56 -37
  156. package/ui/package.json +1 -0
  157. package/ui/src/App.jsx +29 -14
  158. package/ui/src/components/ChatSidebar.jsx +69 -12
  159. package/ui/src/lib/api.js +76 -0
  160. package/ui/src/lib/dashboard-token.js +41 -0
  161. package/ui/src/main.jsx +22 -5
  162. package/ui/src/pages/Dashboard.jsx +22 -36
  163. package/ui/third-party-notices.js +119 -0
  164. package/ui/vite.config.js +5 -1
  165. package/docs/releases/v0.18.12.md +0 -59
  166. package/memhouse/COMPETITION.md +0 -221
  167. package/memhouse/DESIGN.md +0 -222
  168. package/memhouse/RESUME-AND-UPDATE.md +0 -182
  169. package/public/assets/index-CnWzrjF3.js +0 -78
@@ -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.