memhouse 0.18.12 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/AGENTS.md +22 -151
  2. package/CHANGELOG.md +263 -29
  3. package/LICENSE +202 -22
  4. package/LICENSES/agentlytics-MIT.txt +21 -0
  5. package/LICENSES/memhouse-MIT-until-0.18.11.txt +22 -0
  6. package/LICENSES/models.dev-MIT.txt +22 -0
  7. package/NOTICE +28 -0
  8. package/README.md +42 -20
  9. package/SECURITY.md +21 -8
  10. package/bin/memhouse.js +2223 -311
  11. package/docs/README.md +11 -9
  12. package/docs/guides/admin-password.md +37 -10
  13. package/docs/guides/configuration.md +13 -6
  14. package/docs/guides/daemons.md +64 -3
  15. package/docs/guides/import-claude-ai.md +204 -0
  16. package/docs/guides/install.md +11 -6
  17. package/docs/guides/invite.md +21 -7
  18. package/docs/guides/operate.md +114 -7
  19. package/docs/guides/search-and-resume.md +72 -3
  20. package/docs/guides/share.md +91 -52
  21. package/docs/guides/storage.md +44 -4
  22. package/docs/guides/troubleshooting.md +121 -14
  23. package/docs/guides/uninstall.md +4 -3
  24. package/docs/guides/update.md +16 -0
  25. package/docs/guides/upgrade-to-0.20.md +188 -0
  26. package/docs/releases/README.md +2 -1
  27. package/docs/releases/v0.18.10.md +5 -5
  28. package/docs/releases/v0.18.11.md +3 -3
  29. package/docs/releases/v0.20.0.md +627 -0
  30. package/docs/releases/v0.20.1.md +114 -0
  31. package/docs/tutorials/01-first-house.md +10 -7
  32. package/docs/tutorials/02-join-a-house.md +24 -15
  33. package/docs/tutorials/03-share-your-memory.md +40 -31
  34. package/editors/antigravity.js +1 -80
  35. package/editors/base.js +463 -9
  36. package/editors/bridge.js +119 -0
  37. package/editors/claude.js +273 -105
  38. package/editors/codex.js +278 -52
  39. package/editors/copilot.js +1 -70
  40. package/editors/cursor.js +1 -75
  41. package/editors/derive-fingerprints.json +18 -0
  42. package/editors/harness-paths.js +69 -0
  43. package/editors/index.js +1 -20
  44. package/editors/normalise/agy.js +910 -0
  45. package/editors/normalise/copilot-cli.js +321 -0
  46. package/editors/normalise/cursor-agent.js +852 -0
  47. package/editors/normalise/cursor.js +474 -0
  48. package/editors/normalise/devin.js +575 -0
  49. package/editors/normalise/gemini-cli.js +602 -0
  50. package/editors/normalise/goose.js +673 -0
  51. package/editors/normalise/opencode.js +633 -0
  52. package/editors/normalise/vscode.js +549 -0
  53. package/editors/normalise/zed.js +473 -0
  54. package/editors/opencode-v4.js +119 -0
  55. package/editors/scope.js +14 -3
  56. package/editors/sessions-log.js +349 -0
  57. package/editors/sqlite.js +28 -1
  58. package/editors/vscode.js +1 -72
  59. package/editors/windsurf.js +1 -143
  60. package/examples/01-local-one-machine/README.md +24 -19
  61. package/examples/01-local-one-machine/run.sh +1 -1
  62. package/examples/02-existing-clickhouse/README.md +30 -22
  63. package/examples/02-existing-clickhouse/run.sh +5 -4
  64. package/examples/03-team-house/README.md +44 -37
  65. package/examples/03-team-house/run.sh +35 -18
  66. package/examples/04-self-hosted-team/README.md +29 -14
  67. package/examples/04-self-hosted-team/compose.yml +5 -2
  68. package/examples/04-self-hosted-team/smoke.sh +26 -12
  69. package/examples/README.md +8 -4
  70. package/examples/fixtures/README.md +4 -1
  71. package/examples/fixtures/make-sessions.js +8 -5
  72. package/memhouse/admin-secret.js +89 -24
  73. package/memhouse/argv-secrets.js +64 -0
  74. package/memhouse/channel.js +49 -9
  75. package/memhouse/codex-plugin.js +194 -0
  76. package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +2 -1
  77. package/memhouse/delivery/AGENT-INSTALL.md +22 -11
  78. package/memhouse/delivery/INVITATION.md.tpl +44 -45
  79. package/memhouse/delivery/PROMPT.md +53 -5
  80. package/memhouse/delivery/UPGRADE.md.tpl +1 -1
  81. package/memhouse/delivery/codex/AGENTS-BLOCK.md +11 -0
  82. package/memhouse/delivery/codex/SKILL.md +50 -0
  83. package/memhouse/delivery/kernel-install.md +17 -9
  84. package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
  85. package/memhouse/delivery/plugin/reference/HOUSE.md +168 -41
  86. package/memhouse/delivery/plugin/skills/access/SKILL.md +13 -5
  87. package/memhouse/delivery/plugin/skills/admin/SKILL.md +14 -6
  88. package/memhouse/delivery/plugin/skills/house/SKILL.md +25 -8
  89. package/memhouse/delivery/plugin/skills/recall/SKILL.md +66 -8
  90. package/memhouse/delivery/plugin/skills/sql/SKILL.md +66 -7
  91. package/memhouse/deploy.js +1 -1
  92. package/memhouse/flags.js +35 -18
  93. package/memhouse/help.js +37 -0
  94. package/memhouse/host.js +82 -2
  95. package/memhouse/house/HOUSE.md +2 -2
  96. package/memhouse/house/convert-profile.js +638 -0
  97. package/memhouse/house/convert-v3.js +742 -0
  98. package/memhouse/house/doctor-v3.js +324 -0
  99. package/memhouse/house/host-alias.js +85 -0
  100. package/memhouse/house/house.js +3 -3
  101. package/memhouse/house/migrate.js +2 -2
  102. package/memhouse/house/profile-dedupe.js +183 -0
  103. package/memhouse/house/profile-edges.js +554 -0
  104. package/memhouse/house/profile-house.js +430 -0
  105. package/memhouse/house/profile-rollup.js +190 -0
  106. package/memhouse/house/q-cli.js +863 -0
  107. package/memhouse/house/queries-v3.js +1013 -0
  108. package/memhouse/house/reconcile-v3.js +192 -0
  109. package/memhouse/house/records-to-profile.js +174 -0
  110. package/memhouse/house/records-to-rows.js +169 -0
  111. package/memhouse/house/relocate.js +2 -1
  112. package/memhouse/house/schema-choice.js +121 -0
  113. package/memhouse/house/schema-profile.js +290 -0
  114. package/memhouse/house/schema-profile.sql.tpl +398 -0
  115. package/memhouse/house/schema-v3.js +267 -0
  116. package/memhouse/house/schema-v3.sql.tpl +213 -0
  117. package/memhouse/import/claude-ai.js +420 -0
  118. package/memhouse/import/import.js +372 -0
  119. package/memhouse/import/pyjson.js +206 -0
  120. package/memhouse/import/records.js +64 -0
  121. package/memhouse/import/writer-profile.js +192 -0
  122. package/memhouse/import/writer-v3.js +240 -0
  123. package/memhouse/import/zip.js +112 -0
  124. package/memhouse/invitation.js +102 -0
  125. package/memhouse/invitefile.js +5 -2
  126. package/memhouse/plugin-copies.js +176 -0
  127. package/memhouse/provision.js +7 -2
  128. package/memhouse/release-hint.js +24 -0
  129. package/memhouse/resume.js +4 -4
  130. package/memhouse/server/api-v3.js +632 -0
  131. package/memhouse/server/dashboard-auth.js +181 -0
  132. package/memhouse/server/dashboard-port.js +91 -0
  133. package/memhouse/server/queries.js +30 -0
  134. package/memhouse/server/server.js +118 -20
  135. package/memhouse/service.js +1 -1
  136. package/memhouse/share.js +687 -1
  137. package/memhouse/shipper/adapter-capture.js +756 -0
  138. package/memhouse/shipper/capture.js +2571 -0
  139. package/memhouse/shipper/dedupe-profile.js +237 -0
  140. package/memhouse/shipper/dedupe-v3.js +1027 -0
  141. package/memhouse/shipper/forks.js +233 -0
  142. package/memhouse/shipper/log-adapter.js +219 -0
  143. package/memhouse/shipper/maintenance.js +49 -0
  144. package/memhouse/shipper/normalise-run.js +148 -0
  145. package/memhouse/shipper/passlog.js +68 -5
  146. package/memhouse/shipper/profile-capture.js +903 -0
  147. package/memhouse/shipper/ship.js +91 -8
  148. package/memhouse/shipper/snapshots.js +193 -0
  149. package/memhouse/shipper/source-roots.js +111 -0
  150. package/memhouse/version-notice.js +66 -0
  151. package/package.json +14 -7
  152. package/public/THIRD_PARTY_NOTICES.txt +2 -2
  153. package/public/assets/index-CcNnhfM3.js +78 -0
  154. package/public/index.html +1 -1
  155. package/ui/package-lock.json +56 -37
  156. package/ui/package.json +1 -0
  157. package/ui/src/App.jsx +29 -14
  158. package/ui/src/components/ChatSidebar.jsx +69 -12
  159. package/ui/src/lib/api.js +76 -0
  160. package/ui/src/lib/dashboard-token.js +41 -0
  161. package/ui/src/main.jsx +22 -5
  162. package/ui/src/pages/Dashboard.jsx +22 -36
  163. package/ui/third-party-notices.js +119 -0
  164. package/ui/vite.config.js +5 -1
  165. package/docs/releases/v0.18.12.md +0 -59
  166. package/memhouse/COMPETITION.md +0 -221
  167. package/memhouse/DESIGN.md +0 -222
  168. package/memhouse/RESUME-AND-UPDATE.md +0 -182
  169. package/public/assets/index-CnWzrjF3.js +0 -78
@@ -26,8 +26,18 @@ GROUP BY table;
26
26
 
27
27
  -- what someone may do
28
28
  SHOW GRANTS FOR alice;
29
+
30
+ -- every scoped share's filter, and any left behind on a table that is gone
31
+ SELECT database, short_name, table, select_filter, apply_to_list,
32
+ (database, table) NOT IN (SELECT database, name FROM system.tables) AS table_gone
33
+ FROM system.row_policies
34
+ ORDER BY database, table;
29
35
  ```
30
36
 
37
+ A policy whose table is gone is dropped with `DROP ROW POLICY <short_name> ON <db>.<table>`:
38
+ dropping a table leaves its policies, and a table later made under the same name inherits them.
39
+ Members do not hold `SHOW ROW POLICIES`, so this listing is the admin's.
40
+
31
41
  ## Adding and removing people
32
42
 
33
43
  - **Add:** [`memhouse invite`](invite.md).
@@ -35,8 +45,57 @@ SHOW GRANTS FOR alice;
35
45
  member who is locked out there is no verb: run `ALTER USER <name> IDENTIFIED BY '…'` as
36
46
  the admin, and hand them an env file with the new password. Every machine shipping as
37
47
  them stops until its env file has it.
38
- - **Remove:** there is no verb yet. `DROP USER <name>` stops every machine shipping as
39
- that member. Their rooms remain until you drop them as well.
48
+ - **Remove:** there is no verb yet. Follow [Removing a member](#removing-a-member): their
49
+ shares first, then `DROP USER <name>`, which stops every machine shipping as them, then their
50
+ tables, in **both** databases.
51
+
52
+ ### Removing a member
53
+
54
+ In this order. Each step is irreversible, and the last one deletes the member's memory.
55
+
56
+ 1. **Their shares.** Run as the member, while the account still exists:
57
+ `memhouse share <reader> --revoke` for each reader `memhouse share --list` names. A share's
58
+ grants and row policies are kept by name, and neither is dropped with the tables: one left
59
+ behind reaches, or filters, a later table of the same name. If the member cannot run it, find
60
+ both as the admin (the list below is every table name the member can have), and remove each
61
+ with `REVOKE … ON <db>.<table> FROM <reader>` and `DROP ROW POLICY <name> ON <db>.<table>`:
62
+
63
+ ```sql
64
+ WITH ['alice_session_parts', 'alice_session_usage', 'alice_session_links',
65
+ 'alice_session_lifecycle', 'alice_calls', 'alice_sessions', 'alice_meta', 'alice_events',
66
+ 'alice_session_stats', 'alice_session_model_stats', 'alice_session_tool_stats',
67
+ 'alice_messages', 'alice_tool_calls', 'alice_turns',
68
+ 'alice_session_units', 'alice_session_sources', 'alice_records', 'alice_files',
69
+ 'alice_messages_pre_v3', 'alice_tool_calls_pre_v3', 'alice_sessions_pre_v3'] AS mine
70
+ SELECT 'grant' AS kind, user_name AS who, access_type AS what, database, table FROM system.grants
71
+ WHERE database IN ('mem', 'mem_archive') AND has(mine, table) AND user_name != 'alice'
72
+ UNION ALL
73
+ SELECT 'policy', short_name, '', database, table FROM system.row_policies
74
+ WHERE database IN ('mem', 'mem_archive') AND has(mine, table);
75
+ ```
76
+
77
+ A 0.18 full share is a wildcard grant, `mem.alice_*`: `SHOW GRANTS FOR <reader>` shows it.
78
+ 2. **The user:** `DROP USER alice`. Every machine shipping as them stops.
79
+ 3. **Their tables.** List them **by exact name**, never by prefix: `LIKE 'alice_%'` also matches
80
+ another member called `alice_smith`. The same list as above:
81
+
82
+ ```sql
83
+ WITH [ … the same names … ] AS mine
84
+ SELECT database, name, total_rows FROM system.tables
85
+ WHERE database IN ('mem', 'mem_archive') AND has(mine, name) ORDER BY database, name;
86
+ ```
87
+
88
+ Then drop each by its full name (`DROP TABLE mem.alice_session_parts`, …). Which of them
89
+ exist depends on the member's history:
90
+ - **on schema 3:** the six conversation tables in `mem`, plus `_meta` and `_events` once
91
+ the member has run `memhouse share` (step 1 does), and `_session_units` and
92
+ `_session_sources` in `mem_archive`;
93
+ - **from a 0.20 development build:** also `_turns` in `mem`, and `_records` and `_files` in
94
+ `mem_archive`;
95
+ - **converted from 0.18:** also `_meta`, `_events` and the three stats tables in `mem`, and
96
+ the three renamed rooms (`*_pre_v3`) in `mem_archive`;
97
+ - **still on the 0.18 rooms:** `_messages`, `_tool_calls`, `_sessions`, `_meta`, `_events` and
98
+ the stats tables, all in `mem`.
40
99
 
41
100
  **Before any destructive statement, look first.**
42
101
 
@@ -85,10 +144,58 @@ Afterwards, point each member at the new URL with `memhouse setup --url …`.
85
144
 
86
145
  ## Backups
87
146
 
88
- memhouse does not back up the house. Use ClickHouse's own `BACKUP DATABASE mem TO …`, to a
89
- disk or an object store, on a schedule, and test a `RESTORE` into a scratch server.
90
- [`examples/04-self-hosted-team`](../../examples/04-self-hosted-team/README.md) shows a nightly job,
91
- rotation, and a tested restore.
147
+ memhouse does not back up the house. Use ClickHouse's own `BACKUP`, to a disk or an object
148
+ store, on a schedule. **A schema-3 house is two databases:** `mem`, and `mem_archive`, the
149
+ capture archive every session is derived from. A backup of `mem` alone leaves the archive out.
150
+ The accounts (users, their grants, the row policies of scoped shares) live outside both, so
151
+ take them in the same statement:
152
+
153
+ ```sql
154
+ BACKUP DATABASE mem, DATABASE mem_archive,
155
+ TABLE system.users, TABLE system.roles, TABLE system.row_policies,
156
+ TABLE system.settings_profiles, TABLE system.quotas
157
+ TO File('mem-2026-10-27.zip')
158
+ ```
159
+
160
+ `File(…)` works on a stock server: the official image's configuration allows backups under
161
+ `backups/` in the server's data directory (`/var/lib/clickhouse/backups/` in the image), and
162
+ that is where the file lands. It is on the server's own disk, so copy it somewhere else. With a
163
+ backups disk configured, write `TO Disk('backups', '…')` instead; a stock server refuses
164
+ `Disk(…)` ("the 'backups.allowed_disk' configuration parameter is not set").
165
+ [`examples/04-self-hosted-team`](../../examples/04-self-hosted-team/README.md) shows such a
166
+ disk, with a nightly job, rotation, and a restore onto a fresh server. A house still on the
167
+ 0.18 rooms has no `mem_archive` until its upgrade begins: leave `DATABASE mem_archive` out until
168
+ then.
169
+
170
+ **Check a backup** without touching the live house: restore its two databases under other names,
171
+ compare, and drop the copies.
172
+
173
+ ```sql
174
+ RESTORE DATABASE mem AS mem_check, DATABASE mem_archive AS mem_archive_check
175
+ FROM File('mem-2026-10-27.zip');
176
+
177
+ -- every table on both sides
178
+ SELECT database, count() FROM system.tables
179
+ WHERE database IN ('mem', 'mem_check', 'mem_archive', 'mem_archive_check') GROUP BY database;
180
+
181
+ -- rows per table, live against restored
182
+ SELECT replaceRegexpOne(database, '_check$', '') AS db, table,
183
+ sumIf(rows, NOT endsWith(database, '_check')) AS live,
184
+ sumIf(rows, endsWith(database, '_check')) AS restored
185
+ FROM system.parts
186
+ WHERE active AND database IN ('mem', 'mem_check', 'mem_archive', 'mem_archive_check')
187
+ GROUP BY db, table ORDER BY db, table;
188
+
189
+ DROP DATABASE mem_check;
190
+ DROP DATABASE mem_archive_check;
191
+ ```
192
+
193
+ Each database and its `_check` copy should hold the same number of tables, and `restored` should
194
+ be close to `live` on every table (the house may have grown since the backup). No
195
+ `mem_archive_check` in the first result, or a table with no `restored` rows where `live` has
196
+ many, means the backup does not hold what you think. For a house still on the 0.18 rooms, leave
197
+ `DATABASE mem_archive AS mem_archive_check` out of the `RESTORE` too. These statements were
198
+ checked on ClickHouse 26.7.
92
199
 
93
200
  - **Keep backups as private as the house.** They hold every member's transcripts.
94
201
  - **Backups outlive deletions.** A row removed from the house remains in every backup
@@ -98,7 +205,7 @@ rotation, and a tested restore.
98
205
 
99
206
  | setting | why |
100
207
  |---|---|
101
- | async inserts off for members | `user_id` is stamped at insert; memhouse pins `async_insert=0` on each member, and the shipper sets it per insert too |
208
+ | async inserts off for members | the writing user is stamped at insert (`writer` on schema 3, `user_id` on the 0.18 rooms); memhouse pins `async_insert=0` on each member, and the shipper sets it per insert too |
102
209
  | `users_without_row_policies_can_read_rows` | scoped shares need it to behave as memhouse expects; `share` measures it and refuses if a policy would blind the owner |
103
210
  | `old_parts_lifetime` | how long replaced parts stay on disk after merges and mutations (480 s by default) |
104
211
  | HTTP behind TLS | members connect over HTTP(S); put the server behind TLS before exposing it beyond a LAN |
@@ -10,6 +10,26 @@ memhouse search clickhouse auth --limit 50
10
10
  This is a full-text search across every session in your rooms, from every machine and
11
11
  every editor. Each hit names the session id, project, date and the matching line.
12
12
 
13
+ ### On schema 3: the named queries
14
+
15
+ A house on schema 3 (every new house from 0.20) also answers named queries, the same ones the
16
+ `/mem` skills use:
17
+
18
+ ```bash
19
+ memhouse q # the list, with each one's usage
20
+ memhouse q recent --limit 20 # the latest sessions
21
+ memhouse q search clickhouse auth
22
+ memhouse q session <ref> # one session, page by page
23
+ memhouse q spend --by model # tokens and cost
24
+ memhouse q removed # what is kept as removed versions
25
+ ```
26
+
27
+ - **A session's ref names its store,** `<harness>:<session_id>~<token>`, because the same id
28
+ on another machine, or in another data folder, is another session. `q recent` and
29
+ `q search` print full refs; a bare id still works where only one session has it.
30
+ - **`q removed`** lists what every other read hides: a message an editor reverted, rewound
31
+ or deleted, kept as a removed version. Narrow it with a ref, `--harness` or `--days`.
32
+
13
33
  ## Resume
14
34
 
15
35
  ```bash
@@ -35,7 +55,7 @@ memhouse stats # sessions, messages and tokens per editor
35
55
  memhouse sessions-query # the per-session rollup as SQL, to paste into your own queries
36
56
  ```
37
57
 
38
- The dashboard (`memhouse start`, then http://localhost:4640) shows the same and more:
58
+ The dashboard (`memhouse start`, then `memhouse dashboard`) shows the same and more:
39
59
 
40
60
  - cost by model, editor and project;
41
61
  - tool-call rankings;
@@ -55,6 +75,11 @@ memhouse plugins list
55
75
  memhouse plugins remove claude
56
76
  ```
57
77
 
78
+ Each copy is bound to the install that put it there, and kept at that install's version: after
79
+ an upgrade, by `memhouse update` or by `npm install -g`, the first `memhouse` command (or the
80
+ shipper's restart) replaces it with the new version's skills. `memhouse doctor` names any copy
81
+ it could not refresh.
82
+
58
83
  The five skills:
59
84
 
60
85
  | skill | for |
@@ -74,11 +99,55 @@ missing skill never announces itself.
74
99
  For an agent that does not use Claude Code skills, `memhouse prompt` prints a snippet for
75
100
  its system prompt: where memory lives and how to query it.
76
101
 
102
+ ### Codex
103
+
104
+ The `/mem` skills are for Claude Code. For Codex:
105
+
106
+ ```bash
107
+ memhouse plugins install codex # under CODEX_HOME, or ~/.codex
108
+ memhouse plugins list # shows the codex copy and the command it runs
109
+ memhouse plugins remove codex
110
+ ```
111
+
112
+ It writes two things, and only when you run it:
113
+
114
+ - **A block at the top of `AGENTS.md`**, between `<!-- memhouse:begin … -->` and
115
+ `<!-- memhouse:end -->`. Codex puts this file in every prompt, so the block holds only what must
116
+ always hold: never read or print `~/.memhouse/env` or a `MEMHOUSE_*` value; `MEMHOUSE_URL` is the
117
+ house and `MEMHOUSE_PORT` the dashboard; questions about past sessions go to the `memhouse`
118
+ skill. The file is created if it is missing. Installing again replaces the block where it
119
+ stands. Removing takes out the block and the blank line after it, so the rest of the file is
120
+ what it was, byte for byte; a file that held only the block is deleted, or, when `AGENTS.md`
121
+ is a symlink, its target is emptied and the link kept. Leave the markers
122
+ alone: with one of them missing, install and remove refuse and change nothing.
123
+ - **A skill, `skills/memhouse/`**, with the read recipes: `memhouse q`, `--member` for a
124
+ housemate's sessions, `memhouse room-sql`. Codex lists it by name and description and reads
125
+ the rest when it uses it. The directory carries `.memhouse.json`; a `skills/memhouse` without
126
+ it is someone else's, and install refuses to replace it and remove leaves it.
127
+
128
+ For an instance with its own home or binary, both name it: `MEMHOUSE_HOME=<home> <binary> q …`.
129
+ After an upgrade, the first `memhouse` command refreshes both, as it does the `/mem` copies; a
130
+ block you took out by hand stays out.
131
+
77
132
  ## Writing your own queries
78
133
 
79
- The rooms are documented column by column in
134
+ On schema 3 (0.20), the tables are `mem.<you>_session_parts` (one row per part of a message:
135
+ text, thinking, a tool call or its result), `mem.<you>_session_usage` (one row per usage
136
+ record), `mem.<you>_calls` and `mem.<you>_sessions` (one row per session, with its totals),
137
+ with `_session_links` and `_session_lifecycle` beside them. Their columns are described in
138
+ [`memhouse/house/schema-profile.sql.tpl`](../../memhouse/house/schema-profile.sql.tpl). Three rules:
139
+
140
+ 1. **Read with `FINAL`, and keep `is_deleted = 0 AND removed = 0`.** Removed content is
141
+ kept as removed versions (`memhouse q removed` lists them); it is not current.
142
+ 2. **A session is its store:** `(host, harness, harness_path, session_id, incarnation)`.
143
+ A bare `session_id` can repeat across editors and machines.
144
+ 3. **Count a session's messages as its text parts (`kind = 'text'`), as `sessions.messages`
145
+ does, and take its totals from `sessions`.** A forked session's copied prefix is billed once, in `sessions`;
146
+ summing the parts or the usage rows of a fork and its parent counts that prefix twice.
147
+
148
+ On the 0.18 rooms, the rooms are documented column by column in
80
149
  [`memhouse/house/HOUSE.md`](../../memhouse/house/HOUSE.md). Two rules matter for every
81
- query:
150
+ query there:
82
151
 
83
152
  1. **Join on `(session_id, user_id)`**, never on `session_id` alone. Two members' editors
84
153
  can produce the same local session id.
@@ -1,29 +1,32 @@
1
1
  # Share and revoke
2
2
 
3
- A share lets another member of the same server read your rooms. It is a ClickHouse
3
+ A share lets another member of the same server read your sessions. It is a ClickHouse
4
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.
5
+ installed as yourself. Each command acts on your own tables only.
6
6
 
7
7
  ```bash
8
- memhouse share bob # full: everything in mem.<you>_*
9
- memhouse share bob --only project=billing # scoped: three rooms, filtered
8
+ memhouse share bob # full: every session
9
+ memhouse share bob --only project=billing # scoped: the sessions that match
10
10
  memhouse share --list # what memhouse has recorded
11
11
  memhouse share bob --revoke # take it back
12
12
  ```
13
13
 
14
- ## Full or scoped
14
+ This page describes a 0.20 house (schema 3). For a house still on the 0.18 rooms, see
15
+ [the last section](#a-house-on-the-018-rooms).
16
+
17
+ ## What a share reaches
18
+
19
+ Full or scoped, a share is `SELECT` on exactly your six conversation tables:
20
+ `<you>_session_parts`, `<you>_session_usage`, `<you>_session_links`, `<you>_session_lifecycle`,
21
+ `<you>_calls` and `<you>_sessions`. Never a wildcard, never the database.
15
22
 
16
23
  | | full (`share bob`) | scoped (`share bob --only …`) |
17
24
  |---|---|---|
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.
25
+ | grant | `SELECT` on the six tables | `SELECT` on the six tables |
26
+ | rows | all | those matching the scope, through a row policy on each table |
27
+ | capture archive (`<db>_archive`) | not granted | not granted |
28
+ | `<you>_meta`, `<you>_events` | not granted | not granted |
29
+ | tables added later | not granted | not granted |
27
30
 
28
31
  ## Scopes
29
32
 
@@ -31,31 +34,20 @@ you run it.
31
34
 
32
35
  | key | filters on |
33
36
  |---|---|
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
37
+ | `project=` | the project name. Only message parts, calls and sessions carry one: a project scope passes no usage, link or lifecycle rows, and the `sessions` row carries the totals |
38
+ | `folder=` | the working directory, on the same three tables as `project=` |
39
+ | `session=` | one session: a ref with its store (`<harness>:<id>~…`, as `memhouse q recent` prints it), or a bare id, which matches that id in every store |
40
+ | `host=` | one machine, by its id (`memhouse doctor` lists it under "host identity") |
41
+ | `harness=` | one editor, by the stored value (below); `source=` means the same |
42
+ | `since=` / `until=` | a date range: the row's own time for message parts, usage, calls and lifecycle records, the start for `sessions`. A row with no time, and every link, never matches |
43
+
44
+ `harness=` compares against the stored value, which is not always the adapter name you
42
45
  use in `MEMHOUSE_EDITORS`. Claude Code rows, for example, say `claude-code`, not `claude`.
43
- The values:
46
+ To see what your tables hold:
44
47
 
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`.
48
+ ```sql
49
+ SELECT harness, count() FROM mem.<you>_sessions FINAL GROUP BY harness
50
+ ```
59
51
 
60
52
  An empty scope (`--only ""`, or `--only "$SCOPE"` with the variable unset) is refused.
61
53
  It never falls back to a full share.
@@ -68,35 +60,82 @@ your full-share grantees. Before it creates the first policy, `share` measures h
68
60
  server behaves, and refuses if a policy would blind you. The fix is server configuration
69
61
  (`users_without_row_policies_can_read_rows`), and it is the administrator's to make.
70
62
 
63
+ ## The order, and what a failure leaves
64
+
65
+ - **Scoped:** the row policies are written first, then the grants. If a policy cannot be
66
+ written, nothing is granted.
67
+ - **Full:** the grants first, then any leftover policies are dropped. Until they are
68
+ dropped they only narrow.
69
+ - **A reader who still holds your 0.18 share** (`<you>_*`, from before the conversion): a
70
+ new share refuses, because it would sit beside that grant and narrow nothing. So does a
71
+ share when what the reader holds cannot be read. Revoke first, or, for a full share memhouse
72
+ recorded, the admin moves it with `migrate --v3-shares`. In a house where member names
73
+ collide, a revoke may leave the grant and `--v3-shares` refuses: see
74
+ [Troubleshooting](troubleshooting.md#two-member-names-that-collide).
75
+ - If a grant fails part-way, memhouse withdraws what it granted. If it cannot confirm that,
76
+ the filters stay, and it tells you to retry or revoke.
77
+
71
78
  ## Changing a share
72
79
 
73
80
  - **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.
81
+ - **Full to scoped:** `memhouse share bob --only …`. The filters are written first, so
82
+ bob's view narrows before anything else changes.
76
83
  - **One scope to another:** run `--only` again with the new scope. It replaces the
77
84
  policies.
78
85
 
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
86
  ## Revoking
84
87
 
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
+ `--revoke` withdraws the grants on the six tables and on your 0.18 rooms, then drops the
89
+ row policies. If the server refuses a step, the policies are kept, so a failed revoke never
90
+ leaves an unfiltered share behind.
91
+
92
+ A 0.18 wildcard grant the reader still holds (`<you>_*`) is taken back too, unless it also
93
+ matches another member's tables (see [Troubleshooting](troubleshooting.md#two-member-names-that-collide))
94
+ or a table memhouse did not make for you. A REVOKE on the wildcard would strip the reader's
95
+ grants on those too, so the revoke leaves it for the admin and says so. It does the same when
96
+ the reader's grants cannot be read, or the house's tables or users cannot be listed. The admin then
97
+ takes it back by hand: the share is recorded as revoked, and `migrate --v3-shares` refuses a
98
+ revoked share that still holds a grant.
88
99
 
89
100
  A revoke cannot recall what was already read.
90
101
 
91
102
  ## What `--list` shows
92
103
 
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>`.
104
+ `share --list` reads memhouse's own record, the `share:` keys in `<you>_meta`, a share's scope
105
+ included. A grant someone made by hand in SQL does not appear there. A member cannot read
106
+ `system.grants` or `system.row_policies`; an administrator can, with `SHOW GRANTS FOR <user>`
107
+ and the queries in [Operate](operate.md#who-is-here).
108
+
109
+ It also lists the other direction: each member whose sessions your credential can read, with
110
+ the command that reads them. A scoped share shows only the rows its owner chose, and its filter
111
+ is the owner's to see.
96
112
 
97
113
  ## Reading what was shared with you
98
114
 
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
115
+ Shared tables sit in the same database as yours, under the other member's name, for
116
+ example `mem.alice_session_parts`. Query them with `/mem:sql` or any ClickHouse client. Read
117
+ `<m>_sessions` with `FINAL`, as you would your own. A table you were not granted is
102
118
  refused, and a scope you are under returns only its rows.
119
+
120
+ ## When a house is converted from 0.18
121
+
122
+ A 0.18 share is a grant on the old rooms, which the conversion renames into the archive.
123
+ The administrator moves the shares with `memhouse migrate --v3-shares`, part of
124
+ [Upgrade to 0.20](upgrade-to-0.20.md). It re-creates only the shares memhouse recorded,
125
+ narrows a 0.18 full share to the six tables, and refuses when the record and the live
126
+ grants disagree. Run it as part of the conversion: until it runs, readers hold whatever
127
+ their 0.18 grants still reach.
128
+
129
+ ## A house on the 0.18 rooms
130
+
131
+ On a house not yet converted, `share` works as in 0.18:
132
+
133
+ - a full share is `SELECT ON mem.<you>_*`, which also reaches the stats tables, `meta`,
134
+ `events` and any room added later;
135
+ - a scoped share is `SELECT` on `<you>_sessions`, `<you>_messages` and
136
+ `<you>_tool_calls`, filtered by row policies;
137
+ - `source=` filters on the `source` column.
138
+
139
+ **Scoped shares made before 0.18.10** granted the whole pattern while filtering only three
140
+ rooms. Re-run `memhouse share <user> --only <scope>` once for each, and it is narrowed to
141
+ the three rooms.
@@ -1,6 +1,46 @@
1
1
  # How memory is stored
2
2
 
3
- ## A house, rooms, and members
3
+ A house created by memhouse 0.20 is on **schema 3**, described first. A house from 0.18 is on
4
+ the **rooms**, described after it, until its admin converts it
5
+ ([Upgrade to 0.20](upgrade-to-0.20.md)).
6
+
7
+ ## Schema 3 (memhouse 0.20)
8
+
9
+ A member's sessions live in six conversation tables, the harness-sessions-sql profile, plus two capture
10
+ tables only the shipper writes:
11
+
12
+ | table | holds |
13
+ |---|---|
14
+ | `<db>.<member>_session_parts` | one row per message part: text, thinking, a tool call and its full result, an attachment's hash |
15
+ | `<db>.<member>_session_usage` | one row per usage record, at the editor's granularity (a record, a message or a turn): tokens, model, and how the editor counted them |
16
+ | `<db>.<member>_session_links` | one row per link between sessions, such as a fork or a sub-agent, as a session states it or as the engine inferred or verified it |
17
+ | `<db>.<member>_session_lifecycle` | one row per lifecycle record a session states: an operation on a message, a range of messages or the whole session |
18
+ | `<db>.<member>_calls` | one row per tool call: name, arguments, whether it failed, the result's length (the result itself is a `session_parts` row) |
19
+ | `<db>.<member>_sessions` | one row per session: title, folder, counts, tokens per model and hour, links to forks and sub-agents |
20
+ | `<db>_archive.<member>_session_units` | the source as read: every unit (a line, a row, a file) of every transcript or normalised log, and a manifest for each read that changed something |
21
+ | `<db>_archive.<member>_session_sources` | the shipper's cursor per source |
22
+
23
+ - **A session is its store.** It is keyed by `host` (a fingerprint of the machine), `harness`,
24
+ `harness_path` (the harness's data folder; for OpenCode, its DB file), its `session_id`, and an `incarnation` where a
25
+ harness reuses ids. The same id on another machine, or in another data folder, is another
26
+ session.
27
+ - **Nothing is overwritten in place.** A row is replaced by a newer version (`rev`). When a
28
+ message leaves its source (a revert, a rewind, a deleted message), it is written once more
29
+ as a **removed version** (`removed = 1`). Every read hides it, `memhouse q removed` lists it,
30
+ and no total counts it.
31
+ - **The archive outlives the editors.** Its units keep every line, row or file as it was read,
32
+ and a manifest for each read that changed something says what that read delivered. So a session can be rebuilt from the
33
+ archive alone, including what later went away. A transcript the editor deleted stays in the
34
+ house.
35
+ - **A fork is billed once.** Where one session is a verified copy of another's beginning, the
36
+ copy's share of the tokens is subtracted on the fork. The copies stay messages of the fork.
37
+
38
+ `memhouse doctor` checks all of it: tables and sorting keys, identity stamping, duplicate
39
+ timestamps, removed content, forks billed once, and the machine's host identity.
40
+
41
+ ## The 0.18 rooms
42
+
43
+ ### A house, rooms, and members
4
44
 
5
45
  - **A house is a ClickHouse database,** normally `mem`.
6
46
  - **A member is a ClickHouse user** who ships into it.
@@ -29,7 +69,7 @@ database once leaked into each other. A member can read their isolation back wit
29
69
  Reading across members is a `UNION ALL` over the rooms you hold: your own, and any shared
30
70
  with you. Naming a room you were not granted is an error, never a silent omission.
31
71
 
32
- ## Every row says where it came from
72
+ ### Every row says where it came from
33
73
 
34
74
  - **`user_id`** is stamped by the server: `MATERIALIZED currentUser()`. Async inserts are
35
75
  pinned off for members, so the stamp cannot be skipped. `WHERE user_id = 'alice'` is one
@@ -48,7 +88,7 @@ with you. Naming a room you were not granted is an error, never a silent omissio
48
88
  - **`origin`** says who wrote the row. The shipper writes `ship`; an import writes its own
49
89
  value.
50
90
 
51
- ## The house never destroys what it cannot rebuild
91
+ ### The house never destroys what it cannot rebuild
52
92
 
53
93
  **The shipper is insert-only.** It runs no `DELETE`, no `TRUNCATE`, and no mutation of
54
94
  any kind, so no privilege it holds can lose you a row.
@@ -83,7 +123,7 @@ a second row would make every rollup count its messages twice.
83
123
  The shipper checks the rooms' sorting keys before it writes, and refuses if they are
84
124
  wrong. See [Update](update.md#when-the-house-itself-must-change).
85
125
 
86
- ## What is not in the house
126
+ ### What is not in the house
87
127
 
88
128
  - Nothing is embedded, summarised or rewritten. Text is stored as the editor wrote it,
89
129
  truncated at 50,000 characters per message and 20,000 per tool argument.