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.
- package/AGENTS.md +194 -0
- package/CHANGELOG.md +1097 -0
- package/README.md +54 -423
- package/SECURITY.md +68 -0
- package/bin/memhouse.js +299 -133
- package/docs/README.md +46 -0
- package/docs/guides/admin-password.md +95 -0
- package/docs/guides/configuration.md +109 -0
- package/docs/guides/daemons.md +53 -0
- package/docs/guides/install.md +128 -0
- package/docs/guides/invite.md +87 -0
- package/docs/guides/operate.md +112 -0
- package/docs/guides/search-and-resume.md +95 -0
- package/docs/guides/share.md +102 -0
- package/docs/guides/storage.md +93 -0
- package/docs/guides/troubleshooting.md +78 -0
- package/docs/guides/uninstall.md +37 -0
- package/docs/guides/update.md +68 -0
- package/docs/releases/README.md +11 -0
- package/docs/releases/v0.18.10.md +226 -0
- package/docs/releases/v0.18.11.md +191 -0
- package/docs/tutorials/01-first-house.md +132 -0
- package/docs/tutorials/02-join-a-house.md +91 -0
- package/docs/tutorials/03-share-your-memory.md +96 -0
- package/editors/claude.js +68 -10
- package/examples/01-local-one-machine/README.md +99 -0
- package/examples/01-local-one-machine/run.sh +61 -0
- package/examples/02-existing-clickhouse/README.md +121 -0
- package/examples/02-existing-clickhouse/run.sh +71 -0
- package/examples/03-team-house/README.md +129 -0
- package/examples/03-team-house/run.sh +124 -0
- package/examples/04-self-hosted-team/.env.example +22 -0
- package/examples/04-self-hosted-team/README.md +187 -0
- package/examples/04-self-hosted-team/compose.yml +158 -0
- package/examples/04-self-hosted-team/smoke.sh +113 -0
- package/examples/README.md +42 -0
- package/examples/fixtures/README.md +24 -0
- package/examples/fixtures/make-sessions.js +63 -0
- package/memhouse/DESIGN.md +9 -2
- package/memhouse/admin-secret.js +81 -0
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/memhouse/delivery/plugin/reference/HOUSE.md +18 -6
- package/memhouse/delivery/plugin/skills/access/SKILL.md +1 -1
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +1 -1
- package/memhouse/delivery/plugin/skills/house/SKILL.md +1 -1
- package/memhouse/delivery/plugin/skills/recall/SKILL.md +34 -14
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +1 -1
- package/memhouse/deploy.js +4 -2
- package/memhouse/flags.js +32 -5
- package/memhouse/host.js +11 -6
- package/memhouse/house/HOUSE.md +1 -1
- package/memhouse/house/house.js +27 -1
- package/memhouse/invite-target.js +37 -0
- package/memhouse/provision.js +12 -3
- package/memhouse/server/queries.js +9 -4
- package/memhouse/service.js +5 -0
- package/memhouse/shipper/ship.js +148 -31
- package/package.json +10 -2
- package/public/THIRD_PARTY_NOTICES.txt +2114 -0
- 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.
|