memhouse 0.18.9 → 0.18.11

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