memhouse 0.18.8 → 0.18.10

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 (45) hide show
  1. package/AGENTS.md +194 -0
  2. package/CHANGELOG.md +970 -0
  3. package/README.md +54 -423
  4. package/SECURITY.md +68 -0
  5. package/bin/memhouse.js +202 -92
  6. package/docs/README.md +46 -0
  7. package/docs/guides/admin-password.md +95 -0
  8. package/docs/guides/configuration.md +109 -0
  9. package/docs/guides/daemons.md +53 -0
  10. package/docs/guides/install.md +128 -0
  11. package/docs/guides/invite.md +87 -0
  12. package/docs/guides/operate.md +112 -0
  13. package/docs/guides/search-and-resume.md +95 -0
  14. package/docs/guides/share.md +102 -0
  15. package/docs/guides/storage.md +90 -0
  16. package/docs/guides/troubleshooting.md +78 -0
  17. package/docs/guides/uninstall.md +35 -0
  18. package/docs/guides/update.md +68 -0
  19. package/docs/releases/README.md +10 -0
  20. package/docs/releases/v0.18.10.md +226 -0
  21. package/docs/tutorials/01-first-house.md +132 -0
  22. package/docs/tutorials/02-join-a-house.md +91 -0
  23. package/docs/tutorials/03-share-your-memory.md +96 -0
  24. package/examples/01-local-one-machine/README.md +99 -0
  25. package/examples/01-local-one-machine/run.sh +61 -0
  26. package/examples/02-existing-clickhouse/README.md +121 -0
  27. package/examples/02-existing-clickhouse/run.sh +71 -0
  28. package/examples/03-team-house/README.md +129 -0
  29. package/examples/03-team-house/run.sh +124 -0
  30. package/examples/04-self-hosted-team/.env.example +22 -0
  31. package/examples/04-self-hosted-team/README.md +187 -0
  32. package/examples/04-self-hosted-team/compose.yml +158 -0
  33. package/examples/04-self-hosted-team/smoke.sh +113 -0
  34. package/examples/README.md +42 -0
  35. package/examples/fixtures/README.md +24 -0
  36. package/examples/fixtures/make-sessions.js +63 -0
  37. package/memhouse/admin-secret.js +81 -0
  38. package/memhouse/delivery/plugin/.claude-plugin/plugin.json +1 -1
  39. package/memhouse/deploy.js +4 -2
  40. package/memhouse/flags.js +4 -4
  41. package/memhouse/provision.js +12 -3
  42. package/memhouse/server/queries.js +18 -4
  43. package/memhouse/server/server.js +9 -1
  44. package/memhouse/service.js +5 -0
  45. package/package.json +10 -2
package/CHANGELOG.md ADDED
@@ -0,0 +1,970 @@
1
+ # Changelog
2
+
3
+ Versions before 0.8.0 were beta-only. Beta installs of 0.7.x and earlier should
4
+ uninstall and reinstall — the 0.8.0 layout is new, and an in-place `update` +
5
+ migration path is planned work, not a promise the old versions can cash.
6
+
7
+ ## 0.18.10 — 2026-10-08
8
+
9
+ Release notes: [`docs/releases/v0.18.10.md`](docs/releases/v0.18.10.md), covering what
10
+ changed, the upgrade steps, and the status of the 0.18.10 pre-tag list.
11
+
12
+ - **A security release; everyone on 0.18.x should upgrade.** It covers G23 (#20), the admin
13
+ password off argv (#14) and the scoped share boundary (#13).
14
+ - **No drill was run for this release; waived by the pilot (2026-10-08). The drill runs before 0.19.0.**
15
+ - **Moved to 0.18.11:** the subagent re-ship fix and the gentar kit v0.9.3 bump.
16
+ - **Docs:** the repository now meets the documentation standard (#17): a short README,
17
+ `docs/` tutorials and guides, `examples/`, and the `AGENTS.md` install section.
18
+
19
+ - **No command prints a member password (G23).**
20
+ - `install --admin-user` used to show the generated password "once". It went into the
21
+ env file anyway, and the terminal copy landed in scrollback and, through `/mem:access`
22
+ and `/mem:admin`, in a transcript memhouse ships into the house. It is no longer shown.
23
+ - If the grant then fails, the user that run created is dropped, so no account is left
24
+ with a password nobody holds.
25
+ - `install --print-sql` and `invite --print-sql` print `IDENTIFIED BY <member-password>`
26
+ in place of a password. The placeholder does not parse until a quoted password replaces
27
+ it. They also print the env-file lines to hand over, with `MEMHOUSE_INVITE=1` from
28
+ `invite`, so the member's `install --env` offers rotation. A `--member-password` given
29
+ with `--print-sql` is not echoed.
30
+ - `misc/invite-matrix.sh` checks the output of every install, invite and passwd path
31
+ against the real passwords. A unit test fails on any print call that interpolates a
32
+ password variable.
33
+ - **Docs:** `memhouse passwd --member` does not exist. `--member` was accepted and ignored
34
+ there; the guides said otherwise and are corrected.
35
+
36
+ - **An admin password never has to go on the command line.** `install --member`, `invite`,
37
+ `passwd`, `convert` and `members` take it from `--admin-password-file <file>` (or `-` for
38
+ stdin), from `MEMHOUSE_ADMIN_PASSWORD` (so `with-secret` lends it to one command), from a
39
+ credential this install already keeps for the same admin, or from a prompt. Before, giving
40
+ `--admin-user` as a flag switched every other source off, so the only scriptable admin
41
+ path put the password on argv, where `ps` and shell history keep it. Found by O's sandbox
42
+ rehearsal. **Deprecation:** `--admin-password <value>` still works in this release, with a
43
+ warning, and is **removed in 0.19.0**, the next minor release after this one (deprecated
44
+ 2026-10-08). Move scripts to `MEMHOUSE_ADMIN_PASSWORD` or `--admin-password-file`.
45
+ - **An admin install no longer saves the admin credential**, unless `--keep-admin` (or the
46
+ env file already keeps that same admin). The help said it was saved; every agent-facing
47
+ text (the install prompt, invite's advice, `/mem:admin`) said it never was. Credentials
48
+ already saved keep working.
49
+ - **A daemon never carries the admin credential.** Service units drop `MEMHOUSE_ADMIN_*`
50
+ before inlining the env, and the shipper and dashboard are started without it even when
51
+ the shell exported it. `deploy --local` hands the ClickHouse password to `docker run` by
52
+ environment, not as `-e CLICKHOUSE_PASSWORD=<value>` on its argv. A unit test now fails on
53
+ any spawn/exec in the shipped code that passes a password in argv.
54
+
55
+ - **A scoped share now shares only what its scope filters.** `memhouse share <user> --only
56
+ <scope>` granted `SELECT` on the member's whole pattern (`mem.<member>_*`) while its row
57
+ policies covered only `sessions`, `messages` and `tool_calls`. The grantee could therefore
58
+ read `<member>_session_stats` in full — the opening prompt, folder, project and token totals
59
+ of EVERY session in every project — plus the model and tool stats, `meta`, `events`, and any
60
+ room the pattern matched later. A scoped share is now granted table by table, on exactly the
61
+ three filtered rooms; narrowing an existing full share takes the wildcard back first. A full
62
+ share (no `--only`) is unchanged and still reaches everything, by design. **Existing scoped
63
+ shares keep their old wildcard until re-run:** `memhouse share <user> --only <scope>` again
64
+ narrows it. Found by an agent reading the code.
65
+ - **`--revoke` withdraws the grant before it drops the filters.** The old order failed open: a
66
+ `REVOKE` refused after the policies were gone left an unfiltered share behind.
67
+ - **`--only ""` is refused.** An empty scope (`--only "$SCOPE"` with the variable unset) used
68
+ to fall through to a full share.
69
+ - **The permissive-server check before a scoped share actually runs.** It built its probe
70
+ table outside the member's one-layout grant, was refused on every server, and always
71
+ reported "could not verify"; the table now lives inside `mem.<member>_*`.
72
+
73
+ ## 0.18.9 — 2026-09-22
74
+
75
+ - **The Sessions page rendered blank on a house with ~900+ sessions.** It asks for
76
+ `/api/chats?limit=1000`; that returned HTTP 500 with `{"error":""}`, and nothing in the
77
+ dashboard log. Cause: per-chat costs are scoped by passing every id on the page as
78
+ `{scopeIds:Array(String)}` query parameters, and the driver puts query parameters in the
79
+ request URI — about 60 bytes per id, twice. Well under ClickHouse's own
80
+ `http_max_uri_size`, but above what the Cloudflare tunnel in front of a public house
81
+ accepts: 900 sessions worked, 925 did not. The scope is chunked at 250 now, so the URI
82
+ stays small whatever the page size; costs are unchanged (byte-identical on a 50-row page),
83
+ and 1,506 sessions load in 0.56 s.
84
+ - **An error with no message now says something.** The ClickHouse driver reports a
85
+ transport-level refusal as `new Error('')` — no message, no code, no own keys — which
86
+ `route()` passed straight through as `{"error":""}`. It now logs the method and path and
87
+ names the class of failure (a proxy refusing the request: URI too long, body too large,
88
+ timeout) instead of handing the reader an empty string.
89
+
90
+ ## 0.18.8 — 2026-09-22
91
+
92
+ - **A growing session re-ships only its tail.** The shipper re-sent the whole transcript on
93
+ every pass, and ReplacingMergeTree kept every copy as a version — 1.79 copies of the
94
+ average row on a real house, seven of the worst, all paid for by every `FINAL` read. Rows
95
+ whose stored `line_hash` matches are now dropped from the send; only new turns travel.
96
+ Withheld (whole re-ship as before) when a timestamp was interpolated, the host changed,
97
+ or the folder moved — the three shipped columns the hash cannot see. `--full` is
98
+ untouched. Measured: an unchanged session writes 0 rows; a session grown by two turns
99
+ writes 2, where it wrote its entire 24 before. Known limit: a session with folded
100
+ subagents still forks on growth, because appending a parent turn shifts every subagent
101
+ row's `seq` — that is the fold order, not this change, and it forked the same way before.
102
+
103
+ ## 0.18.7 — 2026-09-19
104
+
105
+ - **`status` and `doctor` say when an update is out.** `update --check` always existed and
106
+ nobody ran it: a member sat four days behind the release that fixed the very thing they
107
+ were about to hit, with nothing on any screen saying so. `status` now prints one line,
108
+ `doctor` a `✗` with the command. Silent when current, when the registry does not answer,
109
+ when this build is ahead of its tag, and for a checkout or tarball on no tag — "unknown"
110
+ must never read as "you are behind" on a screen someone gates on.
111
+
112
+ ## 0.18.6 — 2026-09-19
113
+
114
+ The upgrade path itself, and a machine that stays the same machine.
115
+
116
+ - **An upgrade no longer leaves a broken window.** `update` restarted the daemons before
117
+ bringing the house to the new schema, so a release that adds rooms (0.18.3's stat
118
+ tables) produced failed passes, a `status` quoting them, and a dashboard whose every
119
+ request 500'd behind a page that merely looked blank — until something happened to run
120
+ `ship --ensure-schema`. Two real members hit this within an hour. `update` now ensures
121
+ the schema in the one moment the daemons are already down, and says so.
122
+ - **A converted house is complete when `convert` finishes.** It moved the rooms but left
123
+ the dashboard's per-member stat tables to `ensureSchema`, which a plain `ship` never
124
+ calls. `convert` creates them per member as the admin; the matrix asserts it.
125
+ - **The dashboard adopts a moved house.** The shipper and `update` learned this in 0.18.4;
126
+ the server did not, so after a conversion it answered 500 from the database its env
127
+ named at boot. It now checks once at startup and switches to `mem`, saying which.
128
+
129
+ - **A machine keeps its host identity across reinstalls.** The fingerprint was
130
+ `randomBytes(16)` living only in `host.json`, so wiping `MEMHOUSE_HOME` — an
131
+ `uninstall --full-removal`, a reset, a re-imaged laptop — brought the same physical
132
+ machine back as a stranger: one MacBook read as three writers in the fleet list, each
133
+ holding a slice of one machine's history, and none of them removable without deleting
134
+ rows. It is now derived from the machine's own id (IOPlatformUUID, `/etc/machine-id`,
135
+ MachineGuid) hashed with a fixed salt, so the raw id never leaves the machine and cannot
136
+ be recovered from a row; where none can be read it still falls back to random. An
137
+ existing `host.json` always wins, so no install in the field changes identity.
138
+
139
+ ## 0.18.5 — 2026-09-18
140
+
141
+ The first release on **one line**: published to `latest` and `team` alike. zeo was converted to the one layout the same evening with `memhouse convert` (six members, every row count identical before and after, shares carried).
142
+
143
+ - **One release line.** With zeo converted to the one layout there is no 0.17 line to keep:
144
+ `latest` now points at the same 0.18.x build as `team` (kept as an alias), the publish
145
+ guard and `AGENTS.md` say so, and `release/0.17` is retired.
146
+
147
+ - **`memhouse convert` — an operator moves a pre-one-layout house to the one layout.**
148
+ Before 0.18 each member owned a database (`polat.messages`, `ALL ON polat.*`). `convert`
149
+ finds every such member, renames their five rooms into `mem.<name>_*` in one atomic
150
+ statement (nothing copied), swaps the database-wide grant for the one wildcard, carries
151
+ whole-house shares and row policies to the new names, drops the 0.17.1 refreshable
152
+ views (the shipper fills stats in the one layout), records an event in the member's
153
+ events room, and drops the emptied database. `--dry-run`/`--print-sql` show the plan;
154
+ `--guides --out DIR` renders `MEMHOUSE-UPGRADE-<member>.md` per member.
155
+ - **A member's env repairs itself after the move.** `memhouse update` and the shipper's
156
+ startup both notice that `<db>.messages` is gone and `mem.<name>_messages` exists, and
157
+ rewrite `MEMHOUSE_DB=mem`; `doctor` names it. So the member's whole upgrade is one
158
+ command. Rehearsed by `misc/convert-matrix.sh`: a real 0.17.1-built house on 26.7,
159
+ converted, shares intact, isolation intact, the member's old env adopted, 24/24.
160
+
161
+ ## 0.18.4 — 2026-09-17
162
+
163
+ On the `team` channel. Found by the operator's own second machine on its first `update`.
164
+
165
+ - **`memhouse update` never crosses release lines on its own.** An unpinned 0.18.2 whose
166
+ `team` tag had moved to 0.18.3 matched no tag, and the picker fell back to `latest` —
167
+ zeo's 0.17.1, a different house layout. A real member was downgraded on her first update.
168
+ The picker now follows the tag on the install's own major.minor line, and when no tag is
169
+ on that line, or the registry does not answer, it stops and says so; `--channel` is the
170
+ only way across.
171
+ - **`install --env` keeps the invite's channel, editors and roots.** The hoist copied only
172
+ URL/USER/PASSWORD/DB, so a joined env had no `MEMHOUSE_CHANNEL` even though the invite
173
+ carried one. The invite matrix asserts the joined env keeps it.
174
+
175
+ ## 0.18.3 — 2026-09-17
176
+
177
+ On the `team` channel.
178
+
179
+ - **Folded subagent turns say which subagent they came from.** Claude Code forks
180
+ (`<session>/subagents/agent-<id>.jsonl`) were folded into the parent with only a
181
+ `[subagent]` tag, so one fork's transcript could not be isolated or cited, and an agent
182
+ looking for the fork as its own session concluded it was never shipped. Each folded turn
183
+ now carries `extra.agent.{id, description, type, turn}` — the description is what the
184
+ parent's `Agent` call asked for, joined through the tool result that names the agentId.
185
+ A fork the parent never named — a Workflow-run agent, or an older parent — is described
186
+ by its own first prompt. `extra` is not part of the line hash, so nothing already shipped
187
+ is re-shipped; rows from before this carry `{}` until their session ships again (or
188
+ `memhouse ship --full`). The plugin reference and `/mem:recall` say how to read a fork.
189
+ - **Workflow-run agents ship.** Agents spawned by the Workflow tool live one level deeper,
190
+ `<session>/subagents/workflows/wf_<id>/agent-<id>.jsonl`, and the fold never looked
191
+ there: on two real playbooks, 99 of 164 forks had never reached the house. They fold like
192
+ the rest, with `extra.agent.workflow` naming the run. Sessions with them ship a new epoch
193
+ on their next pass (their text changes); the journal and meta files are not transcripts.
194
+
195
+ - **Bare `memhouse` beside an invite offers to process it.** With an `invite-<name>.env` in
196
+ the current directory, `memhouse` with no command reads what it promises — house, member,
197
+ server, without the password — and asks whether to join, instead of printing help. `--yes`
198
+ joins unattended; a house already configured is only replaced on an explicit yes. It
199
+ scans the current directory only, never `~/Downloads`, so invitations to other houses do
200
+ not surface or auto-join by accident. With no invite present, the help screen as before.
201
+
202
+ - **A playbook is bound to the instance that installed its plugin.** `plugins install
203
+ claude` from an instance that ships specific playbooks installs into exactly those, and
204
+ stamps `MEMHOUSE_HOME` and `MEMHOUSE_BIN` into each playbook's `settings.json` `env`, so
205
+ every session under it reads that instance's house and runs that instance's binary
206
+ rather than whatever the shell inherited. The skills say so in their reference and use
207
+ `${MEMHOUSE_BIN:-memhouse}`. `plugins list` shows the binding, `plugins remove` removes
208
+ it, and nothing else in `settings.json` is touched.
209
+
210
+ - **The dashboard reads precomputed rollups.** Every dashboard aggregate used to rebuild
211
+ the session rollup — `<member>_sessions FINAL` joined to the current parse of
212
+ `<member>_messages`, 23 aggregate expressions, grouped over the whole house — on every
213
+ query, and the cost, model and tool queries scanned the message and tool rooms on top.
214
+ On a 714k-message house one such query read 1.4M rows and held 117 MiB; a page load was
215
+ 4–10 seconds. Three tables per member now hold the rollups —
216
+ `<member>_session_stats`, `<member>_session_model_stats`, `<member>_session_tool_stats`
217
+ — and the dashboard reads those (a few thousand rows). A page load is ~250 ms.
218
+ **Filled by the shipper at the end of every pass, not by a materialized view:** a
219
+ refreshable view swaps its target through a temporary table and needs
220
+ `CREATE TABLE … ON <db>.*`, the database-wide grant this layout exists to withhold
221
+ (measured on 25.8). Each refresh is a complete snapshot stamped with one `refreshed_at`;
222
+ reads take the newest generation; a one-day TTL retires the rest. Created by `ship
223
+ --ensure-schema` under the member's own `<db>.<member>_*` grant — so `memhouse install`
224
+ and `memhouse update` both do it, and a house without them (or with empty, not yet
225
+ refreshed ones) falls back to the inline rollup — slow, never wrong.
226
+ - **Every endpoint issues its reads in one round trip.** `/api/dashboard-stats` awaited
227
+ nine queries in sequence; two thirds of what the browser waited for was network.
228
+ - **`/api/chats` prices the page, not the house.** The cost query priced all sessions to
229
+ decorate fifty.
230
+ - Two latent ordering bugs fixed: the session list's `ORDER BY` was not a total order
231
+ (paginated pages could repeat or skip a row on a timestamp tie), and top-N cuts resolved
232
+ count ties by arrival order. Both tiebreak deterministically now.
233
+
234
+ ## 0.18.2 — 2026-09-11
235
+
236
+ On the `team` channel. Two bugs found by a two-member arrival drill on 0.18.1, plus what
237
+ the drill taught:
238
+
239
+ - **The shipper spawned by `install --env` now holds the rotated password.** It held the
240
+ invite's, which the rotation had just killed, and failed every pass with
241
+ "Authentication failed" while every CLI command worked. The shipper also adopts a newer
242
+ env-file credential on an auth failure instead of backing off on a dead one.
243
+ - **`status` and `doctor` read the shipper log's last pass.** A daemon failing every pass
244
+ was "running" by pid and green in doctor. Now: `running — but the last pass FAILED — <why>
245
+ — restart it: memhouse stop && memhouse start`, and a `✗` in doctor.
246
+ - The invitation guide says what to do when an agent's `npm install -g` is blocked by a
247
+ permission classifier: the person runs step 1, the agent continues from step 2.
248
+ - `drills/DRILLBOOK-team-arrival-2026-09-10.md` and the drill record.
249
+
250
+ ## 0.18.1 — 2026-09-08
251
+
252
+ On the `team` channel. Everything since 0.18.0, none of it reaching `latest` (0.17.0):
253
+
254
+ - `memhouse instance` — which memhouse this is on one screen; `/mem:house` opens with it.
255
+ - Playbook binding — `plugins install` stamps `MEMHOUSE_HOME`/`MEMHOUSE_BIN` into the
256
+ playbooks an instance ships, so their skills read that instance's house and binary.
257
+ - Bare `memhouse` beside an `invite-<name>.env` offers to join; `--yes` joins unattended.
258
+ The invitation guide points the invitee, or their agent, at the sibling credential file.
259
+
260
+ ## 0.18.0 — 2026-09-08
261
+
262
+ Published on the `team` channel (`npm install -g memhouse@team`). `latest` stays 0.17.0:
263
+ this release speaks one room layout, and a house from before it is refused with the renames
264
+ that convert it rather than written into. Everything below is that layout and what it made
265
+ possible.
266
+
267
+ - **`memhouse update` follows the channel the install came from.** It used to install
268
+ `memhouse@latest` unconditionally, which would have downgraded a house running a build
269
+ published under another dist-tag, and replaced a tarball install with whatever latest
270
+ was. Now the channel is pinned (`MEMHOUSE_CHANNEL`, or `--channel`, which writes it) or
271
+ inferred from the installed version's tag; a version on no tag gets no automatic update
272
+ and is told why. An invite from a house on a channel carries it into the invitee's env
273
+ file, and the invitation guide says `npm install -g memhouse@<channel>`.
274
+
275
+ - **`invite` writes `MEMHOUSE-INVITATION.md` beside the credential.** The invitee gets two
276
+ files: the one-time `.env`, and a guide with nothing secret in it — install, join,
277
+ verify, what they own and who can see it, things to try, which sessions ship, and what
278
+ the two refusals they might meet mean. Rendered rather than linked because it names
279
+ their file, this house, and (until the build is on npm) the tarball to install. The
280
+ matrix asserts the guide exists, names their rooms, carries no password, and has no
281
+ unrendered placeholder.
282
+
283
+ - **The bookkeeping rooms are `<member>_meta` and `<member>_events`.** They were
284
+ `<member>_house_meta` / `<member>_house_events`, a name from when the house was the unit
285
+ of ownership. Every row in them is about one member — their schema version, their
286
+ machines, their last ship, their shares — so the old prefix named the wrong unit. A
287
+ house from before the one-layout still holds `house_meta` / `house_events`, and the
288
+ shipper's legacy guard names that rename alongside the other three.
289
+
290
+ - **Choose which sessions ship.** `MEMHOUSE_EDITORS` names the adapters to run, and
291
+ `MEMHOUSE_<EDITOR>_ROOTS` moves one adapter's location — `MEMHOUSE_CLAUDE_ROOTS` for
292
+ the Claude Code config directories, `MEMHOUSE_CODEX_ROOTS` for Codex's home, one per
293
+ adapter, named from the adapter. A machine with several Claude Code instances can ship
294
+ one of them into a team house and leave the rest alone. `memhouse discover` prints what
295
+ every adapter is watching and the variable that changes it. A name or directory that
296
+ does not exist is refused against that adapter, never shipped from the default instead.
297
+
298
+ - **One layout.** Every member's rooms are named for them — `mem.polat_messages`,
299
+ `mem.alice_messages` — and one grant covers them: `GRANT … ON mem.<name>_* TO <name>
300
+ WITH GRANT OPTION`. A wildcard on the member's own name, verified on 25.11 and 26.7: it
301
+ lets the member create and rebuild their own rooms and share them, and reaches nothing
302
+ else — not a housemate's rooms to read, list, drop or re-grant. Nobody is granted the
303
+ database. A house with one member is a house of one, not a different kind of house.
304
+
305
+ This replaces the two layouts of the previous unreleased work (a house per member with
306
+ `ALL ON db.*`, and a prefixed variant beside it) together with everything that existed
307
+ to keep them apart: the one-owner invariant, the fencing step, `--shared-db`,
308
+ `--table-prefix`, `MEMHOUSE_TABLE_PREFIX`, the operator creating rooms at invite, and a
309
+ layout branch in eight code sites and nine documents. The database name is `mem` unless
310
+ somebody has a reason; `--db` remains for that reason.
311
+
312
+ - **One provisioning plan.** `memhouse/provision.js` is the only description of what a
313
+ member is granted. The live path executes it, `--print-sql` renders it, the unit tests
314
+ assert on it. Three copies of this used to exist and had drifted — the printed one
315
+ handed a non-admin the configuration the live path refused.
316
+
317
+ - **Standalone is a team of one.** `deploy --local` creates the container with an admin
318
+ credential (`memhouse_root`) and a member named after your OS user, keeps both in
319
+ `~/.memhouse/env`, and ships as the member. `invite`, `members` and `whoami --admin`
320
+ use the admin credential from the file, so a house you deployed needs no `--admin-*`
321
+ flags. A volume from before this release was initialised with the member as superuser
322
+ and is reused as it is.
323
+
324
+ - **Houses from before this layout are refused, loudly.** A shipper that finds plain rooms
325
+ (`messages`, not `<name>_messages`) stops and prints the five `RENAME TABLE` statements
326
+ that move them across — instant, nothing copied — rather than creating an empty second
327
+ set beside them and hiding every past session. Conversion of an existing house is a
328
+ rename plus one grant swap; `doctor --fix` for that is the next change, not this one.
329
+
330
+ - `memhouse members` reads wildcard grants on servers without the `is_wildcard` column
331
+ (25.11, the default local tag).
332
+
333
+ - **Bringing a housemate into a house you already own now works, and moves nothing.** A
334
+ member alone in their own database holds `ALL ON polat.*`. That grant is **dynamic** —
335
+ it covers rooms created later — so a housemate's rooms would be readable *and
336
+ droppable* by the owner the moment they existed (both measured). `invite` used to
337
+ refuse and point at a migration command that did not exist.
338
+
339
+ It now **fences** the sitting owner instead: a `REVOKE` plus one `GRANT` per room they
340
+ already have. Their rooms keep their names, their data does not move, there is no
341
+ rename, no copy, no re-ship and no downtime — and an admin credential still reads the
342
+ whole house, which is what an operator has.
343
+
344
+ The order is REVOKE-then-GRANT, which is the opposite of what looks safe and the only
345
+ one that works: `REVOKE ALL ON db.*` covers every table beneath it, so per-room grants
346
+ issued first are wiped by it. Doing it the intuitive way locked a member out of their
347
+ own memory — caught on a real server, now a test.
348
+
349
+ - Reading across members is `UNION ALL` over the rooms you hold, not a `merge()` pattern.
350
+ The anchored pattern the README carried (`'^.*_messages$'`) silently dropped the house
351
+ owner's own rooms, which are unprefixed — the one person most likely to run a team
352
+ query saw everyone's memory except their own.
353
+
354
+ - **`share --only project=x` granted every project, then failed to scope.** The worst
355
+ defect in this branch, and it was reachable by any member of a shared house. `share`
356
+ granted `SELECT` on all rooms first and built the row-policy filters second; the
357
+ prefixed layout never granted the row-policy rights (`ALL ON db.*` carries them, an
358
+ explicit per-table list does not), so the scoping step could not succeed at all. A
359
+ member asking to share ONE project handed over ALL of them, and `share --list` then
360
+ reported "nobody has been granted a read" — the bookkeeping write happens after the step
361
+ that failed.
362
+
363
+ Now the filters are built BEFORE anything is granted, a failure drops what it built and
364
+ grants nothing, and members hold `CREATE/ALTER/DROP/SHOW ROW POLICY` on their own rooms.
365
+ The matrix asserts both halves — that a scoped share exposes only the named project, and
366
+ that a scoping failure leaves the grantee with exactly what they had before.
367
+
368
+ Found in a drill: an agent asked to open one project to a colleague, which no matrix
369
+ did because every matrix shares whole rooms.
370
+
371
+ - **`invite --print-sql` printed the configuration the live path refuses.** It ignored
372
+ `--shared-db` and `--table-prefix` entirely and emitted unprefixed shared rooms plus
373
+ `GRANT ALL ON db.* TO <member> WITH GRANT OPTION` — the shape that lets any member read
374
+ a housemate's rows and grant them to an outsider. The help routes non-admins to exactly
375
+ this path ("Not an admin? `--print-sql` gives the statements to hand to whoever is"), so
376
+ the one person who could not check the result was handed the leak, confidently, at exit
377
+ 0. It now honours the prefix, emits per-room grants and no database-wide grant, and the
378
+ matrix RUNS the printed SQL and asserts it isolates identically to the live path.
379
+
380
+ Found by a drill — an agent given an admin credential, a database, and two colleagues to
381
+ set up, which is the only instrument that reads the output instead of the code.
382
+
383
+ - **`memhouse members`** — who is in a house, and what each of them reaches. Before shared
384
+ houses a database had exactly one member and the question did not exist; this layout
385
+ creates it, and the operator had no supported way to answer it. Warns when one account
386
+ holds the whole database while others hold rooms in it — memhouse cannot create that
387
+ shape, but a hand-written `GRANT` can.
388
+
389
+ - Help fixes the same drill turned up: the `--shared-db` block had been spliced through the
390
+ middle of the sentence "write the env **file their install needs**", and four lines
391
+ describing `install` were hanging under `passwd`.
392
+
393
+ - `invite` no longer prints "5 room(s) already exist and are not yours to create" while
394
+ verifying a member it provisioned seconds earlier. It reads as a name collision on a
395
+ database that was empty a moment ago, and sent one operator to `system.tables` to find
396
+ out what had gone wrong. Nothing had.
397
+
398
+ - **Two members can no longer share one database with a grant each — it leaked.** Until
399
+ now `invite bob --db alices-house --adopt` put both in one database with `ALL ON db.*`
400
+ apiece, and the README taught it as the way to run a team. Measured on a real server:
401
+ alice runs `GRANT SELECT ON team.* TO carol` and carol reads **bob's** messages. No
402
+ admin involved, nothing written to any log, bob never told.
403
+
404
+ `invite` now enforces one invariant: **a database is one member's house, or it holds
405
+ per-member rooms — never both, and never two database-wide owners.** `--adopt` does not
406
+ override it; that flag now means what it was needed for, taking over your *own* house
407
+ with a new credential. Inviting a second member into an occupied database points at
408
+ `--shared-db` instead.
409
+
410
+ A team dashboard does not need the removed shape. `merge(mem, '^.*_messages$')` reads
411
+ every room in one query and is filtered by grant: the operator sees all of them, a
412
+ member sees only their own from the identical query, and a member who joins tomorrow
413
+ stays invisible until someone grants their room. Aggregate visibility became opt-in.
414
+
415
+ - **`memhouse invite --shared-db <db>` — one database, a room set per member.** Until now
416
+ a member meant a database: `alice.messages`. On a ClickHouse where that is not wanted,
417
+ `--shared-db mem` puts everyone in one database under their own names —
418
+ `mem.alice_messages`, `mem.bob_messages` — and grants each member only their own three
419
+ rooms, one statement per table because ClickHouse rejects `ON db.a, db.b` outright
420
+ ("Syntax error … Expected access type").
421
+
422
+ It exists for a reason that is social rather than technical: **a grant is something a
423
+ colleague can verify and a row policy is not.** Alice runs `SHOW GRANTS FOR alice`, sees
424
+ her three tables, and `SHOW TABLES FROM mem` does not even list the rooms she was not
425
+ granted — measured, she cannot read, drop, or enumerate a housemate's rooms, and cannot
426
+ create tables beside them. Nothing has to be taken on trust.
427
+
428
+ The **operator** creates the rooms, during `invite`, while an admin credential is in
429
+ hand. If the member created them she would need `CREATE TABLE` on the whole database —
430
+ enough to add tables beside everyone else's, which is the blast radius this layout
431
+ removes. `CREATE TABLE IF NOT EXISTS` is checked against the grant *before* existence,
432
+ so `ensure-schema` skips what it may not do and continues, which it already knew how to.
433
+
434
+ Members hold `WITH GRANT OPTION` on their own rooms and nothing else, so `memhouse share`
435
+ still works without an operator and still cannot reach a housemate's rows. Sharing,
436
+ revoking and the `--only` row policies all target the member's own rooms.
437
+
438
+ `MEMHOUSE_TABLE_PREFIX` carries it, written by `invite` into the env file, so the
439
+ invitee picks nothing. Empty means the layout memhouse has always had — verified byte
440
+ for byte against the previous resolution.
441
+
442
+ - **`memhouse rooms`** — what your rooms are actually called. Boring in your own house
443
+ (`messages`); the only way to know in a shared one (`mem.alice_messages`). It resolves
444
+ through the same function the shipper writes with, so it cannot drift from the server.
445
+
446
+ - **The `/mem:*` skills work in a shared house.** They write `FROM messages` bare in some
447
+ fourteen places, which names nothing in a house holding `alice_messages`. Rather than
448
+ teach fourteen query sites about prefixes, the `q()` helper in `reference/HOUSE.md` —
449
+ the single connection every skill routes through — now rewrites room names after
450
+ `FROM`/`JOIN`. A needle like `LIKE '%messages%'` is untouched, and `FROM yigit_messages`
451
+ is left alone so a shared room still reads.
452
+
453
+ Written with `[[:space:]]` classes, not `\b`: **BSD `sed` does not support `\b`**, and
454
+ the first version matched nothing on macOS. `misc/prefix-skill-recipe.sh` extracts
455
+ `q()` from the reference and runs it against both layouts — the doc is executable
456
+ because it is load-bearing.
457
+
458
+ ## 0.17.0 — 2026-08-25
459
+
460
+ - **`memhouse share` — partial sharing, by row policy.** A share used to be all or
461
+ nothing: `GRANT SELECT` on the whole house, every project and machine and anything ever
462
+ pasted into a session. Now `memhouse share <user> --only project=memhouse` (or
463
+ `session=`, `folder=`, `host=`, `source=`, `since=`, `until=`, combined with commas)
464
+ scopes it with a row policy per room. `--list` shows who reads what, `--revoke`
465
+ withdraws.
466
+
467
+ It is a command rather than SQL in a skill because partial sharing has four quiet
468
+ failure modes. A permissive catch-all policy for everyone else is the obvious design and
469
+ **fails open** — measured, a second scoped user saw all 22,500 rows instead of their
470
+ 1,860, because policies are OR'd. ClickHouse answers this properly through
471
+ `users_without_row_policies_can_read_rows`, but that is server config with a default
472
+ that has moved between versions, so the command *measures* the behaviour on a scratch
473
+ table before creating the first policy and refuses on a server that would blindfold the
474
+ readers you already have. Revoking now drops the policies — leaving them made a later
475
+ re-share silently reinherit the old scope. Widening a scoped share to a full one clears
476
+ them and says so. All three rooms move together or the share leaks, and `sessions` is
477
+ scoped on `created_at` because it has no `ts`.
478
+
479
+ `memhouse share --list` also reports policies pointing at a table that no longer exists:
480
+ dropping a table or a house leaves its policies behind, and one recreated under the same
481
+ name silently inherits them.
482
+
483
+ - **The `/mem:*` skillset is five skills and one shared reference, down from ten.** Four
484
+ of the ten had to explain in their own descriptions why they were not their siblings
485
+ (`/mem:ask`: *"this is the retrieve-and-answer skill; /mem:search is find-the-session"*),
486
+ which is what a wrong boundary looks like — an agent asked "what did I decide about X"
487
+ had to choose between `ask`, `search`, `sessions` and `sql`, all of which run SQL over
488
+ three tables and read rows. They are now `/mem:recall` (find and answer), `/mem:sql`
489
+ (numbers), `/mem:house` (what this is, whether it works, who can read it), `/mem:access`
490
+ (invite and share) and `/mem:admin`.
491
+ - **The data model is written once, in `reference/HOUSE.md`.** The connection recipe was
492
+ copy-pasted into eight skills, and the epoch filter — omit it and a real house
493
+ over-counts by 34% — was explained in `ask/SKILL.md` and nowhere else, so `/mem:sql`,
494
+ the skill most likely to produce a number someone acts on, never mentioned it. Schema,
495
+ connection, the three traps and the known measurement gaps now live in one file every
496
+ skill points at. 11,388 words became 5,854.
497
+ - **`/mem:recall` restores a name the canon already carried** (`TERMINOLOGY.md`: `memorecall`
498
+ retired in favour of `/mem:recall`, 2026-07-29). Its description carries every trigger
499
+ phrase the three skills it replaces had, so auto-triggering does not narrow.
500
+ - **The CLI no longer advertises a directory as a skill.** `plugins` listed
501
+ `readdirSync(skills/)` verbatim; a `reference/` directory beside them would have been
502
+ announced as `/mem:reference`. It now lists only directories carrying a `SKILL.md`.
503
+
504
+ ## 0.16.0 — 2026-08-24
505
+
506
+ - **An unknown option now stops the run instead of being ignored.** Anything `--like-this`
507
+ was accepted and silently discarded, which is quiet in the good case and dangerous in the
508
+ bad one: `--dryrun` for `--dry-run` did not warn, it ran the migration; `--adopt` on a
509
+ build predating that guard was swallowed and the invite proceeded into somebody else's
510
+ house. Each command now declares what it takes (`memhouse/flags.js`), anything else exits
511
+ 2 naming the flag it probably meant, and a test holds the table against the flags
512
+ `bin/memhouse.js` actually reads so the two cannot drift apart.
513
+ - **`whoami --admin` says when it fell back.** With no `MEMHOUSE_ADMIN_USER` set it
514
+ resolved the ordinary credential and printed exactly what bare `whoami` prints, so the
515
+ reader could not tell whether the flag had been heard. It now says so, and `--json`
516
+ carries `admin_requested` / `admin_env_present`.
517
+
518
+ ## 0.15.0 — 2026-08-24
519
+
520
+ - **`memhouse whoami` — which credential is in play, and what it may actually do.**
521
+ `/mem:admin` was deciding that in prose: read some files, run `SHOW GRANTS`, grep the
522
+ result. Reasoning about privileges in a skill gets it subtly wrong, and wrong here is
523
+ either "you cannot" to an administrator or "go ahead" to a member about to hit
524
+ ACCESS_DENIED. One command now answers it, with `--json` for agents and `--admin` to
525
+ resolve `MEMHOUSE_ADMIN_USER`/`MEMHOUSE_ADMIN_PASSWORD`. It prints no password.
526
+ - **Capability detection is scope-aware, and shared.** The check `invite` shipped in
527
+ 0.14.0 matched privilege names anywhere in the grants — but a privilege only means what
528
+ its scope allows, and an ordinary member holds `CREATE DATABASE` inside `ON <their-db>.*`,
529
+ which mints no new house at all. It read as false only because the users half also
530
+ failed; a member with any user-management grant would have been misjudged. The parser
531
+ moves to `memhouse/capabilities.js`, keeps each grant's scope, and counts a privilege
532
+ only when it is granted server-wide. `invite` and `whoami` share it, so they cannot
533
+ disagree about who is an administrator.
534
+ - **`/mem:admin` stops inventing places to keep an admin password.** It looked in
535
+ `~/.memhouse/admin.env` — a file memhouse never creates, and a bad idea besides: a
536
+ member credential owns one database, an admin credential owns the server, and any
537
+ process running as that user can read a file. The skill now calls `whoami --admin`,
538
+ and when there is no administrator it says so and shows how to supply one for the
539
+ current shell (`read -rs`, off the screen and out of history) rather than suggesting
540
+ anything be written to disk.
541
+
542
+ ## 0.14.0 — 2026-08-24
543
+
544
+ - **`memhouse invite` tells you the truth about your own credential, and gives a
545
+ non-admin a way forward.** The capability probe was `SELECT 1 FROM system.users` — but
546
+ since 0.12.6 every member is granted `SHOW USERS`, so *every member passed it*, was told
547
+ "it can manage users on this house", and then failed three steps later at
548
+ `CREATE DATABASE` with a raw `ACCESS_DENIED`. The probe now reads the credential's actual
549
+ grants and requires real provisioning rights. When they are absent it says plainly that
550
+ this is a member account, why a member cannot mint accounts, and splits the two cases:
551
+ if you run the ClickHouse, pass the admin credential you created it with; if someone else
552
+ runs it, you cannot invite at all — so `memhouse invite <name> --print-sql` now prints the
553
+ exact statements to hand to whoever administers the server, contacting nothing.
554
+ - **An unreachable `--url` says so, instead of blaming your privileges.** The capability
555
+ probe folded connection failure into the grants read, so a typo'd host, a down tunnel, or
556
+ a loopback-bound `deploy --local` house all came back as "you are only a member" — sending
557
+ the reader after a privilege they already held. Reachability is now checked first and
558
+ reported as itself, naming the loopback case explicitly.
559
+ - **`--admin-password` is prompted for when omitted.** Passing it as a flag puts the
560
+ password in the process list for the life of the request and in shell history unless the
561
+ caller remembered a leading space. With a TTY it is now asked for instead; without one
562
+ (an agent, CI) the old refusal stands, since there is nobody to ask. memhouse still never
563
+ stores an admin credential — that stays deliberate.
564
+
565
+
566
+ ## 0.13.0 — 2026-08-23
567
+
568
+ - **New skill: `/mem:admin`.** Every other skill is scoped to the caller's own house, so
569
+ anything server-wide — list the accounts, size the houses, provision or remove a member,
570
+ grant and revoke, read mutations and running queries — had no surface and became
571
+ hand-written SQL with an admin password pasted into a chat that memhouse then archives.
572
+ The skill resolves an admin credential from `MEMHOUSE_ADMIN_USER`/`MEMHOUSE_ADMIN_PASSWORD`,
573
+ then `~/.memhouse/admin.env`, then the ordinary member credential (which IS the superuser
574
+ on a `deploy --local` house), and *proves* it by checking `SHOW GRANTS` for
575
+ `ACCESS MANAGEMENT` / `CREATE USER` / `CREATE DATABASE ON *.*` rather than assuming —
576
+ `SHOW USERS` alone is a member privilege and does not qualify. Without one it refuses and
577
+ names the two ways to supply it; it never asks for a password in the conversation and
578
+ never echoes one. Destructive statements must be preceded by a SELECT of the same
579
+ predicate, a report of what is actually there, and an explicit yes for that object.
580
+
581
+ - **`/mem:invite` now finishes the handoff.** The skill provisioned the member and
582
+ stopped, leaving the user to work out where the credential file went and to compose the
583
+ covering message themselves. It now runs the invite from the user's downloads directory
584
+ (the file has to be attached to something, and it must never be left in a git repo —
585
+ `invite-*.env` is gitignored here but not in whatever checkout they were standing in),
586
+ confirms the file is present at mode 600 without ever printing its contents, reports
587
+ whether the house was actually fresh, and drafts the message to send the invitee —
588
+ install steps, that the file is a password, rotation on install, and `ship --full` when
589
+ they are replacing an existing credential. It also handles the new occupied-house
590
+ refusal: report the row count and let the user choose, never reach for `--adopt` on its
591
+ own.
592
+
593
+ - **`memhouse invite` refuses a house that already holds someone's messages.** The house
594
+ is created with `CREATE DATABASE IF NOT EXISTS`, so inviting a name whose database
595
+ already existed silently *adopted* it — identical output to a fresh house, and the
596
+ invitee landed on top of rows that were not theirs. Found the hard way: an invite meant
597
+ for one person was sent to another, who shipped 3,733 messages under it; re-inviting the
598
+ intended person reported success and would have handed over the first person's memory,
599
+ with nothing on any surface saying so. Invite now counts the target's `messages` first
600
+ and refuses with the row count, the writer count and three ways forward (different
601
+ handle, different `--db`, or `--adopt` when sharing the house is the actual intent).
602
+ Read-only and best-effort, so an admin that cannot count rows can still invite. The
603
+ *user* half already behaved: `adminBootstrap` refuses an existing ClickHouse user, so an
604
+ invite has never rotated a sitting member's password.
605
+
606
+ - **`memhouse relocate` needs `GRANT REMOTE ON *.*`, and no bootstrap granted it.**
607
+ relocate runs from the *destination*, pulling the source over `remoteSecure()` — a
608
+ table function ClickHouse gates behind its own access type, separate from `GRANT ALL`
609
+ on a database. Every member created via `invite` or the admin install had `ALL`,
610
+ `SHOW USERS` and self-`ALTER USER`, but not this — so `relocate` failed at the
611
+ native-reachability probe with `ACCESS_DENIED` for any member, source always left
612
+ untouched. Found by running it. Both grant paths (`adminBootstrap` and the
613
+ `--print-sql` template) now include `GRANT REMOTE ON *.* TO <member>`, best-effort
614
+ like the other two. Existing members are unaffected; grant them by hand:
615
+ `GRANT REMOTE ON *.* TO <member>`.
616
+
617
+ ## 0.12.7 — 2026-08-20
618
+
619
+ - **A member fully owns their house — `GRANT ALL ON <db>.* … WITH GRANT OPTION`.** Invite
620
+ and the admin install previously granted `ALL` plus only `SELECT` *with grant option*, so
621
+ a member could share read but not hand on anything more, and making them a true owner took
622
+ a manual `GRANT`. Now the single grant carries the option on everything: their database is
623
+ theirs to do anything with, including granting any of it onward. `/mem:share` is unchanged
624
+ — it still opens only a read-only `SELECT` window — this just stops boxing the owner into
625
+ read-only sharing of their *own* house. Still scoped to their db and still no `CREATE USER`,
626
+ so a member cannot mint accounts or reach another house. Both grant paths updated
627
+ (programmatic `adminBootstrap` + the `--print-sql` template); verified a fresh member gets
628
+ all 43 db privileges grantable. (Existing members already upgraded by hand are unaffected.)
629
+
630
+ ## 0.12.6 — 2026-08-20
631
+
632
+ - **Members can see who else is on the ClickHouse.** New accounts (via `memhouse invite`
633
+ and the admin install) now get `GRANT SHOW USERS ON *.* ` — read-only visibility of the
634
+ user list (names only; no passwords, no data), so `/mem:users` can answer "every user on
635
+ the server" and a member can find who to share with. It grants no read of anyone's rows;
636
+ that still needs an explicit `/mem:share`. Best-effort, like the self-`ALTER USER` grant:
637
+ an admin without access-management just skips it. Both grant paths carry it — the
638
+ programmatic `adminBootstrap` and the `--print-sql` template. (Existing members are
639
+ unaffected; grant them by hand: `GRANT SHOW USERS ON *.* TO <member>`.)
640
+ - **The plugin nudges you when the client is behind.** `/mem:status` (and, quietly,
641
+ `/mem:hello`) now compare the installed `memhouse version` against the latest npm release
642
+ and, on a real gap (a minor/major behind, or many patches), offer `memhouse update` —
643
+ which upgrades the CLI and, since 0.12.5, refreshes the `/mem:*` plugin too. A patch or
644
+ two behind is mentioned gently or not at all; the skills never run the update themselves.
645
+
646
+ ## 0.12.5 — 2026-08-20
647
+
648
+ - **`memhouse update` now refreshes the Claude plugin too.** Before, update upgraded the
649
+ package, the service unit, and the house, but left the installed `/mem:*` skills as
650
+ whatever an earlier `plugins install` had copied — so the plugin drifted behind the CLI
651
+ (and showed an old version). Update now re-copies the plugin into every Claude config dir
652
+ that ALREADY has it (never installs it somewhere new), from the files just put on disk,
653
+ and stamps the manifest with the freshly-installed version. It prints each dir refreshed
654
+ and a `/reload-plugins` reminder. (Self-update note: because `update` runs the
655
+ pre-upgrade code, this takes effect from the update AFTER the one that installs 0.12.5.)
656
+
657
+ ## 0.12.4 — 2026-08-20
658
+
659
+ - **Inserts are compressed and byte-bounded — the shipper stops choking slow links.**
660
+ Profiling a real house behind a Cloudflare tunnel showed the shipper's inserts taking up
661
+ to 77s each, and the ProfileEvents were unambiguous: `NetworkReceiveElapsed = 76s`,
662
+ full-text index build 0.8s, CPU 1.1s, disk 0.009s — the entire cost was **uploading a
663
+ ~27 MB uncompressed JSON batch through the tunnel**, not ClickHouse. Two fixes: the
664
+ shipper now (1) **gzip-compresses the request body** (`compression: { request: true }`) —
665
+ measured 3.6–8× smaller on the wire — and (2) **caps each insert batch at ~4 MB as well
666
+ as 2000 rows**, whichever comes first, so a handful of very wide messages can't build a
667
+ giant single upload (`MEMHOUSE_BATCH_BYTES` overrides the ceiling). Reads were never the
668
+ problem — server-side SELECTs are 20–70ms; the latency you feel over a tunnel is round
669
+ trip, and `FINAL` (needed for ReplacingMergeTree correctness) adds ~40ms.
670
+
671
+ ## 0.12.3 — 2026-08-20
672
+
673
+ - **`install` no longer blocks on the first ship — it ships in the background.** A fresh
674
+ member's first ship loads the entire local backlog (a real invitee's was 558 sessions /
675
+ 170k rows / **224 seconds**), and the installer ran it synchronously and near-silently,
676
+ so `install` looked hung for minutes after "config written". Now install finishes
677
+ immediately and starts the shipper as a detached background daemon (the same one
678
+ `memhouse start` runs), which loads the history and keeps shipping — `memhouse status`
679
+ shows it fill in. It defers to an installed service rather than running a second shipper,
680
+ skips if one is already running, and is started AFTER the invite password rotation from a
681
+ re-read config so the daemon never holds the pre-rotation password. `--no-ship` still
682
+ skips it. Both install paths (invite `--env` and direct `--url/--user`) and `onboard`
683
+ (which calls install) are covered. Verified end to end: install returns in ~0s and the
684
+ detached shipper comes up alive.
685
+
686
+ ## 0.12.2 — 2026-08-20
687
+
688
+ - **The Claude plugin now reports the real version.** `plugin.json` carried a hardcoded
689
+ `"version": "0.11.0"` in the source, copied verbatim on install — so the plugin
690
+ advertised 0.11.0 no matter which memhouse produced it, disagreeing with
691
+ `memhouse --version`. `plugins install` now stamps the installed manifest with the
692
+ package version (the single source of truth), and the source manifest was bumped to
693
+ match. Re-run `memhouse plugins install claude` to refresh an already-installed plugin.
694
+
695
+ ## 0.12.1 — 2026-08-20
696
+
697
+ - **The skills no longer fall back to a hardcoded `mem` house.** A house is a database
698
+ named for its owner — `resolveConfig` defaults `MEMHOUSE_DB` to the connection's
699
+ username (`polat` ships into `polat.messages`), and `memhouse invite` mints a per-user
700
+ database. But the plugin skills hardcoded `${MEMHOUSE_DB:-mem}` in their connection
701
+ recipes, so a config missing `MEMHOUSE_DB` would silently query a `mem` house that the
702
+ user-named convention had moved on from. All eight skills now fall back to
703
+ `${MEMHOUSE_DB:-$MEMHOUSE_USER}`, matching the CLI. (The env file always sets
704
+ `MEMHOUSE_DB`, so this only bit an unset-DB config — but the stale default was wrong.)
705
+
706
+ ## 0.12.0 — 2026-08-20
707
+
708
+ - **`memhouse relocate --to <url>` moves a whole house to a new ClickHouse.** The copy is
709
+ a server-to-server `remoteSecure()` INSERT SELECT — the destination pulls each room
710
+ directly from the source over the native protocol, so the pilot's laptop is never in the
711
+ data path and, crucially, **the shipper never re-ingests**: once the new host holds a
712
+ faithful copy, the shipper's skip predicate sees every old session already present and
713
+ ships only genuinely new work. Provenance is carried, not restamped
714
+ (`insert_allow_materialized_columns=1`), so a shared house keeps every member's
715
+ `user_id` — verified against two live ClickHouse instances (source rows stamped `alice`
716
+ arrive as `alice`, not the copier). `house_meta` carries only durable facts
717
+ (`schema_version`, `min_writer_schema`, `share:*`); per-host heartbeats regenerate. The
718
+ SOURCE is only ever read — a failed run leaves the old house intact — and a hard
719
+ row-count gate must pass before the local config is repointed (the previous env is kept
720
+ as `env.pre-relocate`). Flags: `--to-user/--to-password/--to-db`, `--from-native-host`
721
+ (when the destination's route to the source differs from the pilot's URL),
722
+ `--from-native-port` (default 9440 TLS), `--insecure-native` (`remote()` + 9000),
723
+ `--keep-shipper`, `--dry-run`, `--yes`. The source password reaches the destination's
724
+ `query_log` (never this transcript) — rotate it after if those logs are not yours.
725
+ - This is the host-to-host data-copy piece named in
726
+ `docs/design/host-repoint-reconciliation.md`; the reconciliation gate (detecting an
727
+ *accidental* repoint at an empty host) remains deferred.
728
+
729
+ ## 0.11.3 — 2026-08-20
730
+
731
+ - **Read a friend's shared memory by naming their house.** A share is a read-only GRANT on
732
+ a whole house (a ClickHouse database), and the rooms resolve by the connection's
733
+ database — so `/mem:ask`, `/mem:search` and `/mem:sql` now recognize a house named in the
734
+ question ("how did yigit fix X", "search … in yigit", "query yigit's house"), resolve it
735
+ against `SHOW DATABASES`, and point the connection at that house with your own
736
+ credentials, read-only. A name that matches a readable house wins over the `in <project>`
737
+ qualifier — otherwise "in yigit" silently filtered your own project column and returned
738
+ nothing. `/mem:hello` now names the phrase, and `/mem:users` still lists the houses shared
739
+ with you.
740
+
741
+ ## 0.11.2 — 2026-08-20
742
+
743
+ - **`/mem:hello` leads with a what-is overview.** Before, the welcome skill only said
744
+ what memhouse *is* in its not-configured branch — a configured user got session counts
745
+ and a command tour but never the one-paragraph framing. It now gives a
746
+ state-independent overview first, in every state: a shipper reads 17 editors' session
747
+ files into a ClickHouse *house you own*, on any ClickHouse you point it at (local, your
748
+ own VM, or a managed/remote server — memhouse is not a hosted service and phones nothing
749
+ home), runs no LLM in its path, and outlives the transcripts editors delete after weeks.
750
+ - **The tour now separates the three retrieval skills.** `/mem:ask`, `/mem:search` and
751
+ `/mem:sql` overlap enough that the same words work in each; the tour carries the rule
752
+ that tells them apart — **want a session → search, want an answer → ask, want a number →
753
+ sql** (ask runs a search then reads and cites; search stops at the list; sql aggregates).
754
+
755
+ ## 0.11.1 — 2026-08-20
756
+
757
+ - **Upgrading from 0.9.x needs one manual step**, and the tool now says so plainly. `memhouse update` runs the OLD version's update code (it replaces itself mid-run), and 0.9.x predates migrations — so it cannot prompt to migrate the house the way an upgrade from 0.10+ does. The first ship after such an upgrade refuses (nothing is lost) and now leads with the exact fix: **run `memhouse migrate` once, by hand.** From 0.11 onward the interactive prompt works normally. (Found upgrading a real 87k-message house.)
758
+
759
+ ## 0.11.0 — 2026-08-20
760
+
761
+ - **`memhouse invite <name>`** mints a member and their own house on the server (same
762
+ verified path as the admin install — grants, grant option, async pin, connect-as-member
763
+ proof) and writes the ONE env file their install needs; this machine's config is never
764
+ touched. Refuses loopback URLs (the file must work from the invitee's machine) and
765
+ states plainly that the file is a credential.
766
+ - **`memhouse install --env <file>`** installs from an invite file: values load as if
767
+ typed, nothing prompts, nothing persists until the connection and rooms prove out.
768
+ - **`memhouse passwd`** rotates this member's password and rewrites the env file —
769
+ admin-assisted by ClickHouse's rules (members deliberately hold no ALTER USER), and the
770
+ reason it exists: an invited member's password is known to the inviter until rotated.
771
+ - **Self-service password rotation.** Members are granted `ALTER USER ON <self>` — a
772
+ self-scoped grant (verified non-escalating: the holder cannot alter or grant on any
773
+ other user), so `memhouse passwd` needs no admin. It tries self-rotation first and only
774
+ falls back to `--admin-*` for a pre-0.11 member.
775
+ - **`memhouse invite` uses your own credential when it can.** If the configured user can
776
+ manage users (an install made as an admin-capable ClickHouse user), no `--admin-*` is
777
+ needed. The invite file carries `MEMHOUSE_INVITE=1`, and `memhouse install --env` then
778
+ OFFERS to rotate the inviter-set password to one only the invitee knows (`--yes` does it
779
+ unasked) — so the inviter's knowledge of the password expires at install.
780
+ - **`/mem:invite` and `/mem:hello`** — the invite flow and a grounded introduction from
781
+ inside an agent. Inviting comes before sharing: a share can only grant a user who
782
+ exists.
783
+
784
+ ## 0.10.0 — 2026-08-20
785
+
786
+ **The shipper stops deleting.** Breaking on the room schema: an existing house needs
787
+ `memhouse migrate-rooms` before it can be shipped into.
788
+
789
+ - **`DELETE FROM` is gone from the shipper**, and with it the only privilege whose misuse
790
+ loses data — nothing memhouse runs needs `ALTER DELETE` any more. The clear existed to
791
+ remove the stale `seq` tail a shorter re-parse leaves behind (ReplacingMergeTree dedupes
792
+ same-key rows; it cannot remove a row the new parse no longer produces). It could not
793
+ tell a fixed adapter bug from a **compacted or expired transcript** — Claude Code
794
+ rewrites sessions in place and deletes them after `cleanupPeriodDays`, 30 days by
795
+ default — so it destroyed content the house was the last copy of.
796
+ - **`epoch` joins `origin` in the sorting key** of `messages` and `tool_calls`. A re-parse
797
+ that is shorter than the stored one, or that differs from it at a `seq` the house already
798
+ holds (compared on the stored `line_hash`), is written under a new epoch; the superseded
799
+ parse stays complete and readable. An unchanged or merely longer re-parse reuses its
800
+ epoch and dedupes exactly as before, so the common case costs nothing.
801
+ - **Reads show one parse per session.** `roomNames()` resolves `messages` and `tool_calls`
802
+ to a current-epoch subquery, and the bare tables only as `messages_raw` /
803
+ `tool_calls_raw` for writes and DDL. Counts, tokens and cost are unchanged. Hand-written
804
+ SQL needs the filter — `/mem:sql` carries it.
805
+ - **`memhouse migrate`** — a migration runner, not a one-off. Migrations live in
806
+ `memhouse/house/migrations/<id>.js` (registry: id, component, toVersion, detect, plan,
807
+ steps); the runner detects what a house still needs FROM ITS ROOMS (never from the
808
+ record — a hand-migrated house has no record), shows the plan, and executes in order.
809
+ `--dry-run` prints and touches nothing; `--yes` skips the confirm. Every migration
810
+ inherits the invariants: nothing deleted, provenance never restamped, atomic swap,
811
+ late writes survive, everything recorded in `house_events`. `memhouse migrate-rooms`
812
+ is the same runner scoped to the rooms component.
813
+ - **`memhouse update` names, asks, or runs pending migrations.** After the files update
814
+ it detects what the house needs: `--migrate` (or `--yes`) runs them unasked,
815
+ an interactive session is asked once (the inner confirm is not repeated), and a
816
+ non-interactive run only names them and prints the command — a cron must never start
817
+ a house-wide copy on its own. `update --no-install` skips the npm/git step for pilots
818
+ who already upgraded by hand and want the half a bare `npm i -g` leaves undone.
819
+ - **`memhouse uninstall` asks first.** It used to start removing the service the moment
820
+ it was typed. It now prints exactly what the chosen tier removes and keeps (the house
821
+ data is never touched, and says so), confirms once, takes `--yes` for scripts, and a
822
+ non-interactive run without `--yes` refuses rather than proceeding.
823
+ - **`memhouse nightly [--out DIR]`** builds an installable, version-stamped tarball from
824
+ a checkout (`<base>-nightly.<YYYYMMDDTHHMM>`) without publishing — stamp, `npm pack`,
825
+ restore, so the checkout stays clean and the test machine's `--version` tells the
826
+ truth. On such an install use `memhouse update --no-install`; plain `update` installs
827
+ `memhouse@latest` and silently downgrades a nightly.
828
+ - **`/mem:share` and `/mem:users`** — sharing as a skill. Members are now granted
829
+ `SELECT … WITH GRANT OPTION` beside their `ALL` (both install paths), so
830
+ `/mem:share <user>` opens a read-only window into your own house with no operator —
831
+ and can hand on nothing more, because the grant option stops at SELECT. `revoke` closes
832
+ it; bare `/mem:share` lists who can read you. On a pre-0.10 house (no grant option) the
833
+ skill prints the one statement the admin runs. `/mem:users` reports who writes into
834
+ your house, whose houses you can read, who can read yours, and (where permitted) the
835
+ server's user list — each section degrading legibly on a hardened server.
836
+ - **`/mem:status`** reports the memory system's state from inside an agent: per-editor
837
+ holdings (current parses only), freshness and coverage bounds, schema generation and
838
+ migration state, and the writer fleet — including the rows-without-record signature of
839
+ a pre-0.10 machine still writing.
840
+ - **The plugin is `mem` now, and it answers questions.** Skills install as `/mem:ask`,
841
+ `/mem:search`, `/mem:sessions`, `/mem:sql` (the `mem` short name belonged to the
842
+ retired memory-house and moves to the living product). `/mem:ask` is new: retrieve the
843
+ relevant past sessions, read the transcripts, answer with citations — the synthesis is
844
+ the agent's; memhouse itself still runs no LLM anywhere. The installer removes a
845
+ pre-0.10 `skills/memhouse/` copy (only when it is provably ours) so the old and new
846
+ namespaces never load side by side.
847
+ - **`install` ends with a getting-started overview** — dashboard, service, skills,
848
+ search, doctor — instead of a single next-step line.
849
+ - **Writer compatibility is enforced, both directions.** Each release declares which
850
+ schema generations it may write (`SUPPORTED_SCHEMAS`); every pass starts by reading the
851
+ house's own record and refuses — before touching a room — when the house is newer than
852
+ the shipper (`Update THIS machine: memhouse update`) or below the floor a migration set
853
+ (`house_meta['min_writer_schema']`). The room-shape checks stay: they catch what a
854
+ record cannot (a hand-built house with no record at all).
855
+ - **The house knows its fleet.** Every writer records the schema it supports and a
856
+ per-pass heartbeat, keyed `member@host` (member-only keying let two machines of one
857
+ member clobber each other's entry). `memhouse status` shows the fleet — writer,
858
+ version, last ship — and `doctor` fails on the writers that matter: `legacy` (a
859
+ pre-0.10 memhouse, visible because its ROWS are in the house while it records nothing
860
+ about itself — its re-ships delete retained parses) and `outdated` (refusing every
861
+ pass until updated). Ground truth is the data, not the record, so a writer that
862
+ predates the record cannot hide.
863
+ - **Mixed-version fleets: upgrade every machine of a member promptly.** A pre-0.10
864
+ shipper on another machine keeps working against migrated rooms (its key check only
865
+ looks for `origin`), but its delete-before-reinsert reaches every retained parse of a
866
+ session it re-ships. `memhouse migrate` warns about this at migration time.
867
+ - **`memhouse migrate-rooms`** rebuilds rooms whose sorting key predates this version:
868
+ copy, one atomic `RENAME`, old room kept as `<room>_pre_epoch` for the pilot to drop.
869
+ Nothing is deleted, and `user_id` is carried across explicitly rather than restamped —
870
+ it is `MATERIALIZED currentUser()`, so a plain `INSERT SELECT` would reassign every row
871
+ in a shared house to whoever ran the migration. Rows a running shipper writes during the
872
+ copy are picked up afterwards.
873
+ - **The house keeps a record of itself**: `house_meta` (schema version, per-member client
874
+ version) and the append-only `house_events` (migrations pending/applied/failed, version
875
+ changes, actor, host, row counts). `memhouse doctor` reads it back, so a migration that
876
+ died between the copy and the swap is reported rather than left to be noticed.
877
+ - **Fixed: the schema column healer had been dead** since the rooms became plain shared
878
+ tables. It matched `<type>_{{MEMBER}}`, which no longer exists, so it compared zero
879
+ columns — while `ensureSchema`, `--ensure-schema`, `warnMissingColumns` and `doctor` all
880
+ reported success over that empty comparison.
881
+ - `npm run test:house` — the first tests that run against a real ClickHouse (throwaway
882
+ container, fixture adapters). Green on 25.11 and 26.7.
883
+
884
+ ## 0.9.0 — 2026-08-16
885
+
886
+ **`node:sqlite`.** Breaking on the Node floor, and the reason `npm i -g memhouse` is
887
+ now the whole install.
888
+
889
+ - The `better-sqlite3` dependency is **gone**, replaced by `node:sqlite` — built into
890
+ Node, stable in 24. No native binding, no install script, no build step.
891
+ - **`--allow-scripts=better-sqlite3` is retired.** It was required on every install and
892
+ every upgrade, npm never remembered it, and omitting it left cursor, zed, opencode,
893
+ goose and antigravity reading zero sessions — indistinguishable from editors the
894
+ pilot does not have. That failure class no longer exists, so the probe that reported
895
+ it (`MISSING_BINDING`, `probeSqlite`) and the `missingBinding` flag on every
896
+ `getAdapterErrors()` entry are removed. Per-store failures — locking, permissions,
897
+ corruption, schema drift — are reported exactly as before.
898
+ - **`engines` moves to `>=24`.** `node:sqlite` works flagless on 22.13+ but prints
899
+ `ExperimentalWarning` on every command (measured on v22.23.2); Node 20 is past EOL.
900
+ - Blob columns arrive as `Uint8Array` rather than `Buffer`, whose `toString()` renders
901
+ byte values instead of decoding utf-8. Every blob-or-text read in the adapters now
902
+ goes through `editors/sqlite.js`; message output was verified byte-identical against
903
+ the old implementation on real opencode and zed stores and on BLOB-valued cursor and
904
+ goose fixtures.
905
+
906
+ ## 0.8.0 — 2026-08-14
907
+
908
+ **The shared house.** Breaking, and the reason the number moved.
909
+
910
+ - A **house is a ClickHouse database**; its rooms are three plain shared tables —
911
+ `sessions`, `messages`, `tool_calls`. Everyone in the house writes into the same
912
+ tables with their own credential. `FROM messages` just works.
913
+ - **Provenance is two columns no client can fake**: `user_id` (server-stamped
914
+ `MATERIALIZED currentUser()`, with `async_insert = 0 CONST` pinned on the user so the
915
+ stamp cannot be skipped) and `host` (a random fingerprint minted once per install).
916
+ - **Joining any ClickHouse is two statements**: `CREATE USER` + `GRANT ALL ON <house>.*`.
917
+ The member's own shipper creates and evolves the rooms.
918
+ - The house name defaults to the username (`polat` ships to `polat.messages`) and is
919
+ freely decoupled (`--db`); `system` and `information_schema` are refused everywhere —
920
+ previously `--db system` granted a member everything on the server's own database.
921
+ - Removed: per-member suffixed rooms, Merge rooms, the narrowed grant set,
922
+ `provision.js`, `--adopt-user`. Housemates are collaborators; groups that should not
923
+ see each other get separate houses.
924
+ - Field-tested: two physical machines shipping as one member into one house, split
925
+ cleanly by `host`; rootless-podman `deploy --local`; ClickHouse 25.11 and 26.7.
926
+
927
+ ## 0.7.x — 2026-08-13
928
+
929
+ - **0.7.1**: the shared settings profile detached every previously-assigned member on
930
+ each provision (only the last member ever had resource ceilings, since 0.4.7) — fixed,
931
+ then removed entirely in favour of a single per-user pin. `async_insert = 0 CONST`
932
+ guards the identity stamp server-side. `install` no longer reports a user it just
933
+ created as "already exists".
934
+ - **0.7.0**: `uninstall` became three tiers (default keeps config and identity;
935
+ `--credentials`; `--full-removal`, which asks). Host identity became a random
936
+ per-install fingerprint — the old derived id collided across same-hostname machines
937
+ and split on rename.
938
+
939
+ ## 0.6.0 — 2026-08-13
940
+
941
+ - Deleted every path that existed only for versions nobody ran: the inherited
942
+ agentlytics server/cache/relay stack (~4,800 lines), four dependencies, the second
943
+ installer, and the solo-tier/pre-0.4 compatibility branches.
944
+
945
+ ## 0.5.0 — 2026-08-12
946
+
947
+ - `memhouse resume <session-id>` — prints the command that reopens a session in its own
948
+ editor (claude, codex, opencode verified from their own `--help`; GUI editors and
949
+ unverified CLIs refuse, each with the honest reason).
950
+ - `memhouse update` — re-passes the install flag, restarts daemons, checks the schema.
951
+ - Daemons detect their own upgrade and hand over (exit under a supervisor, re-exec
952
+ otherwise).
953
+ - `plugins install claude` acts on every Claude Code config dir on the machine;
954
+ `onboard` finally offers the skills.
955
+
956
+ ## 0.4.x — 2026-07-31 → 2026-08-12
957
+
958
+ - Per-member rooms as the only layout (later superseded by 0.8.0's shared house).
959
+ - The `origin` guarantee: imported history survives re-ships — `origin` in the
960
+ messages/tool_calls sorting keys and bound in the shipper's clear. The bug this fixed
961
+ destroyed 27,948 imported messages in a single pass.
962
+ - Local house delivery (`deploy --local`, docker/podman), OS service integration,
963
+ the herdr-driven acceptance suite, `memhouse.io`.
964
+
965
+ ## 0.3.x — 2026-07-22 → 2026-07-31
966
+
967
+ - First real releases: the 17-adapter shipper, typed ClickHouse schema, CLI, dashboard,
968
+ Claude Code skills, npm publication. Sixteen adversarial review rounds hardened the
969
+ adapter layer's failure reporting — a failed read writes nothing, and every silent
970
+ swallow found was one layer behind the last.