memhouse 0.4.4 → 0.7.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.
- package/README.md +244 -114
- package/bin/memhouse.js +1177 -231
- package/editors/base.js +6 -0
- package/editors/claude.js +44 -9
- package/editors/goose.js +4 -0
- package/editors/index.js +0 -0
- package/editors/opencode.js +3 -0
- package/{mem-house → memhouse}/COMPETITION.md +5 -5
- package/{mem-house → memhouse}/DESIGN.md +18 -12
- package/memhouse/RESUME-AND-UPDATE.md +185 -0
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
- package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
- package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
- package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
- package/memhouse/host.js +101 -0
- package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
- package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
- package/{mem-house → memhouse}/per-member/SHARING.md +47 -0
- package/memhouse/per-member/provision.js +297 -0
- package/memhouse/per-member/rooms.js +252 -0
- package/{mem-house → memhouse}/per-member/schema-member.sql.tpl +18 -14
- package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
- package/memhouse/resume.js +150 -0
- package/memhouse/self-update.js +150 -0
- package/{mem-house → memhouse}/server/queries.js +80 -34
- package/memhouse/server/server.js +451 -0
- package/{mem-house → memhouse}/service.js +48 -29
- package/memhouse/shipper/ship.js +949 -0
- package/package.json +5 -20
- package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
- package/public/index.html +2 -2
- package/ui/index.html +1 -1
- package/ui/src/App.jsx +1 -1
- package/ui/src/lib/api.js +29 -13
- package/ui/src/pages/Dashboard.jsx +8 -5
- package/ui/src/pages/Sessions.jsx +3 -1
- package/ui/src/pages/SqlViewer.jsx +6 -23
- package/cache.js +0 -850
- package/ch-ingest.js +0 -329
- package/ch-schema.js +0 -152
- package/clickhouse.js +0 -87
- package/index.js +0 -425
- package/mcp-server.js +0 -294
- package/mem-house/delivery/install.sh +0 -183
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
- package/mem-house/per-member/provision.js +0 -142
- package/mem-house/per-member/rooms.js +0 -132
- package/mem-house/server/server.js +0 -228
- package/mem-house/shipper/ship.js +0 -566
- package/relay-client.js +0 -306
- package/relay-server.js +0 -577
- package/server.js +0 -860
- package/share-image.js +0 -367
- /package/{mem-house → memhouse}/deploy.js +0 -0
- /package/{mem-house → memhouse}/envfile.js +0 -0
- /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
- /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
- /package/{mem-house → memhouse}/per-member/README.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
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/editors/opencode.js
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
|
|
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
|
-
| **
|
|
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
|
-
| **
|
|
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.**
|
|
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
|
-
#
|
|
1
|
+
# memhouse — design
|
|
2
2
|
|
|
3
|
-
**
|
|
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`)
|
|
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.
|
|
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
|
-
**
|
|
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,
|
|
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
|
-
|
|
79
|
-
`server.js` (same routes, same response shapes) over the
|
|
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
|
-
`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
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
|
-
#
|
|
1
|
+
# memhouse — agent install
|
|
2
2
|
|
|
3
|
-
Imperative steps for an AI coding agent installing
|
|
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: `[
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
#
|
|
1
|
+
# memhouse — system-prompt snippet
|
|
2
2
|
|
|
3
|
-
Paste (or import) this into an agent's system prompt to make
|
|
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 (
|
|
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 **
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
55
|
+
- Do not paste credentials from `{{ENV_FILE}}` into responses, commits, or logs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# memhouse — install as a kernel agency
|
|
2
2
|
|
|
3
|
-
How to run
|
|
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
|
|
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.
|
|
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"]
|