memhouse 0.3.1 → 0.4.2

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 (37) hide show
  1. package/README.md +83 -6
  2. package/bin/memhouse.js +882 -40
  3. package/editors/zed.js +55 -6
  4. package/index.js +2 -2
  5. package/mem-house/COMPETITION.md +217 -0
  6. package/mem-house/DESIGN.md +61 -32
  7. package/mem-house/delivery/AGENT-INSTALL.md +23 -3
  8. package/mem-house/delivery/PROMPT.md +23 -10
  9. package/mem-house/delivery/install.sh +9 -7
  10. package/mem-house/delivery/kernel-install.md +56 -25
  11. package/mem-house/delivery/skills/memhouse-search/SKILL.md +46 -16
  12. package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +38 -7
  13. package/mem-house/delivery/skills/memhouse-sql/SKILL.md +48 -17
  14. package/mem-house/deploy.js +333 -0
  15. package/mem-house/envfile.js +49 -0
  16. package/mem-house/per-member/INSTALL.md +183 -0
  17. package/mem-house/per-member/PLAN.md +174 -0
  18. package/mem-house/per-member/PROVISIONING.md +177 -0
  19. package/mem-house/per-member/README.md +48 -0
  20. package/mem-house/per-member/SCHEMA.md +73 -0
  21. package/mem-house/per-member/SHARING.md +102 -0
  22. package/mem-house/per-member/provision.js +142 -0
  23. package/mem-house/per-member/rooms.js +132 -0
  24. package/mem-house/per-member/schema-member.sql.tpl +85 -0
  25. package/mem-house/per-member/schema-merge.sql.tpl +24 -0
  26. package/mem-house/server/queries.js +103 -53
  27. package/mem-house/service.js +400 -0
  28. package/mem-house/shipper/ship.js +61 -18
  29. package/package.json +3 -3
  30. package/pricing.js +20 -2
  31. package/pricing.json +216 -24
  32. package/public/assets/{index-CCPh7a2Z.js → index-CtH-5i95.js} +5 -5
  33. package/public/index.html +1 -1
  34. package/ui/src/App.jsx +3 -2
  35. package/ui/src/pages/SqlViewer.jsx +39 -21
  36. package/mem-house/rls.sql +0 -17
  37. package/mem-house/schema.sql +0 -145
package/editors/zed.js CHANGED
@@ -81,6 +81,44 @@ function queryDb(sql) {
81
81
  }
82
82
  }
83
83
 
84
+ // Zed's `threads` schema varies by version: `worktree_branch` exists in some builds
85
+ // and not others, and older builds lack `created_at`. Naming a column that is not
86
+ // there fails the WHOLE query with "no such column", which cost every Zed thread on
87
+ // this machine — silently, until the adapter started reporting. So ask the table what
88
+ // it has rather than assuming. Cached: the schema cannot change while we scan.
89
+ let threadColumns = null;
90
+ function getThreadColumns() {
91
+ if (threadColumns) return threadColumns;
92
+ if (!fs.existsSync(THREADS_DB)) return new Set();
93
+ try {
94
+ const db = new Database(THREADS_DB, { readonly: true });
95
+ const cols = db.prepare('PRAGMA table_info(threads)').all().map((r) => r.name);
96
+ db.close();
97
+ // PRAGMA on a missing or renamed table SUCCEEDS with zero rows rather than
98
+ // throwing. Left unreported, that would cache an empty set, return no sessions,
99
+ // and look exactly like an unused Zed install — the same silent whole-adapter
100
+ // drop this function exists to end. Do not cache it either: the next scan should
101
+ // look again rather than inherit the verdict.
102
+ if (cols.length === 0) {
103
+ adapterErrors.record('zed', new Error('threads table missing or unreadable — incompatible Zed schema'), THREADS_DB);
104
+ return new Set();
105
+ }
106
+ threadColumns = new Set(cols);
107
+ return threadColumns;
108
+ } catch (e) {
109
+ adapterErrors.record('zed', e, THREADS_DB);
110
+ return new Set();
111
+ }
112
+ }
113
+
114
+ // index.js calls this between passes of a long-running `ship --loop`. Without it the
115
+ // column set would be fixed for the life of the process, so a Zed upgrade mid-run
116
+ // would keep querying a column that no longer exists — or keep ignoring one newly
117
+ // added — until someone restarted the shipper.
118
+ function resetCache() {
119
+ threadColumns = null;
120
+ }
121
+
84
122
  function queryBlob(id) {
85
123
  if (!fs.existsSync(THREADS_DB)) return null;
86
124
  try {
@@ -103,22 +141,33 @@ function queryBlob(id) {
103
141
  const name = 'zed';
104
142
 
105
143
  function getChats() {
106
- const rows = queryDb(
107
- 'SELECT id, summary, updated_at, data_type, length(data) as data_size, parent_id, worktree_branch FROM threads ORDER BY updated_at DESC'
108
- );
144
+ const cols = getThreadColumns();
145
+ if (cols.size === 0) return [];
146
+
147
+ // Only these are relied on. Anything else is taken when the build provides it.
148
+ const select = ['id', 'summary', 'updated_at', 'data_type', 'length(data) as data_size', 'parent_id'];
149
+ const hasBranch = cols.has('worktree_branch');
150
+ const hasCreated = cols.has('created_at');
151
+ if (hasBranch) select.push('worktree_branch');
152
+ if (hasCreated) select.push('created_at');
153
+
154
+ const rows = queryDb(`SELECT ${select.join(', ')} FROM threads ORDER BY updated_at DESC`);
109
155
 
110
156
  return rows.map(row => ({
111
157
  source: 'zed',
112
158
  composerId: row.id,
113
159
  name: row.summary || null,
114
- createdAt: row.updated_at ? new Date(row.updated_at).getTime() : null,
160
+ // created_at when the build has it. Falling back to updated_at is not a
161
+ // creation time — it just keeps the field non-null on builds that lack it.
162
+ createdAt: (hasCreated && row.created_at) ? new Date(row.created_at).getTime()
163
+ : (row.updated_at ? new Date(row.updated_at).getTime() : null),
115
164
  lastUpdatedAt: row.updated_at ? new Date(row.updated_at).getTime() : null,
116
165
  mode: 'thread',
117
166
  folder: null,
118
167
  encrypted: false,
119
168
  bubbleCount: 0,
120
169
  _dataType: row.data_type,
121
- _gitBranch: row.worktree_branch,
170
+ _gitBranch: hasBranch ? row.worktree_branch : null,
122
171
  }));
123
172
  }
124
173
 
@@ -226,4 +275,4 @@ function getMCPServers() {
226
275
  return results;
227
276
  }
228
277
 
229
- module.exports = { name, labels, getChats, getMessages, getMCPServers };
278
+ module.exports = { name, labels, getChats, getMessages, getMCPServers, resetCache };
package/index.js CHANGED
@@ -121,10 +121,10 @@ if (isJoin) {
121
121
  // Fail fast: the relay join client still targets the removed synchronous SQLite
122
122
  // cache (relay-client.js pickProjects → cache.getDb), which the ClickHouse
123
123
  // cutover eliminated — it would crash after connecting. In memhouse, team
124
- // sharing is the kernel path (members + owner grants + RLS), not relay.
124
+ // sharing is grants on per-member rooms, not relay.
125
125
  console.error(chalk.red('\n ✗ Relay join is not supported in memhouse.'));
126
126
  console.error(chalk.dim(' Team sharing here is kernel-based: see mem-house/delivery/kernel-install.md'));
127
- console.error(chalk.dim(' (register-member + owner GRANT + own-only RLS).\n'));
127
+ console.error(chalk.dim(' (register-member + per-member rooms + a GRANT on your own rooms).\n'));
128
128
  process.exit(1);
129
129
  }
130
130
 
@@ -0,0 +1,217 @@
1
+ # mem-house — the competition
2
+
3
+ Companion to `DESIGN.md`. That file states **the four bets**; this one grades each
4
+ bet against the field as it actually stands.
5
+
6
+ **Surveyed 2026-08-07.** Every star/fork/date figure was pulled from the GitHub API
7
+ on that date and is a decaying fact — re-run before citing. Claims that could not be
8
+ verified are marked *unverified* rather than asserted. X/Twitter was not reachable
9
+ (HTTP 402 to the fetch tool), so nothing here reflects it.
10
+
11
+ The finding, in one line: **three of the four bets are already occupied by shipping
12
+ competitors, most of them free and further along. Bet 3 is the one that holds.**
13
+
14
+ ---
15
+
16
+ ## The field splits four ways
17
+
18
+ The space shares vocabulary but not architecture. Distinguishing the camps is the
19
+ whole analysis, because most "agent memory" products are not competitors at all.
20
+
21
+ | Camp | What it means | Where memory comes from | Competes with us? |
22
+ |---|---|---|---|
23
+ | **Transcript readers** | Index the JSONL/SQLite stores the agents already wrote | Passive — reads disk | **Yes, directly** |
24
+ | **Hook capturers** | Install lifecycle hooks, compress with an LLM, re-inject | Passive, but must live inside the agent | Yes, on the same users |
25
+ | **Memory SDKs** | Developer calls `add()` / `search()` | Active — the developer writes it | No — different product |
26
+ | **Observability** | OTel/proxy traces of model calls | Passive, at the API layer | Only on analytics |
27
+
28
+ mem-house is a transcript reader with the observability camp's analytics attached.
29
+ That combination is the most defensible thing about the product's *shape*; the
30
+ individual pieces are not scarce.
31
+
32
+ ---
33
+
34
+ ## The four bets, graded
35
+
36
+ ### Bet 1 — Parse on the client. **Occupied. Not differentiated.**
37
+
38
+ The reasoning in `DESIGN.md` is sound and the conclusion is correct — sqlite-backed
39
+ editors do force client-side parsing. But it is no longer a distinguishing choice.
40
+ At least five projects crack the same on-disk stores today, and one of them cracks
41
+ formats we do not touch (encrypted Protobuf sidecars for Antigravity CLI, Markdown
42
+ for aider).
43
+
44
+ Editor coverage — the number we lead the README with — is mid-pack:
45
+
46
+ | Project | Editors | Note |
47
+ |---|---:|---|
48
+ | AgentsView | **50+** | each with documented on-disk paths |
49
+ | lean-ctx | 30+ | claimed |
50
+ | CCHV | 28 | each with documented paths |
51
+ | **mem-house** | **17** | |
52
+ | deja-vu | 17 | different 17 — has Cline, aider, Qwen Code, Kimi Code, Roo Code |
53
+ | coding_agent_session_search | 11+ | |
54
+ | ctx | 8 | |
55
+ | Conare | 7 | |
56
+ | Contextify | 2 | Claude Code + Codex CLI only |
57
+
58
+ deja-vu's tagline is our pitch verbatim: *"it indexes the sessions your coding agents
59
+ already wrote to disk, months of history from before you installed it, across
60
+ seventeen harnesses."*
61
+
62
+ ### Bet 2 — Typed common schema. **Occupied on outcome, distinct on method.**
63
+
64
+ Everyone arrives at structured storage; the axis that still separates us is **when
65
+ the extraction happens and what survives it**.
66
+
67
+ - **SQLite + FTS5 is the category default**, reached independently by AgentsView,
68
+ Agent Sessions, claude-code-chat-explorer, claude-mem, ccrecall and mnemo.
69
+ - **ClickHouse for editor transcripts is unclaimed.** The only other ClickHouse-based
70
+ agent memory found is `clickmem` (57 stars) and it is embedded chDB, single-user,
71
+ so it inherits none of the multi-member properties. Langfuse is ClickHouse-backed,
72
+ MIT and self-hostable — but ingests API traces, not editor stores.
73
+ - **Verbatim-with-no-LLM-on-the-write-path has one serious co-traveller**:
74
+ MemPalace (58k stars in four months) sells exactly that — *"verbatim storage,
75
+ zero API calls."* We are not alone on this axis, but the company is good.
76
+
77
+ Honest caveat: at single-team scale SQLite demonstrably suffices. ClickHouse is a
78
+ bet on a scale we have not yet had to prove.
79
+
80
+ ### Bet 3 — Kernel-installable agency, own-only by absent grant. **This one holds.**
81
+
82
+ This is the finding worth acting on. Every competitor's multi-user story is a
83
+ **shared pool** or **metadata scoping**. Not one enforces isolation at the
84
+ datastore's own permission layer.
85
+
86
+ | Product | Team mechanism | Isolation |
87
+ |---|---|---|
88
+ | AgentsView | `pg push` / `pg serve` to shared PostgreSQL | **None.** Connection string is the whole boundary; `machine_name` is a label, not a fence |
89
+ | memsearch (Zilliz) | Docker + shared Milvus, documented "for team environments" | **None documented** — no RBAC, no per-user namespacing |
90
+ | mem0 | `user_id` + `agent_id` + `run_id` filters | Metadata filtering, not enforcement |
91
+ | Industry pattern | `tenant_id` column + row-level security | Policy layer, revocable by whoever owns the policy |
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 |
94
+
95
+ Bet 3 is not a niche preference. It is a category-wide blind spot, and it is the
96
+ only thing in this document that nobody else is attempting.
97
+
98
+ ### Bet 4 — Borrowed UI, zero fork. **Occupied. Not differentiated.**
99
+
100
+ Analytics over the same files is a crowded, mature niche: ccusage (17.8k stars),
101
+ CodexBar (19.8k). Agent Sessions beats us on the analytics *idea* with a per-session
102
+ "Quota Meter / Session Runway" priced per model against 5-hour and weekly windows.
103
+ AgentsView ships cache-aware, LiteLLM-priced cost reporting plus a REST usage API.
104
+
105
+ Zero-fork was the right call for build cost. It buys no position.
106
+
107
+ ---
108
+
109
+ ## The field, ranked by how directly it competes
110
+
111
+ | # | Project | What it is | Source | Store | Team | Editors | Licence | Signal (2026-08-07) |
112
+ |---|---|---|---|---|---|---:|---|---|
113
+ | 1 | **AgentsView** | Go binary + Tauri app + Docker image | reads disk | SQLite/FTS5, opt. DuckDB, opt. **PostgreSQL** | shared pool, free | 50+ | MIT | **4,723★**, 574 forks, 100 contributors, six months old, MCP server, semantic search |
114
+ | 2 | **claude-mem** | npm plugin + MCP + web viewer | **hooks** | SQLite + Chroma, opt. cloud | none | ~8 | Apache-2.0 | **89,967★**, 7,831 forks, **62,940 npm dl/mo**. The category's centre of gravity |
115
+ | 3 | **MemPalace** | local-first memory, verbatim | mixed, mining transcripts | pluggable | none | — | MIT | **58,182★ in four months**, 696 open issues, contested benchmarks |
116
+ | 4 | **deja-vu** | zero-dep Go binary, CLI + MCP + hook | reads disk | `records.bin`, no server | none — personal SSH sync | 17 | MIT | 593★, HN 131 pts, redacts secrets at index |
117
+ | 5 | **Contextify** | macOS app + CLI, cloud **and** self-hosted | reads disk | local + optional cloud | **real**: seats, roles, per-project opt-in | 2 | proprietary / FSL | $8–15/seat/mo; self-host $132/seat/yr |
118
+ | 6 | **ctx** | Rust CLI, event-level provenance | reads disk | local SQLite | **ctx cloud**, hosted, private beta | 8 | Apache-2.0 | 1,013★ |
119
+ | 7 | **SpecStory** | IDE extensions + CLI | **writes its own** | local + cloud | team rollups | 9 | Apache-2.0 | 1,296★; **Team $300/mo**; "Lore" mines history into installable skills |
120
+ | 8 | **Conare** | CLI + hosted, MCP | reads disk | vendor cloud | shared memory, audit logs, SSO | 7 | proprietary | **Team $500/mo** |
121
+ | 9 | **memsearch** | Markdown + Milvus shadow index | Stop hooks | Markdown truth + Milvus | Docker, no isolation | 4 | MIT | 2,431★, Zilliz-backed |
122
+ | 10 | **CCHV** | Tauri app + **headless server** | reads disk | provider files + cache | server mode w/ accounts | 28 | MIT | 2,020★, 45 contributors |
123
+ | 11 | **Agent Sessions** | native macOS app | reads disk | SQLite/FTS5 | none | 10 | MIT | 762★; best-in-class quota metering; resume + write-back restore |
124
+ | 12 | **ContextPool** | CLI + MCP | reads disk | local + repo dir | shared pool, $7.99/mo | 4 | mixed | 188 PH upvotes |
125
+
126
+ Adjacent and **not** competitors, but they own the vocabulary and the capital:
127
+ mem0 (62.8k★, $24M), cognee (29.8k★), graphiti (29.6k★), supermemory (28.8k★),
128
+ letta (24.1k★, $10M seed), memori (15.7k★), OpenViking (28.1k★, ByteDance),
129
+ Hindsight (19.2k★, $3.6M seed). All of them require the developer to write the
130
+ memory. None ingest agent sessions.
131
+
132
+ ---
133
+
134
+ ## What they do better
135
+
136
+ - **Distribution.** mem-house: 0 stars, 655 npm downloads/month, no HN or search
137
+ presence. claude-mem: 89,967 stars and 62,940 downloads/month, free, no company.
138
+ This dominates every other consideration in the document.
139
+ - **Automatic recall.** deja-vu installs a SessionStart hook; claude-mem injects into
140
+ future sessions; ContextPool loads at session start; SpecStory converts history
141
+ into *installed skills*. Our skills must be chosen. One independent analysis of 270
142
+ real sessions found **only 4.8% involved an explicit history query** — a pull-only
143
+ model gets invoked rarely.
144
+ - **Install weight — we are the heaviest in the category.** AgentsView, deja-vu and
145
+ ctx are single dependency-free binaries via Homebrew, Docker, `go install` or curl.
146
+ We are Node, need `--allow-scripts=better-sqlite3` (five adapters silently read
147
+ nothing without it), and additionally need a ClickHouse to point at. Our README
148
+ spends paragraphs on `EACCES`/`sudo` triage. That is a real adoption tax.
149
+ - **Semantic search.** AgentsView (opt-in index), claude-mem (Chroma),
150
+ claude-history (local embedding model). We are FTS-only. Contested rather than
151
+ settled — deja-vu made the same choice and was criticised for it on HN.
152
+ - **Secret redaction at ingest.** deja-vu redacts credential patterns on index *and*
153
+ on sync export. Whether we do is *unverified* — and it matters more here than
154
+ elsewhere, because Claude Code caches unredacted secrets in transcripts
155
+ (anthropics/claude-code#43675) and we ship those rows to a shared server.
156
+ - **Resume and write-back.** Agent Sessions copies resume commands for six CLIs and
157
+ restores into the archive; claude-history resumes *and forks* worktree-aware. We
158
+ read but do not hand the pilot back in.
159
+ - **Price ceiling.** AgentsView gives the whole stack away under MIT. That caps what
160
+ a self-hosted OSS competitor can charge for.
161
+
162
+ ---
163
+
164
+ ## Unclaimed ground
165
+
166
+ Two positions nobody occupies, both reachable from where we already are.
167
+
168
+ **1. Evidence and claims as two layers.** The sharpest architectural critique found
169
+ anywhere in the survey, and no product in the field claims to do it: keep the capture
170
+ log and the memory layer distinct. The capture log is chronological evidence; memory
171
+ is a set of derived, revisable claims. Each claim cites the exact spans supporting
172
+ it, records when it was inferred, and marks itself current, disputed or superseded —
173
+ and deletion follows that lineage. This is available **only** to a system that keeps
174
+ verbatim transcripts with provenance. Every extract-only competitor has discarded the
175
+ evidence by the time the claim is written. We and MemPalace are the two systems
176
+ positioned to build it. Neither has.
177
+
178
+ **2. The 30-day deletion window.** Claude Code deletes JSONL transcripts after 30
179
+ days. Contextify was built because of it; vibe-replay documents it; **nobody markets
180
+ on it.** A durable server-side store is the strongest possible answer, and we are
181
+ better positioned than any local-index tool. This is free ground.
182
+
183
+ ---
184
+
185
+ ## Three cautions
186
+
187
+ **Do not compete on benchmark scores.** The benchmarks are broken. An independent
188
+ LOCOMO audit found 99 score-corrupting errors in 1,540 questions (6.4%), and the
189
+ standard LLM judge accepted 62.81% of adversarially-generated wrong-but-topical
190
+ answers. Nine vendors claim mutually incompatible SOTA on it. MemPalace's claimed
191
+ 100% was reverse-engineered publicly to 98.4%, achieved with `top-k=50` — which
192
+ exceeds the session count. deja-vu's 84.9% hit@1 carries the same asterisk. Entering
193
+ this contest means measuring the field against itself.
194
+
195
+ **Stars are not traction, and are farmed in this category.** `mindverse/Second-Me`
196
+ holds 15,648 stars and has been dead for ten months. MemPalace took 58k stars in four
197
+ months while accumulating 2,183 issues, one titled *"in short: This sucks shit."*
198
+ Screenpipe reportedly mails its stargazers.
199
+
200
+ **The free incumbent sets the floor.** claude-mem is Apache-2.0, free, no cloud tier,
201
+ no company, one dominant author. Any paid or heavier-install offering is measured
202
+ against that baseline, not against a vendor's price list.
203
+
204
+ ---
205
+
206
+ ## Net position
207
+
208
+ Not differentiated on parse-on-client, editor breadth, self-hosting, analytics, or
209
+ agent-facing skills — all matched or beaten, most comprehensively by AgentsView
210
+ (MIT, free, 50+ editors, 4,723 stars, six months old).
211
+
212
+ Two things hold: **grant-enforced per-member isolation**, which nobody attempts, and
213
+ **verbatim transcripts with provenance**, which is the prerequisite for the
214
+ evidence/claims separation the field has identified but not built.
215
+
216
+ The liabilities are zero distribution and the heaviest install in the category.
217
+ Both are fixable; neither is fixed by adding another editor adapter.
@@ -2,11 +2,16 @@
2
2
 
3
3
  **mem-house** 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
- shipped to one typed ClickHouse store, shareable with a team, installable on the
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.
7
7
 
8
+ **One house, a set of rooms per member** — `sessions_<m>`, `messages_<m>`,
9
+ `tool_calls_<m>`. All of one member's machines ship into that member's rooms, told
10
+ apart by the `host` column; no member writes into another's. The shared-table layout
11
+ this once had was removed in 0.4.0; see `per-member/`.
12
+
8
13
  **Agency, precisely.** In constellation terms (`../TERMINOLOGY.md`) mem-house is a
9
- **house** — the `memhouse` database — **plus a resident**: the shipper. That pairing
14
+ **house** — the `mem` database — **plus a resident**: the shipper. That pairing
10
15
  is what the word *agency* means; a house on its own only holds.
11
16
 
12
17
  The test is mechanical — **residents write, routines read**. The shipper fires on a
@@ -18,27 +23,34 @@ kind = "worker" # deterministic code, no LLM
18
23
  on = "loop" # a daemon — not an insert trigger, not a schedule
19
24
  ```
20
25
 
21
- `sessions_v` is a plain view: computed during your query, for your query, writing
22
- nothing. It is a **routine** — house machinery, not a second resident. mem-house has
23
- **no materialized views at all**, so the shipper is not merely a resident, it is the
24
- only candidate in the tree. Strip it and every remaining moving part is a routine
25
- over rows nobody is writing any more.
26
+ The session rollup is a **routine** — house machinery, not a second resident. Since
27
+ 0.4.0 it is not even an object: it is SQL text (`per-member/rooms.js`), substituted with
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
30
+ — so the shipper is not merely a resident, it is the only candidate in the tree. Strip
31
+ it and every remaining moving part is a routine over rows nobody is writing any more.
26
32
 
27
33
  Note that `user_id MATERIALIZED currentUser()` is a materialized *column*: it computes
28
- during your own insert and is part of the table's definition. It makes own-only RLS
29
- work and it records who wrote each row, but it is an identity property, not what makes
30
- this an agency.
34
+ during your own insert and is part of the table's definition. It is not what isolates
35
+ members — the room they hold no grant on does that — but it records who wrote each row,
36
+ which keeps provenance across a share and keeps the `Merge` rooms meaningful. An identity
37
+ property, not what makes this an agency.
31
38
 
32
39
  **Positioning:** an alternative agency **competing with memory-house**. It borrows
33
- memory-house's proven ideas (server-stamped identity, own-only RLS, idempotent
34
- shipping, skills/plugin delivery) and agentlytics' proven assets (adapters, UI,
35
- cost engine). If it wins, it can become memory-house v4; until then the two run
36
- side by side as separate agencies, each in its own house. Naming: the product is
37
- **mem-house**; ClickHouse identifiers use **`memhouse`** (agency/house name, owner
38
- `memhouse_root`) since identifiers can't carry a dash.
40
+ memory-house's proven ideas (server-stamped identity, idempotent shipping,
41
+ skills/plugin delivery) and agentlytics' proven assets (adapters, UI, cost engine) —
42
+ but **not** its shared-table-plus-row-policy layout, which 0.4.0 removed in favour of
43
+ one set of rooms per member. If it wins, it can become memory-house v4; until then the
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
46
+ (identifiers can't carry a dash; the owner kept the longer name, `memhouse_root`).
39
47
 
40
48
  ## The four bets
41
49
 
50
+ `COMPETITION.md` grades each of these against the field as surveyed 2026-08-07.
51
+ Short version: bets 1, 2 and 4 are occupied by shipping competitors, several of
52
+ them free and further along; **bet 3 is the one nobody else attempts.**
53
+
42
54
  1. **Parse on the client.** memory-house ships raw transcript lines and parses in
43
55
  views — workable only because its 4 agents store sessions as JSON/JSONL files.
44
56
  agentlytics' breadth includes sqlite-backed editors (Cursor, VS Code, Zed,
@@ -48,19 +60,20 @@ side by side as separate agencies, each in its own house. Naming: the product is
48
60
  write axis this bet is precisely *moving work from routine to resident*:
49
61
  memory-house parses when you query, mem-house parses before anyone asks and
50
62
  writes the result down.
51
- 2. **Typed common schema** (`schema.sql`). Physical typed columns (what
52
- memory-house derives in views) + `tool_calls` (memory-house has none) + one
53
- `extra JSON` escape hatch per table so unnormalized adapter fields are never
54
- lost. `ReplacingMergeTree(ingested_at)`; `messages` keyed `(session_id, seq)` so
55
- a re-ship of a grown/changed session replaces stale rows (latest-wins). FTS text
56
- indexes built in (CH ≥ 26.2).
63
+ 2. **Typed common schema** (`per-member/schema-member.sql.tpl`, applied once per
64
+ member; `schema-merge.sql.tpl` for the team rooms). Physical typed columns (what
65
+ memory-house derives in views) + `tool_calls_<m>` (memory-house has no tool table)
66
+ + one `extra JSON` escape hatch per room so unnormalized adapter fields are never
67
+ lost. `ReplacingMergeTree(ingested_at)`; `messages_<m>` keyed
68
+ `(session_id, user_id, seq)` so a re-ship of a grown/changed session replaces stale
69
+ rows (latest-wins). FTS text indexes built in (CH ≥ 26.2).
57
70
  3. **Kernel-installable agency.** Same install path proven for agentlytics-agency:
58
71
  `install-agency{memhouse}` → house + `memhouse_root` + credential; members via
59
- `register-member` + owner `GRANT`; own-only visibility via the row policy
60
- (`rls.sql`) applied by the ego (the `kernel` user) or the mayor. The shipper is
72
+ `register-member` + owner `GRANT` on that member's own rooms. Own-only visibility
73
+ needs no policy: a member is granted their rooms and nobody else's. The shipper is
61
74
  the resident that lands with it. Identity is `user_id MATERIALIZED currentUser()`
62
- (requires `async_insert=0`). Sharing modes: own-only (policy TO member) or
63
- team-pool (no policy).
75
+ (requires `async_insert=0`). Sharing is a further GRANT, issued by the member
76
+ themselves (grant-option) or the owner.
64
77
  4. **Borrowed UI, zero fork.** The React SPA consumes REST JSON, not tables. The
65
78
  mem-house server implements the **same `/api/*` contract** as agentlytics'
66
79
  `server.js` (same routes, same response shapes) over the mem-house schema, and
@@ -70,11 +83,13 @@ side by side as separate agencies, each in its own house. Naming: the product is
70
83
 
71
84
  | Path | What | Notes |
72
85
  |---|---|---|
73
- | `schema.sql` | the house schema (typed) | unqualified names; owner applies in its house |
74
- | `rls.sql` | own-only row policies | the ego (`kernel`) or the mayor applies (owner lacks ACCESS MANAGEMENT) |
86
+ | `per-member/rooms.js` | room-name resolution + the session rollup | one naming rule for both transports |
87
+ | `per-member/schema-member.sql.tpl` | one member's three rooms (typed) | the owner via `provision.js`, or the member on a house they own |
88
+ | `per-member/schema-merge.sql.tpl` | the three team Merge rooms | owner-managed; reduce to the caller's grants |
75
89
  | `shipper/ship.js` | parse-on-client shipper CLI — the resident (`worker`) | reuses `../../editors`; incremental; idempotent |
76
90
  | `server/server.js` | REST API + dashboard | same API contract as agentlytics; serves `../../public` |
77
91
  | `delivery/` | delivery kit | installer, skills, plugin, AGENT-INSTALL.md, prompt |
92
+ | `COMPETITION.md` | the competitive landscape, graded against the four bets | surveyed 2026-08-07; figures decay, re-run before citing |
78
93
 
79
94
  ## Environment contract
80
95
 
@@ -83,7 +98,7 @@ side by side as separate agencies, each in its own house. Naming: the product is
83
98
  | `MEMHOUSE_URL` | `http://localhost:8123` | ClickHouse HTTP(S) endpoint |
84
99
  | `MEMHOUSE_USER` | `memhouse_root` | CH user (owner, or a member's own credential) |
85
100
  | `MEMHOUSE_PASSWORD` | *(empty)* | credential |
86
- | `MEMHOUSE_DB` | `memhouse` | the house |
101
+ | `MEMHOUSE_DB` | `mem` | the house |
87
102
  | `MEMHOUSE_PORT` | `4640` | dashboard/API port |
88
103
 
89
104
  Config file: `~/.memhouse/config.json` (`hiddenProjects`, future prefs).
@@ -102,6 +117,15 @@ Config file: `~/.memhouse/config.json` (`hiddenProjects`, future prefs).
102
117
  - Incremental shipping: read existing `(session_id, last_updated_at, message_count)`
103
118
  (FINAL) and skip chats that haven't grown/changed; a full re-ship must remain
104
119
  safe (ReplacingMergeTree collapses).
120
+ - Re-shipping a known session **deletes its message/tool rows first**: `ReplacingMergeTree`
121
+ collapses same-key rows but cannot remove a row the new parse no longer produces, so a
122
+ shrunken re-parse would leave a stale `seq` tail forever.
123
+ - **A mutation predicate must bind the user, never call `currentUser()`.** A mutation does
124
+ not evaluate it in the caller's context: measured on ClickHouse 25.11 and chdb, the same
125
+ `DELETE` matched 0 rows with `user_id = currentUser()` and 2000 with the value bound —
126
+ silently, both times. Read the identity once (`SELECT currentUser()`) and pass it as a
127
+ query parameter. This applies to `DELETE`/`ALTER … UPDATE`, not to `SELECT`, where
128
+ `currentUser()` behaves as expected.
105
129
  - Costs are computed in the server from `pricing.js` (repo root) over per-model
106
130
  token sums; `<synthetic>` and empty models excluded from model lists.
107
131
 
@@ -114,6 +138,11 @@ to use mem-house as memory), `kernel-install.md` (provision as agency `memhouse`
114
138
 
115
139
  ## Non-goals (v0)
116
140
 
117
- Relay/team-server (the kernel + RLS already covers sharing), launchd daemonization
118
- (ship `--loop` instead), migration tooling from memory-house's `memory` db, and
119
- per-adapter true message timestamps.
141
+ Relay/team-server (per-room grants plus the `Merge` rooms already cover sharing),
142
+ migration tooling from memory-house's `memory` db, and per-adapter true message
143
+ timestamps.
144
+
145
+ OS-level daemonization **was** a non-goal — "ship `--loop` instead" — and is no longer:
146
+ `memhouse service install` writes a systemd `--user` unit or a launchd LaunchAgent. The
147
+ reason the non-goal did not survive is that `--loop` outlives the shell and nothing else,
148
+ so a machine that reboots stops shipping until somebody notices.
@@ -17,6 +17,24 @@ the install script that builds the native binding. Without it those five silentl
17
17
  return zero sessions and you ship a partial history. Verify with step 4 —
18
18
  `memhouse discover` names any adapter it had to skip.
19
19
 
20
+ On `EACCES`, decide by **who owns the prefix root**. Not the path — Homebrew's
21
+ prefix is `/usr/local` on Intel and `/opt/homebrew` on Apple Silicon and is
22
+ user-owned in both. Not the failing file either — an earlier `sudo npm` leaves
23
+ root-owned files inside a prefix that is still the user's, and judging by those
24
+ misreads it as system-managed.
25
+
26
+ ```bash
27
+ ls -ld "$(npm prefix -g)" # the prefix ROOT decides
28
+ ```
29
+
30
+ Prefix root owned by `root` → a system-managed Node; re-run as
31
+ `sudo npm install -g memhouse --allow-scripts=better-sqlite3`.
32
+
33
+ Prefix root owned by the current user → the prefix is theirs (Homebrew, fnm, nvm,
34
+ volta), so the root-owned file npm tripped on is a stray from an earlier `sudo npm`
35
+ and another `sudo` adds more. Repair that path instead:
36
+ `sudo chown -R "$(id -u):$(id -g)" <path npm named>` (same for `~/.npm`).
37
+
20
38
  From a checkout instead (contributors): `npm install --no-audit --no-fund` in the
21
39
  repo root — its `allowScripts` field already covers the binding — then substitute
22
40
  `node bin/memhouse.js` for `memhouse` below.
@@ -32,7 +50,7 @@ cat > ~/.memhouse/env <<'EOF'
32
50
  MEMHOUSE_URL=http://localhost:8123
33
51
  MEMHOUSE_USER=memhouse_root
34
52
  MEMHOUSE_PASSWORD=<credential>
35
- MEMHOUSE_DB=memhouse
53
+ MEMHOUSE_DB=mem
36
54
  EOF
37
55
  chmod 600 ~/.memhouse/env
38
56
  ```
@@ -58,11 +76,13 @@ warns by name about any adapter it had to skip — that warning is the only sign
58
76
  that step 1's `--allow-scripts` flag was missed and five editors are dark.
59
77
 
60
78
  `stats` should show one row per source (claude-code, codex, gemini-cli, …) with
61
- non-zero sessions/messages. Or verify by SQL:
79
+ non-zero sessions/messages. Or verify by SQL — rooms are named for your ClickHouse
80
+ user, and the session rollup is a saved query rather than an object, so
81
+ `memhouse sessions-query` prints it:
62
82
 
63
83
  ```bash
64
84
  curl -s -u "$MEMHOUSE_USER:$MEMHOUSE_PASSWORD" "$MEMHOUSE_URL/?database=$MEMHOUSE_DB" \
65
- --data-binary "SELECT source, count() FROM sessions_v GROUP BY source SETTINGS final=1 FORMAT PrettyCompact"
85
+ --data-binary "SELECT source, count() FROM $(memhouse sessions-query) AS c GROUP BY source SETTINGS final=1, join_use_nulls=1 FORMAT PrettyCompact"
66
86
  ```
67
87
 
68
88
  ## 5. Start the dashboard and the shipper loop
@@ -9,10 +9,13 @@ long-term conversation memory.
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
- editors), across machines, stored in a ClickHouse database called **mem-house**.
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
14
+ you hold no grant on them.
13
15
  Connection: read `~/.memhouse/env` (`MEMHOUSE_URL/USER/PASSWORD/DB`); query over
14
16
  HTTP with `curl -u "$MEMHOUSE_USER:$MEMHOUSE_PASSWORD" "$MEMHOUSE_URL/?database=$MEMHOUSE_DB"`
15
- and always add `SETTINGS final=1` to reads.
17
+ and always add `SETTINGS final=1, join_use_nulls=1` to reads — the second matters
18
+ wherever the session rollup is used (see below).
16
19
 
17
20
  **When to reach for it — before claiming ignorance.** If the user refers to past
18
21
  work that is not in your current context ("that session where…", "how did I solve
@@ -21,22 +24,32 @@ FIRST. Only say you don't know after a search comes back empty.
21
24
 
22
25
  **How to query.** Prefer the installed skills when present: `/memhouse:search`
23
26
  (full-text over messages), `/memhouse:sessions` (list/filter sessions),
24
- `/memhouse:sql` (free-form read-only SQL). Without skills, query directly:
27
+ `/memhouse:sql` (free-form read-only SQL). Without skills, query directly.
28
+
29
+ **Table names first.** Every member owns their rooms, so every table below takes your
30
+ username as a suffix — `messages_<you>`, `sessions_<you>`, `tool_calls_<you>`. There
31
+ are no unsuffixed rooms; the plain names fail with `UNKNOWN_TABLE` rather than
32
+ returning nothing. Get the suffix from `SELECT currentUser()`. **There is no
33
+ `sessions_v` object** — the rollup is a saved query over those same rooms, and
34
+ `memhouse sessions-query` prints it ready to drop into a `FROM (...) AS c`.
35
+ `all_messages` / `all_sessions` / `all_tool_calls` read across every member you hold a
36
+ grant for.
25
37
 
26
38
  - Find sessions about a topic (FTS, lowercase your terms):
27
- `SELECT DISTINCT session_id, any(project), min(ts) FROM messages
39
+ `SELECT DISTINCT session_id, any(project), min(ts) FROM messages_<you>
28
40
  WHERE hasToken(text_word, 'clickhouse') GROUP BY session_id
29
- ORDER BY 3 DESC LIMIT 10 SETTINGS final=1 FORMAT PrettyCompact`
41
+ ORDER BY 3 DESC LIMIT 10 SETTINGS final=1, join_use_nulls=1 FORMAT PrettyCompact`
30
42
  - Recent sessions: `SELECT session_id, source, project, started, first_prompt
31
- FROM sessions_v ORDER BY started DESC LIMIT 20 SETTINGS final=1`
32
- - Replay one session: `SELECT role, text FROM messages
33
- WHERE session_id = '<id>' ORDER BY seq SETTINGS final=1`
43
+ FROM $(memhouse sessions-query) AS c ORDER BY started DESC LIMIT 20 SETTINGS final=1, join_use_nulls=1`
44
+ - Replay one session: `SELECT role, text FROM messages_<you>
45
+ WHERE session_id = '<id>' ORDER BY seq SETTINGS final=1, join_use_nulls=1`
34
46
 
35
47
  **Rules.**
36
48
  - Memory is READ-ONLY for you. Never INSERT/ALTER/DROP — ingestion belongs to the
37
49
  mem-house shipper alone.
38
50
  - Quote retrieved content as *the user's past sessions*, and cite the session_id
39
51
  when the user may want to dig deeper.
40
- - Other members' sessions may be invisible to you (row-level security). An empty
41
- result means "nothing visible", not "nothing ever happened".
52
+ - Other members' sessions are invisible to you unless they have granted you their rooms.
53
+ You hold no grant on them otherwise, so naming one fails rather than returning nothing.
54
+ An empty result means "nothing visible", not "nothing ever happened".
42
55
  - Do not paste credentials from `~/.memhouse/env` into responses, commits, or logs.
@@ -3,19 +3,19 @@
3
3
  #
4
4
  # Checks node >= 20 and a reachable ClickHouse, writes ~/.memhouse/env
5
5
  # (MEMHOUSE_* vars), applies the house schema via the shipper
6
- # (`ship.js --ensure-schema`), runs a first ship, and prints how to start the
6
+ # (`ship.js --ensure-schema` mints the caller's own rooms), runs a first ship, and prints how to start the
7
7
  # dashboard and set up continuous shipping (`--loop`).
8
8
  #
9
9
  # Usage:
10
10
  # ./install.sh # interactive (prompts for missing values)
11
- # ./install.sh --url http://localhost:8123 --user memhouse_root --password 'pw' --db memhouse
11
+ # ./install.sh --url http://localhost:8123 --user memhouse_root --password 'pw' --db mem
12
12
  # ./install.sh --yes # non-interactive, defaults / env / existing env file
13
13
  #
14
14
  # Flags:
15
15
  # --url URL ClickHouse HTTP(S) endpoint (default http://localhost:8123)
16
16
  # --user USER ClickHouse user (default memhouse_root; 'default' works for a local CH)
17
17
  # --password PW credential (default empty)
18
- # --db DB the house (database) (default memhouse)
18
+ # --db DB the house (database) (default mem)
19
19
  # --port PORT dashboard/API port (default 4640)
20
20
  # --yes never prompt; take flags > current env > ~/.memhouse/env > defaults
21
21
  # --no-ship stop after writing env + ensure-schema (skip the first ship)
@@ -43,7 +43,7 @@ fi
43
43
  CH_URL="${MEMHOUSE_URL:-http://localhost:8123}"
44
44
  CH_USER="${MEMHOUSE_USER:-memhouse_root}"
45
45
  CH_PASSWORD="${MEMHOUSE_PASSWORD:-}"
46
- CH_DB="${MEMHOUSE_DB:-memhouse}"
46
+ CH_DB="${MEMHOUSE_DB:-mem}"
47
47
  DASH_PORT="${MEMHOUSE_PORT:-4640}"
48
48
 
49
49
  ASSUME_YES=0
@@ -103,7 +103,7 @@ ONE="$(curl -sS --connect-timeout 5 --fail-with-body \
103
103
  [ "$ONE" = "1" ] || die "unexpected reply from $CH_URL: $ONE"
104
104
  info "ClickHouse ok"
105
105
 
106
- # Ensure the house exists before ensure-schema (schema.sql is unqualified and the
106
+ # Ensure the house exists before ensure-schema (the room DDL is unqualified and the
107
107
  # shipper binds to MEMHOUSE_DB — on a virgin standalone server the database must
108
108
  # be created first). On a kernel realm the house is already provisioned; if the
109
109
  # user lacks CREATE DATABASE but the house is reachable, that's fine too.
@@ -174,8 +174,10 @@ Next steps:
174
174
  # -> installs memhouse-search / memhouse-sessions / memhouse-sql into
175
175
  # \${CLAUDE_CONFIG_DIR:-\$HOME/.claude}/skills (loads next session)
176
176
 
177
- Verify anytime:
177
+ Verify anytime (rooms are named for your ClickHouse user):
178
178
  set -a; . "$ENV_FILE"; set +a
179
+ ME=\$(curl -sS --user "\$MEMHOUSE_USER:\$MEMHOUSE_PASSWORD" \\
180
+ --data-binary "SELECT currentUser() FORMAT TabSeparated" "\$MEMHOUSE_URL/")
179
181
  curl -sS --user "\$MEMHOUSE_USER:\$MEMHOUSE_PASSWORD" --data-binary \\
180
- "SELECT count() FROM sessions" "\$MEMHOUSE_URL/?database=\$MEMHOUSE_DB&final=1"
182
+ "SELECT count() FROM sessions_\$ME" "\$MEMHOUSE_URL/?database=\$MEMHOUSE_DB&final=1"
181
183
  EOF