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
package/README.md
CHANGED
|
@@ -1,452 +1,83 @@
|
|
|
1
1
|
# memhouse
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
something durable was keeping it.
|
|
3
|
+
**Your coding agents forget. Their transcripts get deleted too.** Claude Code removes
|
|
4
|
+
session files after 30 days (`cleanupPeriodDays`); on the machine this was built on, the
|
|
5
|
+
oldest surviving local transcript was nine days old. Every problem solved before that
|
|
6
|
+
exists nowhere, unless something durable kept it.
|
|
8
7
|
|
|
9
|
-
memhouse
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
past session, see what it cost, resume the conversation that solved this before,
|
|
17
|
-
and let an agent query its own history.
|
|
8
|
+
memhouse keeps it. It reads the session transcripts your editors already write to disk
|
|
9
|
+
(16 adapters covering 18 apps: Claude Code, Codex, Cursor, Zed, Copilot, Gemini CLI and
|
|
10
|
+
the rest), parses them locally, and ships typed rows into a ClickHouse **you choose**. That
|
|
11
|
+
can be a container on this laptop, a box you run, your company's cluster, or ClickHouse
|
|
12
|
+
Cloud. memhouse runs no service of its own and proxies nothing. Then you, and your agents,
|
|
13
|
+
can search every past session, see what it cost, and reopen the one that solved this
|
|
14
|
+
before.
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
install. A team pointed at one house is a team memory with real attribution — not
|
|
23
|
-
a label somebody's client claimed.
|
|
16
|
+
Every row says where it came from, in a way no client can fake. `user_id` is stamped by
|
|
17
|
+
the server (`MATERIALIZED currentUser()`), and `host` is a fingerprint minted once per
|
|
18
|
+
install. A team pointed at one house gets a shared memory with real attribution.
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
npm install -g memhouse
|
|
27
|
-
memhouse onboard
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
That is the whole install — no flags, nothing to compile. `onboard` finds your
|
|
31
|
-
editors, sets up a house, ships, and starts the dashboard.
|
|
32
|
-
|
|
33
|
-
**On most Linux distros that first line needs `sudo`.** A distro-packaged Node puts
|
|
34
|
-
its global prefix under `/usr`, which you do not own, so npm stops with `EACCES`.
|
|
35
|
-
That is the single most likely place a first install stalls — measured on a clean
|
|
36
|
-
Ubuntu machine:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
ls -ld "$(npm prefix -g)" # root-owned? use sudo. Owned by you? do NOT — see EACCES below
|
|
40
|
-
sudo npm install -g memhouse
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
If the prefix is yours (Homebrew, fnm, nvm, volta), `sudo` is the wrong fix and
|
|
44
|
-
makes it worse — [EACCES on install](#eacces-on-install) tells them apart.
|
|
45
|
-
|
|
46
|
-
## No ClickHouse yet?
|
|
47
|
-
|
|
48
|
-
`onboard` offers to run one for you if docker or podman is present:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
memhouse deploy --local # a loopback-bound ClickHouse, then install and ship
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The house it stands up is **yours by name**: its superuser defaults to your OS
|
|
55
|
-
username (`polat`, not `memhouse_root` — override with `--user`), and the house
|
|
56
|
-
database defaults to the same name, so your memory lives at `polat.messages` and
|
|
57
|
-
`WHERE user_id = 'polat'` reads like it should. The name
|
|
58
|
-
and password are fixed when the data volume is first initialised, like any ClickHouse
|
|
59
|
-
container, and reused on every redeploy after that.
|
|
60
|
-
|
|
61
|
-
Otherwise point at one you already run, or hand the SQL to whoever administers it:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
memhouse install --url https://… --user … --password … # one you have
|
|
65
|
-
memhouse install --admin-user … --admin-password … # let memhouse build it
|
|
66
|
-
memhouse install --print-sql # print the SQL, run it yourself
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
memhouse does not embed a database. If you have neither a ClickHouse nor a
|
|
70
|
-
container engine, install one of the two.
|
|
71
|
-
|
|
72
|
-
## Commands
|
|
73
|
-
|
|
74
|
-
```text
|
|
75
|
-
memhouse onboard | install | setup | discover | doctor | uninstall | reset
|
|
76
|
-
memhouse ship [--full|--loop N] | search <terms> | stats | status
|
|
77
|
-
memhouse resume <session-id> print the command that reopens a session
|
|
78
|
-
memhouse update [--check] [--channel TAG] upgrade along the channel this install follows,
|
|
79
|
-
restart daemons, check the schema
|
|
80
|
-
memhouse start | stop dashboard + shipper as daemons
|
|
81
|
-
memhouse service install | uninstall survive a reboot
|
|
82
|
-
memhouse deploy --local | --down stand up (or remove) a local house
|
|
83
|
-
memhouse invite <name> mint a member + house, hand them one env file
|
|
84
|
-
memhouse members [--db X] who is in a house, and what each can reach
|
|
85
|
-
memhouse whoami [--admin] which credential is in play, and what it may do
|
|
86
|
-
memhouse instance which memhouse this is: home, binary, house, rooms, scope, bindings
|
|
87
|
-
memhouse rooms what your rooms are actually called
|
|
88
|
-
memhouse share <user> [--only …|--revoke|--list]
|
|
89
|
-
let a housemate read yours, in whole or in part
|
|
90
|
-
memhouse sessions-query print the session rollup SQL
|
|
91
|
-
memhouse passwd rotate this member's password
|
|
92
|
-
memhouse migrate | migrate-rooms [--dry-run] [--yes]
|
|
93
|
-
run whatever this house still needs
|
|
94
|
-
memhouse relocate --to <url> copy this house to a new ClickHouse, then repoint
|
|
95
|
-
memhouse nightly [--out DIR] build an installable tarball from this checkout
|
|
96
|
-
memhouse plugins install claude 5 skills — house, recall, sql, access,
|
|
97
|
-
admin — into every Claude Code config
|
|
98
|
-
dir found
|
|
99
|
-
memhouse prompt memory snippet for an agent's system prompt
|
|
100
|
-
memhouse prompt --install an install prompt, rendered for this machine
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Any command's own usage: `memhouse <command> --help` (or `-h`) — always prints usage,
|
|
104
|
-
never runs the command.
|
|
105
|
-
|
|
106
|
-
Every command works both ways: interactive for you, `--yes` / flags / `--json` for
|
|
107
|
-
an agent — so an agent can install its own memory unattended.
|
|
108
|
-
|
|
109
|
-
`onboard` offers the skills at the end, and `plugins` works across **every Claude Code
|
|
110
|
-
config directory on the machine** — `~/.claude`, whatever `CLAUDE_CONFIG_DIR` points
|
|
111
|
-
at, and each Kommander-style playbook under `~/.claude-playbooks/`. All are selected by
|
|
112
|
-
default; answer with numbers to narrow it, or pass `--target DIR` for exactly one.
|
|
113
|
-
Installing into just the default config is how `/mem:recall` ends up missing from
|
|
114
|
-
the instance you actually work in, silently, since a missing skill never announces
|
|
115
|
-
itself.
|
|
116
|
-
|
|
117
|
-
Config resolves: flags → `MEMHOUSE_*` env → `$MEMHOUSE_HOME/env` (default
|
|
118
|
-
`~/.memhouse/env`). There is no house-shaped default: with nothing configured, the
|
|
119
|
-
commands that read or write memory refuse and say so rather than guessing
|
|
120
|
-
`localhost:8123`, which on a lot of machines is a real house belonging to someone else.
|
|
121
|
-
|
|
122
|
-
## Which sessions ship
|
|
123
|
-
|
|
124
|
-
Everything on the machine, by default: every adapter, from wherever that editor keeps its
|
|
125
|
-
sessions. `memhouse discover` prints what each adapter is watching and the variable that
|
|
126
|
-
moves it:
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
Watched directories (override with the variable shown, in the env file):
|
|
130
|
-
claude ~/.claude, ~/.claude-playbooks/kommander, … [MEMHOUSE_CLAUDE_ROOTS]
|
|
131
|
-
codex ~/.codex [MEMHOUSE_CODEX_ROOTS]
|
|
132
|
-
gemini-cli ~/.gemini [MEMHOUSE_GEMINI_CLI_ROOTS]
|
|
133
|
-
cursor ~/Library/Application Support/Cursor/User, … [built-in]
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Two kinds of line in the env file narrow it, or `--editors` / `--claude-roots` at install:
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
MEMHOUSE_EDITORS='claude'
|
|
140
|
-
MEMHOUSE_CLAUDE_ROOTS='~/.claude-playbooks/kommander-chaos'
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
`MEMHOUSE_EDITORS` names the adapters to run. `MEMHOUSE_<EDITOR>_ROOTS` replaces one
|
|
144
|
-
adapter's location: the adapter's name upper-cased, dashes to underscores. Claude is the
|
|
145
|
-
one adapter with many roots (every Claude Code config directory), so its list may hold
|
|
146
|
-
several; every other adapter has one store, so one path. Adapters marked `[built-in]`
|
|
147
|
-
resolve a platform app-data directory and cannot be moved yet.
|
|
148
|
-
|
|
149
|
-
An adapter name or a directory that does not exist is refused and reported against that
|
|
150
|
-
adapter, never shipped from the default instead: a typo that quietly shipped nothing would
|
|
151
|
-
look like a working install with an empty house, and one that quietly shipped the wrong
|
|
152
|
-
store would be worse.
|
|
153
|
-
|
|
154
|
-
## Channels
|
|
155
|
-
|
|
156
|
-
`memhouse update` follows the npm dist-tag the install came from, not `latest` by
|
|
157
|
-
reflex. A house published under another tag — `npm publish --tag team`, so one team can
|
|
158
|
-
run a newer layout without every other house being dragged along — installs with
|
|
159
|
-
`npm install -g memhouse@team`, and `update` keeps it there. The channel is inferred from
|
|
160
|
-
the installed version; pin it with `MEMHOUSE_CHANNEL='team'` in the env file or
|
|
161
|
-
`memhouse update --channel team`, which writes the same line. A tarball or checkout
|
|
162
|
-
build sits on no tag and is never auto-updated; `update --check` says so and names the
|
|
163
|
-
way forward. An invite from a house on a channel carries it, so the invitee follows it
|
|
164
|
-
too.
|
|
165
|
-
|
|
166
|
-
## Instances
|
|
167
|
-
|
|
168
|
-
An instance is a home: an env file naming a house, the daemons shipping to it, and the
|
|
169
|
-
playbooks whose sessions it ships (`MEMHOUSE_CLAUDE_ROOTS`). A machine can run several,
|
|
170
|
-
each under its own `MEMHOUSE_HOME`, on different channels. `plugins install claude` from an
|
|
171
|
-
instance installs into exactly the playbooks that instance ships and **binds** them: it
|
|
172
|
-
writes `MEMHOUSE_HOME` and `MEMHOUSE_BIN` into each playbook's `settings.json` `env`, which
|
|
173
|
-
Claude Code applies to every session there. So the `/mem:*` skills in a bound playbook read
|
|
174
|
-
that instance's house and run that instance's binary, whatever the shell they were started
|
|
175
|
-
from had. `plugins list` shows the binding; `plugins remove` takes it out. Everything else
|
|
176
|
-
in `settings.json` is left as it was.
|
|
177
|
-
|
|
178
|
-
## Going back into a session
|
|
179
|
-
|
|
180
|
-
`search` finds the conversation; `resume` hands you back into it.
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
memhouse resume claude:6b1f… # prints: cd /path/to/project && claude --resume 6b1f…
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
It **prints** the command instead of running it. A resume run from the wrong directory
|
|
187
|
-
or with a stale id does not fail — it opens a *new* session, and the transcript you
|
|
188
|
-
wanted is still gone while the tool reports success. You paste it, so you see it first.
|
|
189
|
-
|
|
190
|
-
Verified for **claude, codex and opencode**. Cursor, Zed, VS Code, Kiro, Copilot for
|
|
191
|
-
JetBrains, Antigravity and Devin/Windsurf have no CLI that takes a session id, so
|
|
192
|
-
there is nothing to resume into and `resume` says exactly that, rather than opening a
|
|
193
|
-
folder and calling it the same thing. Everything else — goose, gemini-cli,
|
|
194
|
-
cursor-agent, copilot-cli — is refused as *unverified*, which is a different sentence
|
|
195
|
-
on purpose: nobody has read the flag out of its own `--help` yet, and guessing is how
|
|
196
|
-
you print a command that quietly does the wrong thing. See `memhouse/resume.js`.
|
|
197
|
-
|
|
198
|
-
## Which machine is which
|
|
199
|
-
|
|
200
|
-
All of *your* machines ship into *your* rooms, and the `host` column is what tells them
|
|
201
|
-
apart. So each install writes `~/.memhouse/host.json` — a random fingerprint, minted once
|
|
202
|
-
— and every row that machine ships carries `<hostname>-<8 hex>` built from it.
|
|
203
|
-
|
|
204
|
-
Random, not derived, because deriving it fails in both directions: two laptops that both
|
|
205
|
-
answer to the default `MacBook-Pro` on the same platform hash to the **same** id and merge
|
|
206
|
-
into one apparent machine, and renaming a machine **moves** a derived id so its own history
|
|
207
|
-
appears to stop and a stranger's to begin. A stored fingerprint cannot collide and does not
|
|
208
|
-
move — rename the machine and `status` says so while the id stays put.
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
memhouse status # ✓ host: macminim-4127a95b
|
|
212
|
-
memhouse doctor # names every host in your rooms, and whether this one is among them
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Then query one machine with `WHERE host = 'macminim-4127a95b'`.
|
|
216
|
-
|
|
217
|
-
## Uninstalling, in three tiers
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
memhouse uninstall # stop everything; KEEP the config and this host's identity
|
|
221
|
-
memhouse uninstall --credentials # also forget the house and its password
|
|
222
|
-
memhouse uninstall --full-removal # all of ~/.memhouse, identity included
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The default is deliberately the conservative one. Stopping the shipper is routine — before
|
|
226
|
-
an upgrade, while debugging, when a laptop should go quiet for a week — and it should not
|
|
227
|
-
also mean "forget which house I use and who this machine is". `--full-removal` asks first,
|
|
228
|
-
because a later reinstall mints a **new** identity and this machine then reads as two
|
|
229
|
-
machines in your history.
|
|
230
|
-
|
|
231
|
-
**No tier touches your memory.** Transcripts live in ClickHouse, and everything the shipper
|
|
232
|
-
wrote rebuilds from the local session stores with `ship --full`.
|
|
233
|
-
|
|
234
|
-
## Staying current
|
|
20
|
+
## Install
|
|
235
21
|
|
|
236
22
|
```bash
|
|
237
|
-
memhouse
|
|
238
|
-
memhouse
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
A bare `npm i -g memhouse@latest` does half the job. It does not restart the daemons,
|
|
242
|
-
which keep parsing with the code they booted with (a shipper here once ran 1d16h out of
|
|
243
|
-
a directory that had been *moved*); and it does not check whether your house predates a
|
|
244
|
-
room the new shipper needs. `update` does both, and from a checkout it rebuilds
|
|
245
|
-
`public/` — otherwise `git pull` serves you the previous release's dashboard forever.
|
|
246
|
-
|
|
247
|
-
The daemons also notice on their own: each one compares the installed version against
|
|
248
|
-
what it booted with and hands over — exiting under systemd/launchd so the supervisor
|
|
249
|
-
restarts it, re-execing itself when nothing is supervising. `update` just makes it
|
|
250
|
-
immediate.
|
|
251
|
-
|
|
252
|
-
## How your memory is stored
|
|
253
|
-
|
|
254
|
-
**A house is a database; its rooms are tables.** `sessions`, `messages` and `tool_calls`
|
|
255
|
-
hold your turns. Every row says where it came from: `user_id`, stamped by the server
|
|
256
|
-
(`MATERIALIZED currentUser()`, with async inserts pinned off so the stamp cannot be
|
|
257
|
-
skipped), and `host`, the machine's install fingerprint. `WHERE user_id = 'alice'` is one
|
|
258
|
-
person; `WHERE host = '…'` is one machine. Neither is forgeable from the client.
|
|
259
|
-
|
|
260
|
-
**Your rooms are named for you, and one grant covers them.** Every member of a house holds
|
|
261
|
-
`GRANT … ON mem.<name>_* TO <name>`: their own rooms, present and future, and nothing else
|
|
262
|
-
in the database. That is the whole access model, and it is the same on a laptop, a team's
|
|
263
|
-
server, or a kernel:
|
|
264
|
-
|
|
265
|
-
| | rooms | the member holds |
|
|
266
|
-
|---|---|---|
|
|
267
|
-
| alone on your laptop | `mem.polat_*` | one grant on `mem.polat_*` |
|
|
268
|
-
| a team on one server | `mem.polat_*`, `mem.alice_*`, … | each their own |
|
|
269
|
-
|
|
270
|
-
A colleague can read their isolation back with `SHOW GRANTS` in one line, which is the
|
|
271
|
-
property this rests on. There is no row policy to trust and no database-wide grant to
|
|
272
|
-
worry about: `ALL ON mem.*` would cover rooms created later, which is how two people in one
|
|
273
|
-
database once became a leak, so memhouse never issues it — not to a member, not to you.
|
|
274
|
-
|
|
275
|
-
Standalone is a team of one. `memhouse deploy --local` creates the house with an admin
|
|
276
|
-
credential (`memhouse_root`) and a member named after your OS user, keeps both in the env
|
|
277
|
-
file, and ships as the member. Inviting a colleague later is `memhouse invite <name>`; the
|
|
278
|
-
admin credential is already there.
|
|
279
|
-
|
|
280
|
-
Reading across everyone is a `UNION ALL` over the rooms you hold:
|
|
281
|
-
|
|
282
|
-
```sql
|
|
283
|
-
SELECT user_id, count() FROM mem.polat_messages GROUP BY user_id
|
|
284
|
-
UNION ALL
|
|
285
|
-
SELECT user_id, count() FROM mem.alice_messages GROUP BY user_id
|
|
23
|
+
npm install -g memhouse # Node 24+; on a distro-packaged Node this needs sudo
|
|
24
|
+
memhouse onboard # finds your editors, sets up a house, ships, starts the dashboard
|
|
286
25
|
```
|
|
287
26
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
### The house never destroys what it cannot rebuild
|
|
293
|
-
|
|
294
|
-
**The shipper is insert-only.** It runs no `DELETE`, no `TRUNCATE`, no mutation of any
|
|
295
|
-
kind, so no privilege it holds can lose you a row.
|
|
296
|
-
|
|
297
|
-
That is not free, because ReplacingMergeTree is a dedupe engine, not a diff engine: a
|
|
298
|
-
re-parse yielding *fewer* messages leaves the old higher-`seq` rows with nothing written
|
|
299
|
-
over them. The shipper used to delete the session's rows first to clear that tail — and
|
|
300
|
-
a shorter re-parse has two indistinguishable causes. Either an adapter bug was fixed and
|
|
301
|
-
the extra rows are junk, or Claude Code **compacted** the transcript, or the retention
|
|
302
|
-
window (`cleanupPeriodDays`, 30 days by default) took it. In the last two cases the house
|
|
303
|
-
holds the only surviving copy, and the delete destroyed it.
|
|
304
|
-
|
|
305
|
-
So rows carry an `epoch`: which parse of the session they belong to.
|
|
306
|
-
|
|
307
|
-
- **Nothing changed** (the common case, including ordinary growth): same epoch, rows
|
|
308
|
-
dedupe exactly as before, no extra storage.
|
|
309
|
-
- **Shorter, or rewritten at a position the house already holds**: the new parse goes to
|
|
310
|
-
`epoch + 1`. The old one stays complete and readable. You pay storage only when the old
|
|
311
|
-
rows are irreplaceable.
|
|
312
|
-
|
|
313
|
-
Reads show one parse per session — the newest — so counts, tokens and cost are unchanged.
|
|
314
|
-
`/mem:sql` and any hand-written query should filter the same way; the skill carries
|
|
315
|
-
the clause.
|
|
316
|
-
|
|
317
|
-
**Imported history is protected the same way.** `origin` says who wrote a row, and it is
|
|
318
|
-
in the sorting key of `messages` and `tool_calls`, so ReplacingMergeTree cannot collapse
|
|
319
|
-
an imported row against a shipped one. Anything you imported from an older house, another
|
|
320
|
-
product, or a machine that no longer exists survives every re-ship. `sessions` is the
|
|
321
|
-
deliberate exception — one row per session, no `origin` and no `epoch` in its key, because
|
|
322
|
-
a session's metadata has a single current version and a second row makes every rollup
|
|
323
|
-
count its messages twice.
|
|
324
|
-
|
|
325
|
-
The shipper verifies the sorting keys before it writes and refuses if they are wrong, so
|
|
326
|
-
an old house cannot be corrupted by a new shipper. `memhouse migrate-rooms` rebuilds it:
|
|
327
|
-
copy, atomic swap, and the old room kept as `<room>_pre_epoch` for you to drop. The house
|
|
328
|
-
records the move in `events`, and `memhouse doctor` reads it back.
|
|
329
|
-
|
|
330
|
-
## Upgrading
|
|
331
|
-
|
|
332
|
-
`memhouse update` upgrades the code, restarts the daemons, and — from 0.10.0 onward —
|
|
333
|
-
detects a schema migration the house needs and prompts to run it.
|
|
27
|
+
No ClickHouse yet? `memhouse deploy --local` runs one in docker or podman, bound to
|
|
28
|
+
loopback, and installs against it. Already have one, or an invite from someone who does?
|
|
29
|
+
See [install and onboard](docs/guides/install.md).
|
|
334
30
|
|
|
335
|
-
|
|
336
|
-
runs the *old* version's code (it replaces itself mid-run), so it cannot prompt. After
|
|
337
|
-
the upgrade the first ship refuses (nothing is lost) and tells you the one command to
|
|
338
|
-
run by hand:
|
|
31
|
+
## What you get
|
|
339
32
|
|
|
340
33
|
```bash
|
|
341
|
-
memhouse
|
|
342
|
-
memhouse
|
|
343
|
-
memhouse
|
|
34
|
+
memhouse status # is it shipping, and how fresh
|
|
35
|
+
memhouse search "auth error" # full-text across every session, every machine
|
|
36
|
+
memhouse resume claude-code:6b1f… # prints the command that reopens that session
|
|
37
|
+
memhouse plugins install claude # /mem:recall and four more skills for your agents
|
|
344
38
|
```
|
|
345
39
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
destructively until you can.
|
|
40
|
+
The dashboard is at `http://localhost:4640`, showing sessions, cost, models, tools and
|
|
41
|
+
machines. From any Claude Code session, `/mem:recall how did I fix the ClickHouse auth
|
|
42
|
+
error` answers from your own history.
|
|
350
43
|
|
|
351
|
-
##
|
|
44
|
+
## Documentation
|
|
352
45
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
(
|
|
360
|
-
`node:sqlite` now — part of Node — so that failure class is gone along with the flag.
|
|
361
|
-
|
|
362
|
-
What can still go wrong is one store at a time: a `state.vscdb` locked by a running
|
|
363
|
-
editor, a corrupt file, a schema a new editor release changed. None of it is silent —
|
|
364
|
-
`memhouse discover` and `memhouse doctor` both name the skipped adapter and say why,
|
|
365
|
-
and the other sixteen keep working.
|
|
366
|
-
|
|
367
|
-
```bash
|
|
368
|
-
npx -y memhouse discover # no flags here either
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### `EACCES` on install
|
|
372
|
-
|
|
373
|
-
`sudo` is right for only one of the two causes, and what tells them apart is **who
|
|
374
|
-
owns the prefix root** — not the path, and not the file npm named.
|
|
375
|
-
|
|
376
|
-
```bash
|
|
377
|
-
ls -ld "$(npm prefix -g)"
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
- **Owned by `root`** — a system-managed Node (distro packages under `/usr`).
|
|
381
|
-
Re-run with `sudo`.
|
|
382
|
-
- **Owned by you** — the prefix is yours (Homebrew, fnm, nvm, volta), so the
|
|
383
|
-
root-owned file npm tripped on is a stray from an earlier `sudo npm`. Another
|
|
384
|
-
`sudo` adds more. Repair just that path:
|
|
385
|
-
`sudo chown -R "$(id -u):$(id -g)" <the path npm named>`. Same for `~/.npm`.
|
|
386
|
-
|
|
387
|
-
The path proves nothing on its own: Homebrew's prefix is `/usr/local` on Intel and
|
|
388
|
-
`/opt/homebrew` on Apple Silicon, and is yours in both cases.
|
|
46
|
+
| | |
|
|
47
|
+
|---|---|
|
|
48
|
+
| [Tutorials](docs/README.md#tutorials) | your first house, joining a team's house, sharing memory |
|
|
49
|
+
| [Guides](docs/README.md#guides) | install, update, invite, share, admin credentials, daemons, search, troubleshooting |
|
|
50
|
+
| [Examples](examples/README.md) | runnable setups, from one machine up to a self-hosted team house |
|
|
51
|
+
| [Release notes](docs/releases/) | what changed in each version, and how to upgrade |
|
|
52
|
+
| [`AGENTS.md`](AGENTS.md) | installing memhouse as an agent, and working on this repository |
|
|
389
53
|
|
|
390
|
-
|
|
54
|
+
Any command's own usage: `memhouse <command> --help`. Every command works both ways:
|
|
55
|
+
interactive for you, and `--yes` / flags / `--json` for an agent.
|
|
391
56
|
|
|
392
|
-
|
|
393
|
-
`memhouse service install` writes a real user service instead (systemd `--user`,
|
|
394
|
-
or a launchd LaunchAgent) and takes over. On Linux a `--user` unit stops at logout
|
|
395
|
-
unless lingering is on; install detects that and prints the `loginctl` command.
|
|
57
|
+
## What's supported
|
|
396
58
|
|
|
397
|
-
|
|
398
|
-
|
|
59
|
+
| Surface | Status |
|
|
60
|
+
|---|---|
|
|
61
|
+
| macOS | primary; developed and driven here daily |
|
|
62
|
+
| Linux | installs, ships, `service install` (systemd `--user`), rootless podman `deploy --local`; exercised on Debian and Ubuntu |
|
|
63
|
+
| Windows | **untested**; the adapters declare Windows paths, but no one has run an install |
|
|
64
|
+
| ClickHouse | 25.11 and 26.x, both in CI |
|
|
65
|
+
| Node | **24 or newer**. SQLite comes from `node:sqlite`; no native dependency, no build step |
|
|
399
66
|
|
|
400
67
|
## Working from a checkout
|
|
401
68
|
|
|
402
69
|
```bash
|
|
403
|
-
npm install # two runtime
|
|
70
|
+
npm install # two runtime dependencies, no build step
|
|
404
71
|
node bin/memhouse.js …
|
|
405
72
|
npm test # syntax gate + unit checks
|
|
406
73
|
```
|
|
407
74
|
|
|
408
|
-
`
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
and are not part of `npm test`.
|
|
412
|
-
|
|
413
|
-
## Layout
|
|
414
|
-
|
|
415
|
-
| Path | What |
|
|
416
|
-
|---|---|
|
|
417
|
-
| `memhouse/` | the product — `DESIGN.md`, `per-member/`, `shipper/`, `server/`, `delivery/` |
|
|
418
|
-
| `editors/` | the 17 editor adapters (inherited from agentlytics; the crown jewels) |
|
|
419
|
-
| `pricing.js` + `pricing.json` | the cost engine |
|
|
420
|
-
| `ui/` | the dashboard SPA (built to `public/`, served by the memhouse server) |
|
|
421
|
-
| `agency/` | the earlier agentlytics-agency wrap — prior art, not how memhouse works |
|
|
422
|
-
| `TERMINOLOGY.md` | the constellation terminology canon |
|
|
423
|
-
|
|
424
|
-
Deeper reading: `memhouse/DESIGN.md` (the bets, and why the room layout has moved three
|
|
425
|
-
times), `memhouse/house/HOUSE.md` (the layout and the schema), `SECURITY.md` (what
|
|
426
|
-
holds, and what does not), `memhouse/delivery/kernel-install.md`, `memhouse/COMPETITION.md`.
|
|
427
|
-
|
|
428
|
-
Deferred designs (captured, not yet built): `docs/design/host-repoint-reconciliation.md`
|
|
429
|
-
— what should happen when the shipper is repointed at a new, empty host (house identity,
|
|
430
|
-
local binding state, and a refuse-then-`init-here` gate so transcripts never land on the
|
|
431
|
-
wrong server).
|
|
432
|
-
|
|
433
|
-
In constellation terms (`TERMINOLOGY.md`) memhouse is an **agency**: a **house**
|
|
434
|
-
— the `mem` database — plus a **resident** working in it, the shipper. The house
|
|
435
|
-
alone would hold; the shipper is what makes it act. It competes with
|
|
436
|
-
memory-house; if it wins, it becomes memory-house v4.
|
|
437
|
-
|
|
438
|
-
## What's supported
|
|
439
|
-
|
|
440
|
-
| Surface | Status |
|
|
441
|
-
|---|---|
|
|
442
|
-
| macOS | primary — developed and driven here daily |
|
|
443
|
-
| Linux | real — installs, ships, `service install` (systemd `--user`), rootless podman `deploy --local`; exercised on Debian and Ubuntu machines |
|
|
444
|
-
| Windows | **untested** — the adapters declare Windows paths but no one has run a single install; treat it as unknown until this row changes |
|
|
445
|
-
| ClickHouse | 25.11 and 26.x, both in CI; 26.2+ needs no flags, 25.x needs the text-index flag the shipper already passes |
|
|
446
|
-
| Node | **≥ 24** — SQLite comes from `node:sqlite`, which is stable in 24. It works flagless on 22.13+ but prints `ExperimentalWarning` on every command (measured on v22.23.2), and Node 20 is past EOL. There is no native dependency and no build step at any version. |
|
|
75
|
+
See [`AGENTS.md`](AGENTS.md) before your first change. Deeper reading:
|
|
76
|
+
`memhouse/DESIGN.md` (the design and why), `memhouse/house/HOUSE.md` (layout and schema),
|
|
77
|
+
`SECURITY.md` (what holds, and what does not).
|
|
447
78
|
|
|
448
|
-
## Heritage
|
|
79
|
+
## Heritage and license
|
|
449
80
|
|
|
450
|
-
Built on [agentlytics](https://github.com/f/agentlytics) by Fatih Kadir Akın (MIT)
|
|
451
|
-
|
|
452
|
-
|
|
81
|
+
Built on [agentlytics](https://github.com/f/agentlytics) by Fatih Kadir Akın (MIT). The
|
|
82
|
+
adapters, dashboard and cost engine come from there, and this repository's history carries
|
|
83
|
+
the full lineage. MIT.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Security model — stated plainly
|
|
2
|
+
|
|
3
|
+
memhouse ships your coding-agent transcripts to a ClickHouse **you choose** — a container
|
|
4
|
+
on your laptop, a box you run, your company's cluster, or ClickHouse Cloud. memhouse runs
|
|
5
|
+
no service of its own: no memhouse cloud to sign up for, no telemetry, no phone-home. The
|
|
6
|
+
credential is yours and the data is in your account, wherever you decided that is. That
|
|
7
|
+
makes the security model short, and worth stating without varnish.
|
|
8
|
+
|
|
9
|
+
## What holds
|
|
10
|
+
|
|
11
|
+
- **A member reaches their own rooms and nothing else.** One grant, on `mem.<name>_*`:
|
|
12
|
+
their rooms, present and future. Ungranted rooms are not merely unreadable but absent
|
|
13
|
+
from `SHOW TABLES`, and a member cannot create, drop or re-grant outside their pattern
|
|
14
|
+
(measured on 25.11 and 26.7). Reserved databases (`system`, `information_schema`) are
|
|
15
|
+
refused as house names everywhere.
|
|
16
|
+
- **Nobody is granted the database.** `ALL ON db.*` is dynamic — it covers rooms created
|
|
17
|
+
later — and a member holding it beside others could read *and* re-grant every
|
|
18
|
+
housemate's transcripts, needing no admin and notifying nobody. memhouse never issues
|
|
19
|
+
it: not to a member, not to the operator's own member account. The operator's reach is
|
|
20
|
+
the admin credential, kept separately.
|
|
21
|
+
- **A member can only share what is theirs.** Grant option is scoped to their own rooms,
|
|
22
|
+
so `memhouse share` needs no operator and cannot reach a housemate's rows. A scoped
|
|
23
|
+
share (`--only`) builds its row filters BEFORE granting anything, so a scoping failure
|
|
24
|
+
grants nothing rather than leaving the grantee with everything.
|
|
25
|
+
- **Attribution cannot be faked by a client.** `user_id` is `MATERIALIZED
|
|
26
|
+
currentUser()` — computed by the server during the insert — and `async_insert = 0
|
|
27
|
+
CONST` is pinned on every member so the stamp cannot be skipped (an async flush
|
|
28
|
+
stores it empty; measured on 25.11).
|
|
29
|
+
- **The dashboard binds loopback** (`127.0.0.1`) by default, and `deploy --local`
|
|
30
|
+
binds the ClickHouse container to loopback too.
|
|
31
|
+
- **Credentials are never printed.** The skills are under standing instruction not to
|
|
32
|
+
echo the env file, and the API's responses and error bodies were checked for
|
|
33
|
+
credential leakage under the acceptance suite.
|
|
34
|
+
- **The tarball is audited in CI** for env files, credentials and host identities.
|
|
35
|
+
|
|
36
|
+
## What you must know
|
|
37
|
+
|
|
38
|
+
- **The dashboard has no authentication.** Loopback-only is the protection. If you
|
|
39
|
+
expose `MEMHOUSE_HOST=0.0.0.0`, anyone who can reach the port can read every
|
|
40
|
+
transcript in the house and run read-only SQL. Do not expose it; put a reverse proxy
|
|
41
|
+
with auth in front if you must.
|
|
42
|
+
- **`/api/query` is read-only by enforcement, within limits.** Writes and table
|
|
43
|
+
functions (`url()`, `file()`, …) are refused server-side per request; resource use is
|
|
44
|
+
capped per query. It is still full SQL over your own house — treat dashboard access
|
|
45
|
+
as house access.
|
|
46
|
+
- **The env file holds the house credential** (`~/.memhouse/env`, mode-restricted).
|
|
47
|
+
`service install` inlines the same credential into the systemd/launchd unit, and says
|
|
48
|
+
so. `uninstall` keeps it by default; `--credentials` or `--full-removal` remove it.
|
|
49
|
+
- **Transcripts are shipped as-is.** memhouse does not redact. Whatever your editors
|
|
50
|
+
wrote to disk — including any secret an agent echoed into a session — is what lands
|
|
51
|
+
in the house. The house is as sensitive as your shell history; place it accordingly.
|
|
52
|
+
- **Isolation is grants, and grants are checkable.** A housemate can run `SHOW GRANTS`
|
|
53
|
+
and see exactly what they hold; memhouse does not ask anyone to trust a row policy they
|
|
54
|
+
cannot inspect. What it does NOT defend against is the operator: whoever administers
|
|
55
|
+
the ClickHouse can read every room by definition, and no arrangement inside the server
|
|
56
|
+
changes that. If you should not be readable by the person running the server, you need
|
|
57
|
+
a different server, not a different layout.
|
|
58
|
+
- **Members can enumerate each other** on a shared server: a table you may not read
|
|
59
|
+
errors differently (`Code: 497`) from one that does not exist (`Code: 60`). ClickHouse
|
|
60
|
+
behaviour, verified independent of memhouse's grants. It leaks names, never content —
|
|
61
|
+
and in a shared house the names are guessable anyway (`<member>_messages`), while
|
|
62
|
+
`SHOW USERS` is granted to members deliberately so `/mem:house` can list who to share
|
|
63
|
+
with. Treat membership as public and content as private.
|
|
64
|
+
|
|
65
|
+
## Reporting
|
|
66
|
+
|
|
67
|
+
The repository is private during beta. Report security issues to the maintainer
|
|
68
|
+
directly (ramazanpolat@gmail.com) rather than in a public channel.
|