memhouse 0.18.8 → 0.18.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +194 -0
- package/CHANGELOG.md +970 -0
- package/README.md +54 -423
- package/SECURITY.md +68 -0
- package/bin/memhouse.js +202 -92
- 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 +90 -0
- package/docs/guides/troubleshooting.md +78 -0
- package/docs/guides/uninstall.md +35 -0
- package/docs/guides/update.md +68 -0
- package/docs/releases/README.md +10 -0
- package/docs/releases/v0.18.10.md +226 -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/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/admin-secret.js +81 -0
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/memhouse/deploy.js +4 -2
- package/memhouse/flags.js +4 -4
- package/memhouse/provision.js +12 -3
- package/memhouse/server/queries.js +18 -4
- package/memhouse/server/server.js +9 -1
- package/memhouse/service.js +5 -0
- package/package.json +10 -2
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Share and revoke
|
|
2
|
+
|
|
3
|
+
A share lets another member of the same server read your rooms. It is a ClickHouse
|
|
4
|
+
`GRANT SELECT`, so it is read-only, and you can withdraw it. Run these on a machine
|
|
5
|
+
installed as yourself. Each command acts on your own rooms only.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
memhouse share bob # full: everything in mem.<you>_*
|
|
9
|
+
memhouse share bob --only project=billing # scoped: three rooms, filtered
|
|
10
|
+
memhouse share --list # what memhouse has recorded
|
|
11
|
+
memhouse share bob --revoke # take it back
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Full or scoped
|
|
15
|
+
|
|
16
|
+
| | full (`share bob`) | scoped (`share bob --only …`) |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| grant | `SELECT ON mem.<you>_*` | `SELECT` on `<you>_sessions`, `<you>_messages`, `<you>_tool_calls` |
|
|
19
|
+
| rows | all | those matching the scope, through row policies |
|
|
20
|
+
| stats tables (`<you>_session_stats`, model and tool stats) | readable: first prompts, folders, projects and tokens of every session | not granted |
|
|
21
|
+
| `<you>_meta`, `<you>_events` | readable, including your other share records | not granted |
|
|
22
|
+
| rooms created later | readable | not granted |
|
|
23
|
+
|
|
24
|
+
**A full share is for someone you would hand your laptop to.** It reaches every table in
|
|
25
|
+
your pattern, including ones memhouse adds in a later release. The command says so when
|
|
26
|
+
you run it.
|
|
27
|
+
|
|
28
|
+
## Scopes
|
|
29
|
+
|
|
30
|
+
`--only key=value[,key=value…]`. All the keys must hold (AND):
|
|
31
|
+
|
|
32
|
+
| key | filters on |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `project=` | the project name |
|
|
35
|
+
| `folder=` | the working directory |
|
|
36
|
+
| `session=` | one session id |
|
|
37
|
+
| `host=` | one machine (its fingerprint, as `status` shows it) |
|
|
38
|
+
| `source=` | one editor, by the value stored in the `source` column (below) |
|
|
39
|
+
| `since=` / `until=` | a date range: message time, or session creation time for `sessions` |
|
|
40
|
+
|
|
41
|
+
`source=` compares against the stored value, which is not always the adapter name you
|
|
42
|
+
use in `MEMHOUSE_EDITORS`. Claude Code rows, for example, say `claude-code`, not `claude`.
|
|
43
|
+
The values:
|
|
44
|
+
|
|
45
|
+
| editor | `source` |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Claude Code | `claude-code` |
|
|
48
|
+
| Codex | `codex` |
|
|
49
|
+
| Cursor / Cursor Agent | `cursor` / `cursor-agent` |
|
|
50
|
+
| VS Code / VS Code Insiders | `vscode` / `vscode-insiders` |
|
|
51
|
+
| GitHub Copilot / Copilot for JetBrains | `copilot-cli` / `copilot-jetbrains` |
|
|
52
|
+
| Gemini CLI | `gemini-cli` |
|
|
53
|
+
| Zed, OpenCode, Goose, Kiro, Codebuff, Antigravity | `zed`, `opencode`, `goose`, `kiro`, `codebuff`, `antigravity` |
|
|
54
|
+
| Devin / Devin Next | `devin` / `devin-next` |
|
|
55
|
+
| Command Code | `commandcode` |
|
|
56
|
+
|
|
57
|
+
To see what your rooms actually hold:
|
|
58
|
+
`SELECT source, count() FROM mem.<you>_sessions FINAL GROUP BY source`.
|
|
59
|
+
|
|
60
|
+
An empty scope (`--only ""`, or `--only "$SCOPE"` with the variable unset) is refused.
|
|
61
|
+
It never falls back to a full share.
|
|
62
|
+
|
|
63
|
+
## How the server must be configured
|
|
64
|
+
|
|
65
|
+
Row policies in ClickHouse have one sharp edge. On some server configurations, the first
|
|
66
|
+
policy on a table hides every row from readers who have no policy of their own: you, and
|
|
67
|
+
your full-share grantees. Before it creates the first policy, `share` measures how this
|
|
68
|
+
server behaves, and refuses if a policy would blind you. The fix is server configuration
|
|
69
|
+
(`users_without_row_policies_can_read_rows`), and it is the administrator's to make.
|
|
70
|
+
|
|
71
|
+
## Changing a share
|
|
72
|
+
|
|
73
|
+
- **Scoped to full:** `memhouse share bob`.
|
|
74
|
+
- **Full to scoped:** `memhouse share bob --only …`. The wildcard grant is withdrawn
|
|
75
|
+
first, so a failure part-way leaves bob with less access, never more.
|
|
76
|
+
- **One scope to another:** run `--only` again with the new scope. It replaces the
|
|
77
|
+
policies.
|
|
78
|
+
|
|
79
|
+
**Scoped shares made before 0.18.10** granted the whole pattern while filtering only three
|
|
80
|
+
rooms. Re-run `memhouse share <user> --only <scope>` once for each, and it is narrowed to
|
|
81
|
+
the three rooms.
|
|
82
|
+
|
|
83
|
+
## Revoking
|
|
84
|
+
|
|
85
|
+
`--revoke` withdraws the grants (the wildcard and the three rooms), then drops the row
|
|
86
|
+
policies. If the server refuses a step, the policies are kept unless every grant is
|
|
87
|
+
confirmed gone, so a failed revoke never leaves an unfiltered share behind.
|
|
88
|
+
|
|
89
|
+
A revoke cannot recall what was already read.
|
|
90
|
+
|
|
91
|
+
## What `--list` shows
|
|
92
|
+
|
|
93
|
+
`share --list` reads memhouse's own record, the `share:` keys in `<you>_meta`. A grant
|
|
94
|
+
someone made by hand in SQL does not appear there. A member cannot read `system.grants`;
|
|
95
|
+
an administrator can, with `SHOW GRANTS FOR <user>`.
|
|
96
|
+
|
|
97
|
+
## Reading what was shared with you
|
|
98
|
+
|
|
99
|
+
Shared rooms sit in the same database as yours, under the other member's name, for
|
|
100
|
+
example `mem.alice_messages`. Query them with `/mem:sql` or any ClickHouse client, joining
|
|
101
|
+
on `(session_id, user_id)` as you would on your own rooms. A table you were not granted is
|
|
102
|
+
refused, and a scope you are under returns only its rows.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# How memory is stored
|
|
2
|
+
|
|
3
|
+
## A house, rooms, and members
|
|
4
|
+
|
|
5
|
+
- **A house is a ClickHouse database,** normally `mem`.
|
|
6
|
+
- **A member is a ClickHouse user** who ships into it.
|
|
7
|
+
- **Each member's rooms are tables named for them:**
|
|
8
|
+
|
|
9
|
+
| room | holds |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `<member>_sessions` | one row per session: editor, project, folder, branch, timestamps, title |
|
|
12
|
+
| `<member>_messages` | every turn: role, text, model, tokens, timestamp, position |
|
|
13
|
+
| `<member>_tool_calls` | every tool call: name, arguments, which message made it |
|
|
14
|
+
| `<member>_session_stats`, `_session_model_stats`, `_session_tool_stats` | precomputed rollups for the dashboard, refreshed after each pass |
|
|
15
|
+
| `<member>_meta` | memhouse's own records: schema version, writers, shares |
|
|
16
|
+
| `<member>_events` | an append-only log: migrations, version changes |
|
|
17
|
+
|
|
18
|
+
**A member holds one grant: `ON mem.<member>_*`.** That covers their own rooms, present and
|
|
19
|
+
future, and nothing else in the database. Nobody is granted the database itself:
|
|
20
|
+
`ALL ON mem.*` would also reach rooms created later, which is how two people in one
|
|
21
|
+
database once leaked into each other. A member can read their isolation back with
|
|
22
|
+
`SHOW GRANTS`.
|
|
23
|
+
|
|
24
|
+
| | rooms | each member holds |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| alone on a laptop | `mem.polat_*` | one grant on `mem.polat_*` |
|
|
27
|
+
| a team on one server | `mem.polat_*`, `mem.alice_*`, … | one grant each, on their own |
|
|
28
|
+
|
|
29
|
+
Reading across members is a `UNION ALL` over the rooms you hold: your own, and any shared
|
|
30
|
+
with you. Naming a room you were not granted is an error, never a silent omission.
|
|
31
|
+
|
|
32
|
+
## Every row says where it came from
|
|
33
|
+
|
|
34
|
+
- **`user_id`** is stamped by the server: `MATERIALIZED currentUser()`. Async inserts are
|
|
35
|
+
pinned off for members, so the stamp cannot be skipped. `WHERE user_id = 'alice'` is one
|
|
36
|
+
person.
|
|
37
|
+
- **`host`** is the machine. Each install mints a random fingerprint once and keeps it in
|
|
38
|
+
`$MEMHOUSE_HOME/host.json`, and every row carries `<hostname>-<8 hex>`. It is random,
|
|
39
|
+
not derived from the machine:
|
|
40
|
+
- a derived id would merge two laptops that both answer to `MacBook-Pro`;
|
|
41
|
+
- a derived id would split one machine in two when it is renamed.
|
|
42
|
+
|
|
43
|
+
`memhouse status` shows this machine's id. `memhouse doctor` names every host in your
|
|
44
|
+
rooms.
|
|
45
|
+
- **`origin`** says who wrote the row. The shipper writes `ship`; an import writes its own
|
|
46
|
+
value.
|
|
47
|
+
|
|
48
|
+
## The house never destroys what it cannot rebuild
|
|
49
|
+
|
|
50
|
+
**The shipper is insert-only.** It runs no `DELETE`, no `TRUNCATE`, and no mutation of
|
|
51
|
+
any kind, so no privilege it holds can lose you a row.
|
|
52
|
+
|
|
53
|
+
That has a cost. ReplacingMergeTree deduplicates but does not diff: a re-parse that yields
|
|
54
|
+
*fewer* messages leaves the old, higher-positioned rows in place. A shorter re-parse has
|
|
55
|
+
three possible causes:
|
|
56
|
+
|
|
57
|
+
- an adapter bug was fixed, and the extra rows were junk;
|
|
58
|
+
- Claude Code **compacted** the transcript;
|
|
59
|
+
- the editor's retention window removed it.
|
|
60
|
+
|
|
61
|
+
In the last two cases the house holds the only surviving copy. So rows carry an `epoch`,
|
|
62
|
+
which says which parse of the session they belong to:
|
|
63
|
+
|
|
64
|
+
- **Nothing changed**, or the session just grew: the same epoch. Rows deduplicate exactly,
|
|
65
|
+
and no extra storage is used.
|
|
66
|
+
- **Shorter, or rewritten at a position the house already holds:** the new parse goes to
|
|
67
|
+
`epoch + 1`. The old parse stays complete and readable.
|
|
68
|
+
|
|
69
|
+
Reads show one parse per session, the newest, so counts, tokens and cost are not
|
|
70
|
+
inflated. The skills, the dashboard and `memhouse search` all filter this way; hand-written
|
|
71
|
+
queries should too ([the clause](search-and-resume.md#writing-your-own-queries)).
|
|
72
|
+
|
|
73
|
+
**Imported history is protected the same way.** `origin` is in the sorting key of
|
|
74
|
+
`messages` and `tool_calls`, so a shipped row can never collapse an imported one. Anything
|
|
75
|
+
imported from an older house, another product, or a machine that no longer exists survives
|
|
76
|
+
every re-ship. `sessions` is the deliberate exception: it holds one row per session, with
|
|
77
|
+
no `origin` or `epoch` in its key. A session's metadata has a single current version, and
|
|
78
|
+
a second row would make every rollup count its messages twice.
|
|
79
|
+
|
|
80
|
+
The shipper checks the rooms' sorting keys before it writes, and refuses if they are
|
|
81
|
+
wrong. See [Update](update.md#when-the-house-itself-must-change).
|
|
82
|
+
|
|
83
|
+
## What is not in the house
|
|
84
|
+
|
|
85
|
+
- Nothing is embedded, summarised or rewritten. Text is stored as the editor wrote it,
|
|
86
|
+
truncated at 50,000 characters per message and 20,000 per tool argument.
|
|
87
|
+
- No LLM runs anywhere in memhouse's write or read path. The skills are read by your agent,
|
|
88
|
+
which does the reading and reasoning.
|
|
89
|
+
- The local transcript files stay where the editor put them. memhouse reads them and never
|
|
90
|
+
modifies them.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Start with the two diagnostics. Both are read-only.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
memhouse doctor # the whole pipeline, one line per check
|
|
7
|
+
memhouse discover # what each editor adapter sees on this machine
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## An editor ships nothing
|
|
11
|
+
|
|
12
|
+
`discover` lists every adapter. Each one is in one of three states:
|
|
13
|
+
|
|
14
|
+
- it has sessions, with a count;
|
|
15
|
+
- it has none, because the editor is not used on this machine;
|
|
16
|
+
- it was **skipped**, with the reason.
|
|
17
|
+
|
|
18
|
+
A skipped adapter and an editor you do not have both contribute zero sessions, so
|
|
19
|
+
memhouse always says which one it is. Common causes, one store at a time:
|
|
20
|
+
|
|
21
|
+
- **A SQLite store locked by the running editor:** `state.vscdb` for Cursor and VS Code.
|
|
22
|
+
The next pass retries.
|
|
23
|
+
- **A store the editor moved or changed** in a new release.
|
|
24
|
+
- **A scope that excludes it:** `MEMHOUSE_EDITORS` or a `MEMHOUSE_<EDITOR>_ROOTS` in the
|
|
25
|
+
env file. See [Configuration](configuration.md#which-sessions-ship).
|
|
26
|
+
|
|
27
|
+
SQLite comes from Node itself (`node:sqlite`), so there is no native module to rebuild.
|
|
28
|
+
|
|
29
|
+
## Status says the shipper runs, but nothing new arrives
|
|
30
|
+
|
|
31
|
+
`memhouse status` reads the shipper's last pass from its log, and shows a failing pass
|
|
32
|
+
as a failure.
|
|
33
|
+
|
|
34
|
+
- **`Authentication failed`:** the password in `~/.memhouse/env` is no longer the
|
|
35
|
+
member's. This happens after a `passwd` on another machine, or a reset by the admin. Put
|
|
36
|
+
the current password into `MEMHOUSE_PASSWORD` in `~/.memhouse/env` (or run
|
|
37
|
+
`memhouse setup`, which asks), then restart the shipper.
|
|
38
|
+
- **A pass that keeps withholding a session:** an adapter returned nothing for a session
|
|
39
|
+
the house already holds. The shipper refuses to replace a transcript with an empty one,
|
|
40
|
+
and says so on every pass, until the store is readable again.
|
|
41
|
+
- **Nothing to ship:** sessions are only shipped once the editor has written them.
|
|
42
|
+
|
|
43
|
+
## Daemons don't survive a reboot
|
|
44
|
+
|
|
45
|
+
`memhouse start` uses pidfiles. Its daemons outlive the shell but not a restart. Use
|
|
46
|
+
`memhouse service install` ([Daemons](daemons.md)). On Linux, also enable lingering, or
|
|
47
|
+
user services stop at logout. A rootless podman house from `deploy --local` has the same
|
|
48
|
+
exposure, and `deploy` prints the fix.
|
|
49
|
+
|
|
50
|
+
## The dashboard is empty, or stale
|
|
51
|
+
|
|
52
|
+
- **It reads the house, not the machine.** Check `memhouse status` for the connection
|
|
53
|
+
first.
|
|
54
|
+
- **It reads precomputed stats,** refreshed at the end of each pass that shipped
|
|
55
|
+
something. On a house where nothing has shipped for a day, the stats age out, and a
|
|
56
|
+
running dashboard keeps reading them. Restart it (`memhouse stop && memhouse start`). It
|
|
57
|
+
then reads the rooms directly, until the next pass that ships refreshes the stats.
|
|
58
|
+
A fix is planned.
|
|
59
|
+
- **After an upgrade it shows the old version:** restart it (`memhouse update` does this).
|
|
60
|
+
|
|
61
|
+
## `EACCES` during `npm install -g`
|
|
62
|
+
|
|
63
|
+
See [Install](install.md#eacces-on-install). `sudo` is right only when the npm prefix is
|
|
64
|
+
owned by root.
|
|
65
|
+
|
|
66
|
+
## `refused`, `ACCESS_DENIED`, `Not enough privileges`
|
|
67
|
+
|
|
68
|
+
- For a member, these are expected outside `mem.<you>_*` and the rooms shared with you.
|
|
69
|
+
`memhouse whoami` shows what your credential may do.
|
|
70
|
+
- For `invite`, `members` or `convert`: you need the admin credential. See
|
|
71
|
+
[Admin password](admin-password.md).
|
|
72
|
+
- A room that should be yours but is refused usually means the house is older than this
|
|
73
|
+
version. Run `memhouse doctor`, then `memhouse migrate` if it says so.
|
|
74
|
+
|
|
75
|
+
## Still stuck
|
|
76
|
+
|
|
77
|
+
Run `memhouse doctor` and `memhouse status --json`, and open an issue with both outputs.
|
|
78
|
+
Check them before posting, and remove anything private, such as a host name.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Uninstall
|
|
2
|
+
|
|
3
|
+
Three tiers. **None of them touches the memory in the house.**
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
memhouse uninstall # stop everything; KEEP the config and this machine's identity
|
|
7
|
+
memhouse uninstall --credentials # also forget the house and its password
|
|
8
|
+
memhouse uninstall --full-removal # all of ~/.memhouse, identity included
|
|
9
|
+
npm uninstall -g memhouse # then remove the package
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- **The default is the conservative one.** Stopping the shipper is routine: before an
|
|
13
|
+
upgrade, while debugging, or when a laptop should go quiet for a week. It should not
|
|
14
|
+
also mean "forget which house I use and who this machine is".
|
|
15
|
+
- **`--full-removal` asks first.** A later reinstall mints a **new** machine identity, and
|
|
16
|
+
this machine then reads as two machines in your history.
|
|
17
|
+
- **Your memory stays.** The rooms in ClickHouse are untouched by every tier. What the
|
|
18
|
+
shipper wrote can be rebuilt from the local session stores with `memhouse ship --full`,
|
|
19
|
+
for as long as the editors keep them.
|
|
20
|
+
|
|
21
|
+
## Removing the house itself
|
|
22
|
+
|
|
23
|
+
- **A local house** from `deploy --local`: `memhouse deploy --down` removes the container
|
|
24
|
+
**and its data volume**. That deletes the memory, and there is no undo.
|
|
25
|
+
- **A shared server:** an administrator drops your rooms and user. See
|
|
26
|
+
[Operate a house](operate.md).
|
|
27
|
+
|
|
28
|
+
## Removing the skills
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
memhouse plugins remove claude
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This removes the `/mem:*` skills, and the instance binding, from the Claude Code config
|
|
35
|
+
directories they were installed into.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Update
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
memhouse update # upgrade along this install's channel, restart the daemons, check the house
|
|
5
|
+
memhouse update --check # compare versions, change nothing
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
`update` does three things that a bare `npm install -g memhouse@latest` does not:
|
|
9
|
+
|
|
10
|
+
1. **It restarts the daemons.** A running shipper keeps parsing with the code it booted
|
|
11
|
+
with. One once ran for 1 day 16 hours out of a directory that had since been moved.
|
|
12
|
+
2. **It checks whether the house needs a migration** for the new version, and offers to run
|
|
13
|
+
it. `--migrate` runs it without asking.
|
|
14
|
+
3. **It stays on your channel** (`MEMHOUSE_CHANNEL`). See
|
|
15
|
+
[Configuration](configuration.md#channels).
|
|
16
|
+
|
|
17
|
+
Upgraded by hand already? Finish the job without reinstalling:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g memhouse@latest
|
|
21
|
+
memhouse update --no-install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The daemons also notice on their own. Each one compares the installed version with the
|
|
25
|
+
version it booted with, and hands over when they differ: under systemd or launchd it
|
|
26
|
+
exits so the supervisor restarts it; with nothing supervising, it re-executes itself.
|
|
27
|
+
`update` just makes the switch immediate.
|
|
28
|
+
|
|
29
|
+
## Several machines on one house
|
|
30
|
+
|
|
31
|
+
Every machine of every member runs its own shipper. Upgrade them all.
|
|
32
|
+
|
|
33
|
+
- The release notes for each version say whether the order matters and whether mixed
|
|
34
|
+
versions can ship side by side. See [`docs/releases/`](../releases/).
|
|
35
|
+
- `memhouse status` lists every machine writing to your rooms, with the version each
|
|
36
|
+
last reported and when it last shipped. A machine that is behind is named there.
|
|
37
|
+
- Machines that stopped writing long ago are folded into one line. That is history, not
|
|
38
|
+
a fault. `memhouse status --all` lists them.
|
|
39
|
+
|
|
40
|
+
## When the house itself must change
|
|
41
|
+
|
|
42
|
+
Some releases need the rooms rebuilt, for example when a sorting key changes. The shipper
|
|
43
|
+
checks the rooms' keys before it writes, and **refuses** to write into a house it does not
|
|
44
|
+
match, so an old house cannot be corrupted by a new shipper. To bring the house up to
|
|
45
|
+
date:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
memhouse migrate --dry-run # what would run
|
|
49
|
+
memhouse migrate # copy, atomic swap; nothing is deleted
|
|
50
|
+
memhouse doctor # every line a check mark
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A migration copies each room, swaps it in atomically, and keeps the old one as
|
|
54
|
+
`<room>_pre_<migration>` for you to drop when you are satisfied. The house records the move
|
|
55
|
+
in its `events` room, and `doctor` reads it back.
|
|
56
|
+
|
|
57
|
+
## Upgrading from 0.9.x
|
|
58
|
+
|
|
59
|
+
0.9.x predates migrations, and `update` runs the **old** version's code (it replaces itself
|
|
60
|
+
mid-run), so it cannot prompt. After the upgrade, the first ship refuses (nothing is lost)
|
|
61
|
+
and names the one command to run by hand: `memhouse migrate`.
|
|
62
|
+
|
|
63
|
+
## Upgrading a pre-0.18 house (a database per member)
|
|
64
|
+
|
|
65
|
+
0.18 moved every member's rooms into one database, as `mem.<member>_*`. An operator moves
|
|
66
|
+
an old house once with `memhouse convert`; see [Operate a house](operate.md#convert). After
|
|
67
|
+
that, members upgrade with `memhouse update`, and the shipper adopts the moved rooms
|
|
68
|
+
itself.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Release notes
|
|
2
|
+
|
|
3
|
+
One document per release, from 0.18.10 onward. Each says what changed, what you need to
|
|
4
|
+
do, how to upgrade, what is deprecated and when it goes, and what was deferred.
|
|
5
|
+
|
|
6
|
+
| version | date | headline |
|
|
7
|
+
|---|---|---|
|
|
8
|
+
| [0.18.10](v0.18.10.md) | 2026-10-08 | security: the admin password never on argv; no member password printed; a scoped share reaches only its scope |
|
|
9
|
+
|
|
10
|
+
Earlier versions: [`CHANGELOG.md`](../../CHANGELOG.md).
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# memhouse 0.18.10 — 2026-10-08
|
|
2
|
+
|
|
3
|
+
A security release:
|
|
4
|
+
|
|
5
|
+
- an admin password no longer has to go on the command line;
|
|
6
|
+
- no command prints a member password;
|
|
7
|
+
- a scoped share no longer reaches past its scope.
|
|
8
|
+
|
|
9
|
+
Everyone on 0.18.x should upgrade.
|
|
10
|
+
|
|
11
|
+
**No drill was run for this release; waived by the pilot (2026-10-08). The drill runs before 0.19.0.**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
memhouse update # or: npm install -g memhouse@latest, then memhouse update --no-install
|
|
15
|
+
memhouse status # every line a check mark
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Nothing in the house changes: no migration, no new room, no re-ship. Machines on 0.18.9
|
|
19
|
+
and 0.18.10 can ship into the same house side by side.
|
|
20
|
+
|
|
21
|
+
## Security
|
|
22
|
+
|
|
23
|
+
### An admin password never has to go on the command line (#14)
|
|
24
|
+
|
|
25
|
+
The admin-assisted commands (`install --member`, `invite`, `passwd`, `convert`,
|
|
26
|
+
`members`) now take the admin password from, in order:
|
|
27
|
+
|
|
28
|
+
1. `--admin-password-file <file>`, a file holding the password (one trailing newline is
|
|
29
|
+
dropped), or `--admin-password-file -` to read it from stdin;
|
|
30
|
+
2. `--admin-password <value>` (still accepted, with a warning; see below);
|
|
31
|
+
3. `MEMHOUSE_ADMIN_PASSWORD` in the environment, which is how `with-secret` lends a secret
|
|
32
|
+
to one command;
|
|
33
|
+
4. a credential this install already keeps, but only for the same admin user;
|
|
34
|
+
5. a prompt, when there is a terminal to prompt on.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
with-secret MEMHOUSE_ADMIN_PASSWORD=keychain:pilot/house-admin -- \
|
|
38
|
+
memhouse invite alice --url https://house.example.com --admin-user default
|
|
39
|
+
printf '%s' "$ADMIN_PASSWORD" | memhouse invite alice --url … --admin-user default --admin-password-file -
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Before this release, `--admin-user` as a flag switched every other source off. The only
|
|
43
|
+
scriptable admin path therefore put the password on argv, where `ps` shows it for as long
|
|
44
|
+
as the command runs and shell history keeps it. Found by O's sandbox rehearsal (finding 6).
|
|
45
|
+
With no source and no terminal, the command stops and prints the three ways above instead
|
|
46
|
+
of hanging or guessing.
|
|
47
|
+
|
|
48
|
+
### An admin install no longer saves the admin credential (#14)
|
|
49
|
+
|
|
50
|
+
`install --admin-user …` builds the member and then ships as the member. It used to write
|
|
51
|
+
the admin credential into `~/.memhouse/env` as well. The help said it was saved; every
|
|
52
|
+
agent-facing text said it never was. Now it is saved only with `--keep-admin`, or when
|
|
53
|
+
the env file already keeps that same admin. Credentials already saved keep working.
|
|
54
|
+
|
|
55
|
+
### A daemon never carries the admin credential (#14)
|
|
56
|
+
|
|
57
|
+
- Service units (systemd, launchd) drop `MEMHOUSE_ADMIN_*` before they inline the env.
|
|
58
|
+
- The shipper and the dashboard are started without it, even when the shell exported it.
|
|
59
|
+
- `deploy --local` passes the ClickHouse password to `docker run` through the environment,
|
|
60
|
+
no longer as `-e CLICKHOUSE_PASSWORD=<value>` on its argv.
|
|
61
|
+
|
|
62
|
+
A unit test now fails on any spawn or exec in the shipped code that puts a password on
|
|
63
|
+
argv.
|
|
64
|
+
|
|
65
|
+
### No command prints a member password (G23)
|
|
66
|
+
|
|
67
|
+
`install --admin-user` used to show the generated member password once, on the terminal.
|
|
68
|
+
It was already written to the env file, so the terminal copy served no one. It landed in
|
|
69
|
+
scrollback and, through `/mem:access` and `/mem:admin`, in an agent's transcript, which
|
|
70
|
+
memhouse ships into the house, where the archive is insert-only. (#20)
|
|
71
|
+
|
|
72
|
+
- **Install and invite never print a generated member password.** It goes to the env file
|
|
73
|
+
(install) or the invite file (invite), and nowhere else.
|
|
74
|
+
- **A failed install cleans up after itself.** If the grant fails after the member was
|
|
75
|
+
created, the run drops that member, so no account is left with a password nobody holds.
|
|
76
|
+
- **`install --print-sql` and `invite --print-sql` print a placeholder,
|
|
77
|
+
`IDENTIFIED BY <member-password>`.** As printed, that line does not parse. The
|
|
78
|
+
administrator puts a quoted password they generate in its place, and the same password
|
|
79
|
+
goes into the env-file lines the command prints for the member. From `invite`, those
|
|
80
|
+
lines carry `MEMHOUSE_INVITE=1`, so the member's `install --env` offers to rotate it.
|
|
81
|
+
A `--member-password` given with `--print-sql` is not echoed.
|
|
82
|
+
- **Tests guard it.**
|
|
83
|
+
- A unit test fails on any print call that evaluates a password: as an argument,
|
|
84
|
+
concatenated, or interpolated.
|
|
85
|
+
- `misc/invite-matrix.sh` runs every install, invite and passwd path against a real
|
|
86
|
+
ClickHouse and checks each output against the real passwords.
|
|
87
|
+
|
|
88
|
+
### A scoped share shares only what its scope filters (#13)
|
|
89
|
+
|
|
90
|
+
`memhouse share <user> --only <scope>` granted `SELECT` on the member's whole pattern,
|
|
91
|
+
`mem.<member>_*`, while its row policies covered only `sessions`, `messages` and
|
|
92
|
+
`tool_calls`. A grantee scoped to one project could therefore read these in full:
|
|
93
|
+
|
|
94
|
+
- `<member>_session_stats`: the opening prompt, folder, project and token totals of
|
|
95
|
+
every session in every project;
|
|
96
|
+
- the model and tool stats;
|
|
97
|
+
- `<member>_meta`, including the member's other `share:` records;
|
|
98
|
+
- `<member>_events`;
|
|
99
|
+
- any table the pattern matched later.
|
|
100
|
+
|
|
101
|
+
Now:
|
|
102
|
+
|
|
103
|
+
- **A scoped share is granted table by table,** on exactly the three filtered rooms.
|
|
104
|
+
Narrowing an existing full share to a scope takes the wildcard back first, so a failure
|
|
105
|
+
part-way leaves the grantee with less access, never more.
|
|
106
|
+
- **`--revoke` withdraws the grants before it drops the row policies.** The old order
|
|
107
|
+
failed open: a `REVOKE` refused after the policies were gone left an unfiltered share.
|
|
108
|
+
A failed cleanup now keeps the policies unless every grant is confirmed withdrawn.
|
|
109
|
+
- **`--only ""` and whitespace-only scopes are refused.** An unset `--only "$SCOPE"`
|
|
110
|
+
used to fall through to a full share.
|
|
111
|
+
- **The permissive-server check before a scoped share runs.** Its probe table sat
|
|
112
|
+
outside the member's grant, so it was refused on every server and always reported
|
|
113
|
+
"could not verify".
|
|
114
|
+
|
|
115
|
+
A full share (no `--only`) is unchanged: it reaches everything in `mem.<member>_*`, by
|
|
116
|
+
design, and says so.
|
|
117
|
+
|
|
118
|
+
**Action for existing scoped shares:** they keep their old wildcard until re-run. On the
|
|
119
|
+
sharing member's machine, run `memhouse share <user> --only <scope>` again for each one;
|
|
120
|
+
`memhouse share --list` shows them.
|
|
121
|
+
|
|
122
|
+
## Deprecations
|
|
123
|
+
|
|
124
|
+
| what | since | removed in | use instead |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `--admin-password <value>` on the command line | 0.18.10 (2026-10-08) | **0.19.0** | `MEMHOUSE_ADMIN_PASSWORD` (via `with-secret`), `--admin-password-file <file>`, or `--admin-password-file -` |
|
|
127
|
+
|
|
128
|
+
It still works in 0.18.x and prints one warning on stderr. Move scripts now.
|
|
129
|
+
|
|
130
|
+
## Upgrading
|
|
131
|
+
|
|
132
|
+
- **One machine:** `memhouse update`. The daemons restart on the new code.
|
|
133
|
+
- **Upgraded by hand** (`npm install -g memhouse@latest`): run
|
|
134
|
+
`memhouse update --no-install` afterwards to restart the daemons.
|
|
135
|
+
- **Several machines on one house:** no ordering is needed. This release changes no row
|
|
136
|
+
shape, no room and no shipper behaviour.
|
|
137
|
+
- **Share owners:** re-run each scoped share once (above).
|
|
138
|
+
- **Scripts and CI that pass `--admin-password`:** move to `MEMHOUSE_ADMIN_PASSWORD` or
|
|
139
|
+
`--admin-password-file` before 0.19.0.
|
|
140
|
+
- **Scripts that read a member password from `install --admin-user` output:** read
|
|
141
|
+
`MEMHOUSE_PASSWORD` from the env file instead. The output no longer contains it.
|
|
142
|
+
- **Scripts that run `--print-sql` output as is:** put a quoted password where
|
|
143
|
+
`<member-password>` stands. As printed, the line is refused by the server.
|
|
144
|
+
- **If an admin credential is in `~/.memhouse/env`** and you did not mean to keep it,
|
|
145
|
+
remove the `MEMHOUSE_ADMIN_USER` / `MEMHOUSE_ADMIN_PASSWORD` lines. memhouse never
|
|
146
|
+
needs it to ship.
|
|
147
|
+
|
|
148
|
+
## Also in this release
|
|
149
|
+
|
|
150
|
+
- **The documentation standard** (#17). See [Documentation](#documentation) below. This
|
|
151
|
+
does change the package: it now also ships `docs/`, `examples/`, `AGENTS.md`,
|
|
152
|
+
`SECURITY.md` and `CHANGELOG.md`.
|
|
153
|
+
|
|
154
|
+
These do not change the installed package:
|
|
155
|
+
|
|
156
|
+
- **CI's tarball audit can fail now.** Its forbidden-file check was `! grep`, which
|
|
157
|
+
`set -e` never acts on, so it passed whatever the tarball held.
|
|
158
|
+
- **gentar arena adoption** (#10). memhouse is now a gentar subject. Its suites are
|
|
159
|
+
`memhouse-house` and `memhouse-install`, kit v0.9.2, and they install from `npm pack`
|
|
160
|
+
exactly as a publish ships. The phase-1 check runs on GitHub-hosted runners on every
|
|
161
|
+
push and PR. Full arena runs need a self-hosted bench; the one used before the
|
|
162
|
+
repository went public was deregistered then.
|
|
163
|
+
- **memhouse.io describes 0.18** (#12): one layout, five skills, the honest editor count
|
|
164
|
+
(16 adapters reading 18 apps).
|
|
165
|
+
- `memhouse.io/.wrangler` (wrangler's local cache) is no longer tracked.
|
|
166
|
+
- `misc/one-layout-matrix.sh` now asserts the scoped-share boundary, and
|
|
167
|
+
`misc/invite-matrix.sh` the admin-password sources: environment, stdin, `--keep-admin`,
|
|
168
|
+
and none. The file source is covered by unit tests. CI runs both matrices against
|
|
169
|
+
ClickHouse 25.11 and 26.7.
|
|
170
|
+
|
|
171
|
+
## Not in this release
|
|
172
|
+
|
|
173
|
+
**The gentar kit v0.9.3 bump (#18) also moves to 0.18.11.** It changes CI only.
|
|
174
|
+
|
|
175
|
+
**The subagent re-ship fix moves to 0.18.11.** It is on branch `claude/subagent-stable-seq`
|
|
176
|
+
and stops a Claude Code session with subagents from re-shipping whole on every pass, which
|
|
177
|
+
is what grew one house to 16.2 M message rows for about 295 k real ones. It was reviewed
|
|
178
|
+
before release by four models. Three said "ship with fixes"; the fourth said "do not ship",
|
|
179
|
+
mostly for pre-existing issues. Its pre-tag fixes are not all done, and this release was
|
|
180
|
+
not held for them.
|
|
181
|
+
|
|
182
|
+
Status of the 0.18.10 pre-tag list (`review-0.18.10-consolidated`):
|
|
183
|
+
|
|
184
|
+
| item | what | status |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| B1 | scoped share reads past its scope | **landed** (#13) |
|
|
187
|
+
| G3 | share re-scope or revoke fails open | **landed** (#13) |
|
|
188
|
+
| G24 | `--only ""` is a full share | **landed** (#13) |
|
|
189
|
+
| B7 | permissive-server probe never runs | **landed** (#13) |
|
|
190
|
+
| B5 (part) | admin password in argv, units and daemons | **landed** (#14); `0600` enforcement on existing env/unit files is still open |
|
|
191
|
+
| A1 | version bump incl. `package-lock.json` | **landed** here |
|
|
192
|
+
| G23 | admin install and `--print-sql` print the member password | **landed** (#20) |
|
|
193
|
+
| A2 | `/mem:recall` "recent sessions" uses a column the table lacks | deferred to 0.18.11 |
|
|
194
|
+
| A3 | plugin `HOUSE.md` subagent queries lack the epoch filter | deferred to 0.18.11 (with the RC, which makes it matter) |
|
|
195
|
+
| A4–A7 | RC log text, `update --no-install` checks, RC changelog notes | deferred to 0.18.11 (they are the RC's own surface) |
|
|
196
|
+
| A8 | stale prose (ts interpolation, shrink, host id) | deferred to 0.18.11 |
|
|
197
|
+
| G4 | `invite --url` connects with the stored credential to the invitee-facing URL | deferred to 0.18.11 (security) |
|
|
198
|
+
| G26 | skills point at `../reference/HOUSE.md`; the file is `../../reference/HOUSE.md` | deferred to 0.18.11 |
|
|
199
|
+
| G11 | an idle house's dashboard goes empty after the stats TTL | deferred to 0.18.11 |
|
|
200
|
+
| GLM-F1 | a subagent file read before its first line is written re-sorts its siblings | deferred to 0.18.11 (with the RC) |
|
|
201
|
+
|
|
202
|
+
## Documentation
|
|
203
|
+
|
|
204
|
+
This release meets the documentation standard (#17):
|
|
205
|
+
|
|
206
|
+
- a short README: what memhouse is, why, and how to install it;
|
|
207
|
+
- `docs/`, with three tutorials and twelve guides;
|
|
208
|
+
- `examples/`, four runnable setups from one machine to a self-hosted team house, each
|
|
209
|
+
with its own README;
|
|
210
|
+
- an install and deploy section for agents in `AGENTS.md`.
|
|
211
|
+
|
|
212
|
+
The npm package now ships `docs/`, `examples/`, `AGENTS.md`, `SECURITY.md` and
|
|
213
|
+
`CHANGELOG.md`, so an installed copy carries its own manual. `change.log` (#19) lists
|
|
214
|
+
every tag and what it changed.
|
|
215
|
+
|
|
216
|
+
## Verification
|
|
217
|
+
|
|
218
|
+
- `npm test` passes.
|
|
219
|
+
- CI is green on `main` at the release commit: the syntax gate, unit tests, the
|
|
220
|
+
one-layout and invite matrices, and a headless e2e on ClickHouse 25.11 and 26.7.
|
|
221
|
+
- No live house was touched to prepare this release.
|
|
222
|
+
- **No drill was run for this release; waived by the pilot (2026-10-08). The drill runs before 0.19.0.**
|
|
223
|
+
`AGENTS.md` asks for a drill before a release that changes a surface a person touches,
|
|
224
|
+
and this one changes install, invite and share. A drill was prepared on the testbed, but
|
|
225
|
+
its setup was refused by this session's permission layer, and the pilot chose to release
|
|
226
|
+
without it.
|