memhouse 0.4.4 → 0.7.0

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 (63) hide show
  1. package/README.md +244 -114
  2. package/bin/memhouse.js +1162 -230
  3. package/editors/base.js +6 -0
  4. package/editors/claude.js +44 -9
  5. package/editors/goose.js +4 -0
  6. package/editors/index.js +0 -0
  7. package/editors/opencode.js +3 -0
  8. package/{mem-house → memhouse}/COMPETITION.md +5 -5
  9. package/{mem-house → memhouse}/DESIGN.md +18 -12
  10. package/memhouse/RESUME-AND-UPDATE.md +185 -0
  11. package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
  12. package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
  13. package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
  14. package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
  15. package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
  16. package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
  17. package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
  18. package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
  19. package/memhouse/host.js +101 -0
  20. package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
  21. package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
  22. package/memhouse/per-member/provision.js +265 -0
  23. package/memhouse/per-member/rooms.js +235 -0
  24. package/{mem-house → memhouse}/per-member/schema-member.sql.tpl +18 -14
  25. package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
  26. package/memhouse/resume.js +150 -0
  27. package/memhouse/self-update.js +150 -0
  28. package/{mem-house → memhouse}/server/queries.js +80 -34
  29. package/memhouse/server/server.js +451 -0
  30. package/{mem-house → memhouse}/service.js +48 -29
  31. package/memhouse/shipper/ship.js +949 -0
  32. package/package.json +5 -20
  33. package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
  34. package/public/index.html +2 -2
  35. package/ui/index.html +1 -1
  36. package/ui/src/App.jsx +1 -1
  37. package/ui/src/lib/api.js +29 -13
  38. package/ui/src/pages/Dashboard.jsx +8 -5
  39. package/ui/src/pages/Sessions.jsx +3 -1
  40. package/ui/src/pages/SqlViewer.jsx +6 -23
  41. package/cache.js +0 -850
  42. package/ch-ingest.js +0 -329
  43. package/ch-schema.js +0 -152
  44. package/clickhouse.js +0 -87
  45. package/index.js +0 -425
  46. package/mcp-server.js +0 -294
  47. package/mem-house/delivery/install.sh +0 -183
  48. package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
  49. package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
  50. package/mem-house/per-member/provision.js +0 -142
  51. package/mem-house/per-member/rooms.js +0 -132
  52. package/mem-house/server/server.js +0 -228
  53. package/mem-house/shipper/ship.js +0 -566
  54. package/relay-client.js +0 -306
  55. package/relay-server.js +0 -577
  56. package/server.js +0 -860
  57. package/share-image.js +0 -367
  58. /package/{mem-house → memhouse}/deploy.js +0 -0
  59. /package/{mem-house → memhouse}/envfile.js +0 -0
  60. /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
  61. /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
  62. /package/{mem-house → memhouse}/per-member/README.md +0 -0
  63. /package/{mem-house → memhouse}/per-member/SHARING.md +0 -0
package/editors/base.js CHANGED
@@ -41,6 +41,12 @@ function getAppDataPath(appName) {
41
41
  * { source, composerId, name, createdAt, lastUpdatedAt, mode, folder, bubbleCount, encrypted }
42
42
  * getMessages(chat) - returns array of message objects:
43
43
  * { role: 'user'|'assistant'|'system'|'tool', content: string|Array }
44
+ * Optional per message, when the underlying transcript records it:
45
+ * _ts - epoch ms the message was sent. Without it the shipper
46
+ * interpolates a position between the session's createdAt and
47
+ * lastUpdatedAt, which can be off by the whole span of a session.
48
+ * Set it wherever the format has it; leave it out otherwise.
49
+ * _model, _inputTokens, _outputTokens, _cacheRead, _cacheWrite, _toolCalls
44
50
  */
45
51
 
46
52
  /**
package/editors/claude.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const path = require('path');
2
2
  const fs = require('fs');
3
3
  const os = require('os');
4
+ const adapterErrors = require('./adapter-errors');
4
5
 
5
6
  const HOME = os.homedir();
6
7
 
@@ -165,14 +166,28 @@ function peekSessionMeta(filePath) {
165
166
 
166
167
  function cleanPrompt(prompt) {
167
168
  if (!prompt || prompt === 'No prompt') return null;
168
- // Strip XML tags and system-reminder blocks
169
+ // Strip envelopes CONTENT AND ALL, before the generic tag strip.
170
+ //
171
+ // <local-command-caveat> was being handled by the tag strip alone, which removes the
172
+ // tags and keeps the text — so 34 of 400 sessions on this machine stored "Caveat: The
173
+ // messages below were generated by the user while running local commands. DO NOT
174
+ // respond to these messages…" as their title and first_prompt. Two costs: it is a
175
+ // 120-character legal blob where a session name belongs, and an agent READING those
176
+ // rows treats it as an instruction aimed at itself. Observed — a Claude Code driving
177
+ // the sessions skill spent two answers warning about "a prompt injection stored as
178
+ // data" instead of listing sessions, and concluded of one row that "the injection
179
+ // worked". Anything whose whole purpose is to instruct a model is not a title.
169
180
  let clean = prompt
170
181
  .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
182
+ .replace(/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g, '')
183
+ .replace(/<command-(?:name|message|args|contents|stdout|stderr)>[\s\S]*?<\/command-\1>/g, '')
171
184
  .replace(/<[^>]+>/g, '')
172
185
  .replace(/\s+/g, ' ')
173
- .trim()
174
- .substring(0, 120);
175
- return clean || null;
186
+ .trim();
187
+ // A prompt that was ONLY an envelope has no title in it. Say so rather than storing a
188
+ // fragment of boilerplate that survived the strip.
189
+ if (/^Caveat: The messages below were generated by the user/i.test(clean)) return null;
190
+ return clean.substring(0, 120) || null;
176
191
  }
177
192
 
178
193
  // Parse one Claude session .jsonl into the adapter's message shape. When
@@ -186,11 +201,27 @@ function parseSessionFile(filePath, isSubagent) {
186
201
 
187
202
  for (const line of lines) {
188
203
  let obj;
189
- try { obj = JSON.parse(line); } catch { continue; }
204
+ // A line that will not parse is a message that will not be stored. Dropping it in
205
+ // silence means the transcript is quietly short: not withheld, not truncated, and
206
+ // nothing in ship, discover or doctor says a line was lost. The SQLite adapters
207
+ // report their parse failures through this sink and the shipper withholds the session
208
+ // on them; JSONL line failures bypassed it entirely.
209
+ try { obj = JSON.parse(line); } catch (e) {
210
+ adapterErrors.record('claude-code', new Error(`unparseable JSONL line: ${e.message}`), filePath);
211
+ continue;
212
+ }
213
+
214
+ // Claude Code stamps every JSONL line with an ISO timestamp. Carrying it through as
215
+ // _ts lets the shipper store when a message was ACTUALLY sent instead of a position
216
+ // interpolated between the session's first and last times — see messageTs() in
217
+ // memhouse/shipper/ship.js. Optional by contract: adapters that have no per-message
218
+ // time simply omit it.
219
+ const _ts = obj.timestamp ? Date.parse(obj.timestamp) : undefined;
220
+ const at = Number.isFinite(_ts) ? _ts : undefined;
190
221
 
191
222
  if (obj.type === 'user' && obj.message) {
192
223
  const content = extractContent(obj.message.content);
193
- if (content) messages.push({ role: 'user', content: tag + content });
224
+ if (content) messages.push({ role: 'user', content: tag + content, _ts: at });
194
225
  } else if (obj.type === 'assistant' && obj.message) {
195
226
  const { text, toolCalls } = extractAssistantContent(obj.message.content);
196
227
  const usage = obj.message.usage;
@@ -198,11 +229,11 @@ function parseSessionFile(filePath, isSubagent) {
198
229
  role: 'assistant', content: tag + text, _model: obj.message.model,
199
230
  _inputTokens: usage?.input_tokens, _outputTokens: usage?.output_tokens,
200
231
  _cacheRead: usage?.cache_read_input_tokens, _cacheWrite: usage?.cache_creation_input_tokens,
201
- _toolCalls: toolCalls,
232
+ _toolCalls: toolCalls, _ts: at,
202
233
  });
203
234
  } else if (obj.type === 'system') {
204
235
  const text = typeof obj.message?.content === 'string' ? obj.message.content : '';
205
- if (text) messages.push({ role: 'system', content: tag + text });
236
+ if (text) messages.push({ role: 'system', content: tag + text, _ts: at });
206
237
  }
207
238
  }
208
239
  return messages;
@@ -383,4 +414,8 @@ function getMCPServers() {
383
414
  return results;
384
415
  }
385
416
 
386
- module.exports = { name, labels, getChats, getMessages, getUsage, getArtifacts, getMCPServers };
417
+ // discoverClaudeRoots is exported for the CLI's plugin installer, which needs the same
418
+ // answer to a different question: where this pilot's Claude Code instances live. Two
419
+ // implementations of "find every CLAUDE_CONFIG_DIR" would drift the first time a playbook
420
+ // layout changes, and this is the copy exercised on every single ship.
421
+ module.exports = { name, labels, getChats, getMessages, getUsage, getArtifacts, getMCPServers, discoverClaudeRoots };
package/editors/goose.js CHANGED
@@ -243,6 +243,10 @@ function getMessagesFromDb(chat) {
243
243
  const msg = { role: mappedRole, content };
244
244
  if (mappedRole === 'assistant' && chat._model) msg._model = chat._model;
245
245
  if (toolCalls.length) msg._toolCalls = toolCalls;
246
+ // created_timestamp is the column this query already sorts by (seconds). Pass it on
247
+ // so the shipper stores the real send time rather than interpolating one; messageTs()
248
+ // handles the seconds-vs-milliseconds split.
249
+ if (row.created_timestamp) msg._ts = Number(row.created_timestamp);
246
250
  result.push(msg);
247
251
  }
248
252
 
package/editors/index.js CHANGED
File without changes
@@ -317,6 +317,9 @@ function getMessagesForSession(sessionId) {
317
317
  _cacheRead: cacheRead,
318
318
  _cacheWrite: cacheWrite,
319
319
  _finish: msg.finish,
320
+ // The same time already used to sort above. Passing it on means the shipper
321
+ // stores when the message was sent instead of interpolating a position.
322
+ _ts: msg.time?.created,
320
323
  });
321
324
  }
322
325
  }
@@ -1,4 +1,4 @@
1
- # mem-house — the competition
1
+ # memhouse — the competition
2
2
 
3
3
  Companion to `DESIGN.md`. That file states **the four bets**; this one grades each
4
4
  bet against the field as it actually stands.
@@ -25,7 +25,7 @@ whole analysis, because most "agent memory" products are not competitors at all.
25
25
  | **Memory SDKs** | Developer calls `add()` / `search()` | Active — the developer writes it | No — different product |
26
26
  | **Observability** | OTel/proxy traces of model calls | Passive, at the API layer | Only on analytics |
27
27
 
28
- mem-house is a transcript reader with the observability camp's analytics attached.
28
+ memhouse is a transcript reader with the observability camp's analytics attached.
29
29
  That combination is the most defensible thing about the product's *shape*; the
30
30
  individual pieces are not scarce.
31
31
 
@@ -48,7 +48,7 @@ Editor coverage — the number we lead the README with — is mid-pack:
48
48
  | AgentsView | **50+** | each with documented on-disk paths |
49
49
  | lean-ctx | 30+ | claimed |
50
50
  | CCHV | 28 | each with documented paths |
51
- | **mem-house** | **17** | |
51
+ | **memhouse** | **17** | |
52
52
  | deja-vu | 17 | different 17 — has Cline, aider, Qwen Code, Kimi Code, Roo Code |
53
53
  | coding_agent_session_search | 11+ | |
54
54
  | ctx | 8 | |
@@ -90,7 +90,7 @@ datastore's own permission layer.
90
90
  | mem0 | `user_id` + `agent_id` + `run_id` filters | Metadata filtering, not enforcement |
91
91
  | Industry pattern | `tenant_id` column + row-level security | Policy layer, revocable by whoever owns the policy |
92
92
  | Contextify | Cloud workspace, per-project opt-in | Real, but proprietary, cloud-first, 2 editors |
93
- | **mem-house** | three `GRANT SELECT` on the member's own rooms | **A member cannot name another member's rooms.** `WITH GRANT OPTION` on `SELECT` and not on `ALL` makes sharing read-only by construction and operator-free |
93
+ | **memhouse** | three `GRANT SELECT` on the member's own rooms | **A member cannot name another member's rooms.** `WITH GRANT OPTION` on `SELECT` and not on `ALL` makes sharing read-only by construction and operator-free |
94
94
 
95
95
  Bet 3 is not a niche preference. It is a category-wide blind spot, and it is the
96
96
  only thing in this document that nobody else is attempting.
@@ -133,7 +133,7 @@ memory. None ingest agent sessions.
133
133
 
134
134
  ## What they do better
135
135
 
136
- - **Distribution.** mem-house: 0 stars, 655 npm downloads/month, no HN or search
136
+ - **Distribution.** memhouse: 0 stars, 655 npm downloads/month, no HN or search
137
137
  presence. claude-mem: 89,967 stars and 62,940 downloads/month, free, no company.
138
138
  This dominates every other consideration in the document.
139
139
  - **Automatic recall.** deja-vu installs a SessionStart hook; claude-mem injects into
@@ -1,6 +1,6 @@
1
- # mem-house — design
1
+ # memhouse — design
2
2
 
3
- **mem-house** is agent conversation memory as a product: every coding-agent session
3
+ **memhouse** is agent conversation memory as a product: every coding-agent session
4
4
  on your machines — across **all 17 editors** agentlytics supports — parsed locally,
5
5
  shipped to a typed ClickHouse store, shareable with a team, installable on the
6
6
  ultimagent kernel as an **agency**, and visible through the agentlytics dashboard.
@@ -10,7 +10,7 @@ ultimagent kernel as an **agency**, and visible through the agentlytics dashboar
10
10
  apart by the `host` column; no member writes into another's. The shared-table layout
11
11
  this once had was removed in 0.4.0; see `per-member/`.
12
12
 
13
- **Agency, precisely.** In constellation terms (`../TERMINOLOGY.md`) mem-house is a
13
+ **Agency, precisely.** In constellation terms (`../TERMINOLOGY.md`) memhouse is a
14
14
  **house** — the `mem` database — **plus a resident**: the shipper. That pairing
15
15
  is what the word *agency* means; a house on its own only holds.
16
16
 
@@ -26,7 +26,7 @@ on = "loop" # a daemon — not an insert trigger, not a schedule
26
26
  The session rollup is a **routine** — house machinery, not a second resident. Since
27
27
  0.4.0 it is not even an object: it is SQL text (`per-member/rooms.js`), substituted with
28
28
  the caller's own room names and computed during your query, for your query, writing
29
- nothing. mem-house has **no materialized views at all** — in fact no stored views at all
29
+ nothing. memhouse has **no materialized views at all** — in fact no stored views at all
30
30
  — so the shipper is not merely a resident, it is the only candidate in the tree. Strip
31
31
  it and every remaining moving part is a routine over rows nobody is writing any more.
32
32
 
@@ -42,7 +42,7 @@ skills/plugin delivery) and agentlytics' proven assets (adapters, UI, cost engin
42
42
  but **not** its shared-table-plus-row-policy layout, which 0.4.0 removed in favour of
43
43
  one set of rooms per member. If it wins, it can become memory-house v4; until then the
44
44
  two run side by side as separate agencies, each in its own house. Naming: the product is
45
- **mem-house**; the agency is **`memhouse`** and its house is the **`mem`** database
45
+ **memhouse**; the agency is **`memhouse`** and its house is the **`mem`** database
46
46
  (identifiers can't carry a dash; the owner kept the longer name, `memhouse_root`).
47
47
 
48
48
  ## The four bets
@@ -58,7 +58,7 @@ them free and further along; **bet 3 is the one nobody else attempts.**
58
58
  parsing is not a style choice — it is what unlocks 17-editor coverage. The
59
59
  shipper runs the adapters (`editors/`) and ships **typed rows**. On the canon's
60
60
  write axis this bet is precisely *moving work from routine to resident*:
61
- memory-house parses when you query, mem-house parses before anyone asks and
61
+ memory-house parses when you query, memhouse parses before anyone asks and
62
62
  writes the result down.
63
63
  2. **Typed common schema** (`per-member/schema-member.sql.tpl`, applied once per
64
64
  member; `schema-merge.sql.tpl` for the team rooms). Physical typed columns (what
@@ -75,8 +75,8 @@ them free and further along; **bet 3 is the one nobody else attempts.**
75
75
  (requires `async_insert=0`). Sharing is a further GRANT, issued by the member
76
76
  themselves (grant-option) or the owner.
77
77
  4. **Borrowed UI, zero fork.** The React SPA consumes REST JSON, not tables. The
78
- mem-house server implements the **same `/api/*` contract** as agentlytics'
79
- `server.js` (same routes, same response shapes) over the mem-house schema, and
78
+ memhouse server implements the **same `/api/*` contract** as agentlytics'
79
+ `server.js` (same routes, same response shapes) over the memhouse schema, and
80
80
  serves the same built `public/` bundle. UI changes: none.
81
81
 
82
82
  ## Components (this directory)
@@ -131,10 +131,16 @@ Config file: `~/.memhouse/config.json` (`hiddenProjects`, future prefs).
131
131
 
132
132
  ## Delivery kit (mirrors memory-house)
133
133
 
134
- `install.sh` (standalone), `AGENT-INSTALL.md` (agent-facing install steps),
135
- `skills/` (Claude Code skills to query/search mem-house), `plugin/` (Claude Code
136
- plugin wrapping the skills), `PROMPT.md` (system-prompt snippet teaching an agent
137
- to use mem-house as memory), `kernel-install.md` (provision as agency `memhouse`).
134
+ `AGENT-INSTALL.md` (agent-facing install steps), `plugin/` (the Claude Code plugin
135
+ wrapping the three skills), `PROMPT.md` (system-prompt snippet teaching an agent to
136
+ use memhouse as memory), `kernel-install.md` (provision as agency `memhouse`).
137
+
138
+ **`install.sh` is retired.** It was a second implementation of `memhouse install`,
139
+ maintained separately and drifting apart from it: by the end it still defaulted to
140
+ `http://localhost:8123` as `memhouse_root` — precisely the guessed house the CLI now
141
+ refuses on purpose, because on many machines that credential reaches a real house
142
+ belonging to someone else. One installer, and it is the one the acceptance suite
143
+ drives.
138
144
 
139
145
  ## Non-goals (v0)
140
146
 
@@ -0,0 +1,185 @@
1
+ # resume, update, and a daemon that notices
2
+
3
+ Three things that were missing, specified together because they share one premise: the
4
+ pilot's memory is only useful if they can *get back into* it, and only trustworthy if the
5
+ process holding it is the version they installed.
6
+
7
+ ---
8
+
9
+ ## 1. `memhouse resume <session-id>`
10
+
11
+ ### The gap
12
+
13
+ memhouse was a read-only archive. You could find the session that solved this problem
14
+ six weeks ago and not return to it. Every rival that does this is praised for it
15
+ (`COMPETITION.md`: Agent Sessions copies resume commands for six CLIs, claude-history
16
+ resumes *and* forks worktree-aware).
17
+
18
+ ### What it needs
19
+
20
+ Nothing new. A stored session already carries both halves:
21
+
22
+ | Column | Supplies |
23
+ |---|---|
24
+ | `session_id` | `<source>:<native-id>` — which CLI, and which session in it (`ship.js:575`) |
25
+ | `folder` | the directory the session ran in |
26
+
27
+ So resume is a **read-time computation over two columns**. No schema change, no
28
+ migration, no new room.
29
+
30
+ ### It prints; it does not run
31
+
32
+ The command is written to stdout for the pilot to paste.
33
+
34
+ Running it would mean guessing a terminal, inheriting this process's cwd, and assuming the
35
+ id is still live. When any of those is wrong, the CLI **does not fail** — it opens a new
36
+ session. The transcript the pilot asked for is still lost, and the tool reports success.
37
+ That is the exact failure shape this repo has been bitten by fifteen times: a wrong answer
38
+ delivered confidently.
39
+
40
+ ### The flag table is evidence, not recall
41
+
42
+ `memhouse/resume.js` holds one entry per resumable editor, and each entry records the date
43
+ its syntax was read out of that CLI's own `--help`.
44
+
45
+ | Source | Command | Read from |
46
+ |---|---|---|
47
+ | `claude` | `claude --resume <id>` | `claude --help`, 2026-08-12 |
48
+ | `codex` | `codex resume <id>` | `codex resume --help`, 2026-08-12 |
49
+ | `opencode` | `opencode --session <id>` | `opencode --help`, 2026-08-12 |
50
+
51
+ **An editor whose flag has not been read that way is absent from the table**, and an absent
52
+ entry prints a refusal. Round 10 is why: a field name was asserted from a failed search,
53
+ written into a comment, a PR and the task file, and was simply wrong. `goose`, `gemini-cli`
54
+ and `cursor-agent` are plausibly resumable and are *not* listed, because they are not
55
+ installed on the machine the table was written on.
56
+
57
+ The refusal distinguishes two cases, for the same reason `discover` separates a skipped
58
+ adapter from an editor you do not have:
59
+
60
+ - **No CLI at all** (`cursor`, `vscode`, `zed`, `kiro`, `copilot-jetbrains`, `antigravity`,
61
+ `devin`, `devin-next`) — nothing takes a session id, and no amount of checking will
62
+ produce something that does. Opening the folder is not resuming the session and is not
63
+ dressed up as it; the folder is printed as the one actionable fact memhouse holds.
64
+ - **Unverified** (`goose`, `gemini-cli`, `cursor-agent`, `copilot-cli`, `codebuff`,
65
+ `commandcode`, `gsd`) — a CLI that may well support it; nobody has read the flag yet.
66
+
67
+ Both sets are **explicit**, and a test derives the full source inventory from `editors/`
68
+ and asserts every source lands in exactly one of the three buckets. That test exists because
69
+ the same mistake was made twice within a day of each other: `RESUMERS` was first keyed on
70
+ `claude` when rows carry `claude-code`, and `windsurf` was listed as a GUI editor when the
71
+ adapter emits its VARIANTS ids, `devin` and `devin-next` — so every Devin session fell
72
+ through to the *unverified* message, which says the opposite of the truth. A source that
73
+ matches nothing must be a test failure, not a default.
74
+
75
+ And `copilot-cli` was in the GUI set while being, self-evidently, a command line.
76
+
77
+ ### Two more decisions
78
+
79
+ - **`FINAL` on the read.** `sessions` is a `ReplacingMergeTree`; without `FINAL` a
80
+ re-shipped session can resolve against its older row and print the folder the project
81
+ *used* to live in.
82
+ - **Ambiguity refuses.** A bare native id can match more than one source. The command lists
83
+ the matches and exits non-zero rather than picking, because resuming the wrong editor's
84
+ session of the same name is precisely the silent-wrong-answer it exists to prevent.
85
+
86
+ ### Explicitly not built: write-back
87
+
88
+ Restoring an archived transcript into an editor's own store. Every editor's format is
89
+ private and different, and a bad write corrupts the pilot's live history — risking the
90
+ original to duplicate the copy.
91
+
92
+ ---
93
+
94
+ ## 2. `memhouse update`
95
+
96
+ ### The gap
97
+
98
+ `npm i -g memhouse@latest` does one third of the job, and the other two thirds have each
99
+ cost a real machine real sessions:
100
+
101
+ 1. **`--allow-scripts=better-sqlite3` is not remembered.** Omit it on the upgrade and
102
+ `better-sqlite3` has no binding again — cursor, zed, opencode, goose and antigravity
103
+ read zero sessions on every pass afterwards.
104
+ 2. **The daemons keep running the old code.** See §3.
105
+ 3. **The house can be older than the shipper.** A newer shipper may need a column an
106
+ existing room lacks; it detects that and prints the rebuild, but only if something runs
107
+ it.
108
+
109
+ And from a checkout there is a fourth: `git pull` updates `ui/src` and leaves the built
110
+ bundle in `public/`, so the dashboard serves the previous release however often it is
111
+ restarted. A published tarball is immune — `prepack` builds `public/` before publish.
112
+
113
+ ### Install kinds, because the wrong upgrade is not a no-op
114
+
115
+ | Kind | Detected by | Action |
116
+ |---|---|---|
117
+ | `global` | under `npm prefix -g` | `npm i -g memhouse@latest --allow-scripts=better-sqlite3` |
118
+ | `checkout` | a `.git` at the root | `git pull --ff-only` → `npm install` → `npm run build` |
119
+ | `local-dep` | `node_modules/memhouse`, not under the global prefix | refuse; print the right command for where it lives |
120
+ | `npx` | `_npx` in the resolved path | refuse — the next `npx` resolves the registry anyway |
121
+
122
+ Running `npm i -g` from a checkout would install the **published** version over the branch
123
+ the pilot was testing. Running `git pull` in a global install does nothing and says it
124
+ worked.
125
+
126
+ ### Order of operations
127
+
128
+ Read the daemon state **first** (`stop` erases the evidence), upgrade, then restart, then
129
+ ensure the schema. A failed upgrade restarts nothing and says so — the running version is
130
+ left intact rather than half-replaced.
131
+
132
+ A service-managed shipper is not restarted behind the supervisor's back; the command prints
133
+ the supervisor's own restart line.
134
+
135
+ ---
136
+
137
+ ## 3. A daemon that notices
138
+
139
+ ### The gap
140
+
141
+ Nothing tells a running daemon that its files were replaced. `status` prints the version of
142
+ the CLI you just typed, not the version the daemon is executing. On macminim a shipper ran
143
+ **1d16h** out of a directory that had been moved.
144
+
145
+ ### Why not `postinstall`
146
+
147
+ npm blocks install scripts by default — the very condition this product already works
148
+ around — so a `postinstall` hook is unreliable exactly where it matters. And a package
149
+ install that restarts a user's daemons is a surprise even when it works.
150
+
151
+ ### The mechanism
152
+
153
+ `memhouse/self-update.js`. Each daemon snapshots at boot: the version **read off disk**
154
+ (never `require`d — require caches the first read, which is the value we are trying to
155
+ detect a change in), plus the realpath and mtime of its own entry script. Then:
156
+
157
+ - **shipper** — checks once per loop pass, *after* the sleep and never mid-pass, so an
158
+ upgrade landing during the interval is acted on at the top of the next pass and a
159
+ half-shipped session is never abandoned.
160
+ - **dashboard** — has no pass, so it polls on an unref'd 60s timer. It must be covered: the
161
+ static middleware bound to the old `public/` at boot, so its stale bundle is what the
162
+ pilot actually *sees*.
163
+
164
+ ### Two modes, and getting them backwards is expensive
165
+
166
+ | Mode | Signal | Behaviour |
167
+ |---|---|---|
168
+ | supervised | `MEMHOUSE_SUPERVISED=1`, written into the unit by `service install` | **exit 0** — the supervisor starts the new version |
169
+ | unsupervised | `memhouse start` pidfiles | **re-exec** detached, adopt the pidfile, exit |
170
+
171
+ A process that re-execs under a supervisor races the unit's own restart, and two shippers
172
+ on one house clear each other's rows. A process that merely exits with no supervisor stops
173
+ collecting memory entirely.
174
+
175
+ ### Guards
176
+
177
+ - **Re-exec chains are counted in the environment** (`MEMHOUSE_REEXEC_CHAIN`) and capped at
178
+ 3, so a mis-detection cannot spin.
179
+ - **A 60s floor** from boot before any handover.
180
+ - **The pidfile is only rewritten if it names us.** A daemon started by hand has no pidfile;
181
+ one naming another process belongs to that process, and overwriting it would point
182
+ `memhouse stop` at ours and orphan theirs forever.
183
+ - **A vanished entry is reported, not acted on.** If the directory is gone there is nothing
184
+ to exec and no supervisor to resolve a fresh path, so the daemon keeps running and says so
185
+ every pass. A stale daemon still collects sessions; a dead one collects nothing.
@@ -38,7 +38,7 @@ already exist.
38
38
  runs, ask them for it. Do not guess, do not read it out of a file they did not name.
39
39
  - **Do not run `deploy --down`.** It destroys the container *and its volume*. It is never
40
40
  part of an install.
41
- - The install writes `~/.memhouse/env` at mode 0600. Do not print its contents; do not
41
+ - The install writes `{{ENV_FILE}}` at mode 0600. Do not print its contents; do not
42
42
  echo a password into the transcript you are writing, which memhouse will then ship.
43
43
  - If a command fails, read what it printed before retrying. memhouse's failures name the
44
44
  cause and usually name the fix — it is not a generic tool that fails generically.
@@ -1,6 +1,6 @@
1
- # mem-house — agent install
1
+ # memhouse — agent install
2
2
 
3
- Imperative steps for an AI coding agent installing mem-house on a machine. Execute
3
+ Imperative steps for an AI coding agent installing memhouse on a machine. Execute
4
4
  in order; every command is copy-pasteable.
5
5
 
6
6
  **Prefer `memhouse prompt --install`.** It renders `AGENT-INSTALL-PROMPT.md` against the
@@ -67,7 +67,7 @@ chmod 600 ~/.memhouse/env
67
67
  memhouse install --yes # applies the schema, then runs the first full ship
68
68
  ```
69
69
 
70
- Expected output shape: `[mem-house] shipped N sessions (0 skipped) → M msg rows, T
70
+ Expected output shape: `[memhouse] shipped N sessions (0 skipped) → M msg rows, T
71
71
  tool rows in Xs` with N in the hundreds on a machine with real agent usage.
72
72
 
73
73
  ## 4. Verify
@@ -107,7 +107,9 @@ re-ship loop, so this replaces any separate cron. `memhouse stop` ends both.
107
107
  memhouse plugins install claude
108
108
  ```
109
109
 
110
- Installs the `memhouse-search` / `memhouse-sessions` / `memhouse-sql` skills into
111
- the agent's skills directory; they read the connection from `~/.memhouse/env`.
112
- `memhouse plugins list` shows what is installed, `memhouse plugins remove claude`
113
- undoes it.
110
+ Installs the memhouse plugin into `<claude-config>/skills/memhouse`, which loads next
111
+ session as `memhouse@skills-dir` and exposes `/memhouse:search`, `/memhouse:sessions`
112
+ and `/memhouse:sql`. They read the connection from `$MEMHOUSE_HOME/env` (default
113
+ `~/.memhouse/env`), with exported `MEMHOUSE_*` vars taking precedence, and refuse to run
114
+ rather than guessing a URL when neither is set. `memhouse plugins list` shows what is
115
+ installed, `memhouse plugins remove claude` undoes it.
@@ -1,25 +1,25 @@
1
- # mem-house — system-prompt snippet
1
+ # memhouse — system-prompt snippet
2
2
 
3
- Paste (or import) this into an agent's system prompt to make mem-house its
3
+ Paste (or import) this into an agent's system prompt to make memhouse its
4
4
  long-term conversation memory.
5
5
 
6
6
  ---
7
7
 
8
- ## Conversation memory (mem-house)
8
+ ## Conversation memory (memhouse)
9
9
 
10
10
  You have persistent memory of past agent sessions — every conversation this user
11
11
  has had with coding agents (Claude Code, Codex, Gemini CLI, Cursor, and other
12
12
  editors), across all of YOUR machines, stored in your own rooms in a ClickHouse
13
- database called **mem**. Other members of the same house have their own rooms and
13
+ database called **{{DB}}**. Other members of the same house have their own rooms and
14
14
  you hold no grant on them.
15
- Connection: read `~/.memhouse/env` (`MEMHOUSE_URL/USER/PASSWORD/DB`); query over
15
+ Connection: read `{{ENV_FILE}}` (`MEMHOUSE_URL/USER/PASSWORD/DB`); query over
16
16
  HTTP with `curl -u "$MEMHOUSE_USER:$MEMHOUSE_PASSWORD" "$MEMHOUSE_URL/?database=$MEMHOUSE_DB"`
17
17
  and always add `SETTINGS final=1, join_use_nulls=1` to reads — the second matters
18
18
  wherever the session rollup is used (see below).
19
19
 
20
20
  **When to reach for it — before claiming ignorance.** If the user refers to past
21
21
  work that is not in your current context ("that session where…", "how did I solve
22
- X before", "did we ever…", "what was I working on last week"), search mem-house
22
+ X before", "did we ever…", "what was I working on last week"), search memhouse
23
23
  FIRST. Only say you don't know after a search comes back empty.
24
24
 
25
25
  **How to query.** Prefer the installed skills when present: `/memhouse:search`
@@ -46,10 +46,10 @@ grant for.
46
46
 
47
47
  **Rules.**
48
48
  - Memory is READ-ONLY for you. Never INSERT/ALTER/DROP — ingestion belongs to the
49
- mem-house shipper alone.
49
+ memhouse shipper alone.
50
50
  - Quote retrieved content as *the user's past sessions*, and cite the session_id
51
51
  when the user may want to dig deeper.
52
52
  - Other members' sessions are invisible to you unless they have granted you their rooms.
53
53
  You hold no grant on them otherwise, so naming one fails rather than returning nothing.
54
54
  An empty result means "nothing visible", not "nothing ever happened".
55
- - Do not paste credentials from `~/.memhouse/env` into responses, commits, or logs.
55
+ - Do not paste credentials from `{{ENV_FILE}}` into responses, commits, or logs.
@@ -1,6 +1,6 @@
1
- # mem-house — install as a kernel agency
1
+ # memhouse — install as a kernel agency
2
2
 
3
- How to run mem-house as an **agency** on an ultimagent kernel — a ClickHouse server
3
+ How to run memhouse as an **agency** on an ultimagent kernel — a ClickHouse server
4
4
  (a **town**) promoted into a realm — instead of a plain ClickHouse. Same flow proven
5
5
  for the agentlytics agency (`agency/AGENCY.md`); the agency is named **`memhouse`** and
6
6
  its house is the **`mem`** database (CH identifiers can't carry a dash, and the owning
@@ -67,7 +67,7 @@ python3 executor/executor.py approve <call_id> && python3 executor/executor.py d
67
67
  # grant-option on mem.*). One idempotent step — see ../per-member/PROVISIONING.md:
68
68
  MEM_URL=https://<kernel-host>:8443 MEM_USER=memhouse_root \
69
69
  MEM_PASSWORD=<credential> MEM_DB=mem \
70
- node mem-house/per-member/provision.js --member alice --merge
70
+ node memhouse/per-member/provision.js --member alice --merge
71
71
  ```
72
72
 
73
73
  **Never `GRANT … ON mem.*` to a member.** A member who can read `mem.*` can read every
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memhouse",
3
3
  "description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: search, browse, and analyze every past session across all 17 supported editors and machines. Skills install as /memhouse:search, /memhouse:sessions, and /memhouse:sql.",
4
- "version": "0.3.0",
4
+ "version": "0.4.6",
5
5
  "author": { "name": "Ramazan Polat" },
6
6
  "homepage": "https://github.com/agent-realm/memhouse",
7
7
  "keywords": ["memory", "clickhouse", "search", "sessions", "analytics", "agentlytics"]