memhouse 0.9.0 → 0.11.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 +56 -16
- package/bin/memhouse.js +969 -64
- package/memhouse/DESIGN.md +37 -12
- package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +10 -2
- package/memhouse/delivery/AGENT-INSTALL.md +41 -19
- package/memhouse/delivery/PROMPT.md +37 -13
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +14 -5
- package/memhouse/delivery/plugin/skills/ask/SKILL.md +112 -0
- package/memhouse/delivery/plugin/skills/hello/SKILL.md +76 -0
- package/memhouse/delivery/plugin/skills/invite/SKILL.md +65 -0
- package/memhouse/delivery/plugin/skills/search/SKILL.md +1 -1
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +3 -3
- package/memhouse/delivery/plugin/skills/share/SKILL.md +130 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +34 -11
- package/memhouse/delivery/plugin/skills/status/SKILL.md +114 -0
- package/memhouse/delivery/plugin/skills/users/SKILL.md +97 -0
- package/memhouse/house/HOUSE.md +40 -5
- package/memhouse/house/house.js +180 -4
- package/memhouse/house/migrate.js +277 -0
- package/memhouse/house/migrations/0100-epoch-key.js +68 -0
- package/memhouse/house/schema.sql.tpl +96 -25
- package/memhouse/service.js +7 -2
- package/memhouse/shipper/ship.js +468 -125
- package/package.json +2 -1
package/memhouse/DESIGN.md
CHANGED
|
@@ -64,9 +64,10 @@ them free and further along; **bet 3 is the one nobody else attempts.**
|
|
|
64
64
|
Physical typed columns (what memory-house derives in views) + `tool_calls`
|
|
65
65
|
(memory-house has no tool table) + one `extra JSON` escape hatch per room so
|
|
66
66
|
unnormalized adapter fields are never lost. `ReplacingMergeTree(ingested_at)`;
|
|
67
|
-
`messages` keyed `(session_id, user_id, origin, seq)` so a re-ship of a
|
|
68
|
-
grown
|
|
69
|
-
|
|
67
|
+
`messages` keyed `(session_id, user_id, origin, epoch, seq)` so a re-ship of a
|
|
68
|
+
grown session replaces stale rows (latest-wins), two housemates' rows never
|
|
69
|
+
collapse into one, and a re-parse that SHRANK or was rewritten lands under a new
|
|
70
|
+
epoch instead of over what it replaces. FTS text indexes built in (CH ≥ 26.2).
|
|
70
71
|
3. **Kernel-installable agency.** Joining any ClickHouse — a kernel's included — is a
|
|
71
72
|
database plus a credential: `CREATE USER`, `GRANT ALL ON <house>.* `, done. The
|
|
72
73
|
shipper is the resident that lands with it and creates its own rooms. Identity is
|
|
@@ -116,15 +117,39 @@ Config file: `~/.memhouse/config.json` (`hiddenProjects`, future prefs).
|
|
|
116
117
|
- Incremental shipping: read existing `(session_id, last_updated_at, message_count)`
|
|
117
118
|
(FINAL) and skip chats that haven't grown/changed; a full re-ship must remain
|
|
118
119
|
safe (ReplacingMergeTree collapses).
|
|
119
|
-
-
|
|
120
|
-
collapses same-key rows but cannot remove a row the new parse no
|
|
121
|
-
shrunken re-parse
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
- **The shipper is insert-only, and nothing may reintroduce a destructive verb.**
|
|
121
|
+
`ReplacingMergeTree` collapses same-key rows but cannot remove a row the new parse no
|
|
122
|
+
longer produces, so a shrunken re-parse leaves a stale `seq` tail. The shipper used to
|
|
123
|
+
clear the session's rows first; that destroyed content held nowhere else, because
|
|
124
|
+
Claude Code compacts transcripts and deletes them after `cleanupPeriodDays` (30 by
|
|
125
|
+
default), and a shorter re-parse cannot be told apart from a fixed adapter bug. Instead
|
|
126
|
+
`decideEpoch` bumps the session's `epoch` when the new parse is shorter than the stored
|
|
127
|
+
one, or diverges from it at an overlapping `seq` (compared on the stored `line_hash`),
|
|
128
|
+
and writes underneath the old parse rather than over it.
|
|
129
|
+
- **memhouse is an accumulator, not a mirror.** Absence of a session on disk is never a
|
|
130
|
+
signal: retention cleanup, a manual delete, a second laptop, an unmounted folder, a
|
|
131
|
+
locked SQLite file and a thrown adapter all produce the identical observation. A
|
|
132
|
+
session that disappears is never cleared and never re-inserted. **No `sync` and no
|
|
133
|
+
`prune` feature may be built here** — either would destroy the archive by design.
|
|
134
|
+
- Reads see one parse per session. `roomNames()` resolves `messages`/`tool_calls` to a
|
|
135
|
+
current-epoch subquery and the bare tables only as `*_raw`, so retention cannot silently
|
|
136
|
+
inflate a rollup, and a read path written by someone who has never heard of epochs is
|
|
137
|
+
still correct. Measured cost of the filter: **+10 ms** across the dashboard's queries on
|
|
138
|
+
a real 66k-message house (all of them under 200 ms), and **+13–37 ms** on a synthetic
|
|
139
|
+
1M-message / 20k-session one. The single shape that grows is a one-session read
|
|
140
|
+
(5 ms → 18 ms at 1M rows): the `IN` aggregates the whole room regardless of the outer
|
|
141
|
+
session filter. If that ever matters, the answer is a small materialized current-epoch
|
|
142
|
+
table maintained by the shipper — never a filter each caller has to remember.
|
|
143
|
+
- **A mutation predicate must bind the user, never call `currentUser()`.** No mutation
|
|
144
|
+
survives in the shipper, but the finding outlives it — `migrate-rooms` and `reset` still
|
|
145
|
+
run statements where it applies. A mutation does not evaluate `currentUser()` in the
|
|
146
|
+
caller's context: measured on ClickHouse 25.11 and chdb, the same `DELETE` matched 0
|
|
147
|
+
rows with `user_id = currentUser()` and 2000 with the value bound — silently, both
|
|
148
|
+
times. Read the identity once (`SELECT currentUser()`) and pass it as a query parameter.
|
|
149
|
+
The same trap bites the other way in a rebuild: `user_id` is `MATERIALIZED
|
|
150
|
+
currentUser()`, so an `INSERT SELECT` that does not carry it explicitly (under
|
|
151
|
+
`insert_allow_materialized_columns=1`) restamps every copied row with whoever ran the
|
|
152
|
+
migration.
|
|
128
153
|
- Costs are computed in the server from `pricing.js` (repo root) over per-model
|
|
129
154
|
token sums; `<synthetic>` and empty models excluded from model lists.
|
|
130
155
|
|
|
@@ -42,8 +42,14 @@ already exist.
|
|
|
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.
|
|
45
|
-
-
|
|
46
|
-
`
|
|
45
|
+
- The house's rooms are three PLAIN, SHARED tables — `sessions`, `messages`,
|
|
46
|
+
`tool_calls` — in the connection's database. No suffixes, no per-member tables.
|
|
47
|
+
Direct reads of `messages`/`tool_calls` must filter to the current parse (the shipper
|
|
48
|
+
retains superseded parses under an `epoch` column); the installed skills carry the
|
|
49
|
+
filter, so prefer them for queries.
|
|
50
|
+
- If `ship` refuses with "wrong sorting key … memhouse migrate", the house was built by
|
|
51
|
+
an older memhouse. Run `memhouse migrate` — it copies, swaps atomically, deletes
|
|
52
|
+
nothing, and shipping resumes on its own. Do NOT try to rebuild tables by hand.
|
|
47
53
|
|
|
48
54
|
## When you are done
|
|
49
55
|
|
|
@@ -52,6 +58,8 @@ mark. Then tell them:
|
|
|
52
58
|
|
|
53
59
|
- where the dashboard is (`http://localhost:{{PORT}}`) and that `memhouse start` runs it
|
|
54
60
|
- that `memhouse search <terms>` searches every past session from every editor
|
|
61
|
+
- that `memhouse plugins install claude` gives their agents /mem:ask, /mem:search,
|
|
62
|
+
/mem:sessions, /mem:share, /mem:sql, /mem:status, /mem:users — offer to run it
|
|
55
63
|
- that you can now search their history yourself, and offer to try one query
|
|
56
64
|
|
|
57
65
|
If `doctor` reports a failure, say which line failed and what memhouse suggested. Do not
|
|
@@ -45,28 +45,31 @@ From a checkout instead (contributors): `npm install --no-audit --no-fund` in th
|
|
|
45
45
|
repo root — nothing compiles — then substitute `node bin/memhouse.js` for `memhouse`
|
|
46
46
|
below.
|
|
47
47
|
|
|
48
|
-
## 2. Configure the
|
|
48
|
+
## 2 + 3. Configure, create the schema, first ship — one command
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
Do NOT hand-write `~/.memhouse/env`. `install` validates the connection, builds the
|
|
51
|
+
rooms, ships, and writes the config LAST — a hand-written file is read as truth by
|
|
52
|
+
every later command even when it is wrong, and an example URL pasted verbatim points
|
|
53
|
+
at somebody's real house.
|
|
54
|
+
|
|
55
|
+
With a credential the user already has on the house:
|
|
52
56
|
|
|
53
57
|
```bash
|
|
54
|
-
|
|
55
|
-
cat > ~/.memhouse/env <<'EOF'
|
|
56
|
-
MEMHOUSE_URL=http://localhost:8123
|
|
57
|
-
MEMHOUSE_USER=memhouse_root
|
|
58
|
-
MEMHOUSE_PASSWORD=<credential>
|
|
59
|
-
MEMHOUSE_DB=mem
|
|
60
|
-
EOF
|
|
61
|
-
chmod 600 ~/.memhouse/env
|
|
58
|
+
memhouse install --url <house-url> --user <member> --password '<credential>' --db <house>
|
|
62
59
|
```
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
Or, holding an admin credential, mint the member and the house in one go:
|
|
65
62
|
|
|
66
63
|
```bash
|
|
67
|
-
memhouse install --
|
|
64
|
+
memhouse install --url <house-url> \
|
|
65
|
+
--admin-user <admin> --admin-password '<admin-credential>' \
|
|
66
|
+
--db <house> --member <member> --member-password '<credential>'
|
|
68
67
|
```
|
|
69
68
|
|
|
69
|
+
The admin credential is used once and never stored. Ask the user for values; never
|
|
70
|
+
invent them and never default to localhost:8123 — on many machines that is a real house
|
|
71
|
+
belonging to someone else.
|
|
72
|
+
|
|
70
73
|
Expected output shape: `[memhouse] shipped N sessions (0 skipped) → M msg rows, T
|
|
71
74
|
tool rows in Xs` with N in the hundreds on a machine with real agent usage.
|
|
72
75
|
|
|
@@ -84,9 +87,11 @@ no longer exists, so anything named here is a single editor's own store (locked
|
|
|
84
87
|
running editor, corrupt, or on a schema this parser does not know).
|
|
85
88
|
|
|
86
89
|
`stats` should show one row per source (claude-code, codex, gemini-cli, …) with
|
|
87
|
-
non-zero sessions/messages. Or verify by SQL — rooms are
|
|
88
|
-
|
|
89
|
-
`memhouse sessions-query` prints it
|
|
90
|
+
non-zero sessions/messages. Or verify by SQL — the rooms are plain shared tables
|
|
91
|
+
(`sessions`, `messages`, `tool_calls`) and the session rollup is a saved query rather
|
|
92
|
+
than an object, so `memhouse sessions-query` prints it (it also applies the
|
|
93
|
+
current-parse filter that any hand-written read of `messages` needs — the shipper
|
|
94
|
+
retains superseded parses under an `epoch`):
|
|
90
95
|
|
|
91
96
|
```bash
|
|
92
97
|
curl -s -u "$MEMHOUSE_USER:$MEMHOUSE_PASSWORD" "$MEMHOUSE_URL/?database=$MEMHOUSE_DB" \
|
|
@@ -109,9 +114,26 @@ re-ship loop, so this replaces any separate cron. `memhouse stop` ends both.
|
|
|
109
114
|
memhouse plugins install claude
|
|
110
115
|
```
|
|
111
116
|
|
|
112
|
-
Installs the memhouse plugin into `<claude-config>/skills/
|
|
113
|
-
session as `
|
|
114
|
-
and `/
|
|
117
|
+
Installs the memhouse plugin into `<claude-config>/skills/mem`, which loads next
|
|
118
|
+
session as `mem@skills-dir` and exposes `/mem:ask`, `/mem:search`, `/mem:sessions`,
|
|
119
|
+
`/mem:share`, `/mem:sql`, `/mem:status` and `/mem:users`. They read the connection from
|
|
120
|
+
`$MEMHOUSE_HOME/env` (default
|
|
115
121
|
`~/.memhouse/env`), with exported `MEMHOUSE_*` vars taking precedence, and refuse to run
|
|
116
122
|
rather than guessing a URL when neither is set. `memhouse plugins list` shows what is
|
|
117
123
|
installed, `memhouse plugins remove claude` undoes it.
|
|
124
|
+
|
|
125
|
+
## 7. Upgrading a machine that already ran memhouse < 0.10
|
|
126
|
+
|
|
127
|
+
The 0.10 schema changed the transcript tables' sorting keys, so the first `ship` against
|
|
128
|
+
an old house refuses and names the fix. Run it:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
memhouse migrate # copies each room, swaps atomically, keeps <room>_pre_epoch
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Nothing is deleted; a service-managed shipper resumes on its next pass by itself.
|
|
135
|
+
`memhouse update` performs this for the user interactively (`--migrate` runs it
|
|
136
|
+
unasked); as an agent, prefer `memhouse update --no-install --migrate` after an
|
|
137
|
+
`npm install -g` you already performed. If OTHER machines still ship into the same
|
|
138
|
+
house with an older memhouse, tell the user to upgrade them promptly — the migration
|
|
139
|
+
output explains why and prints the admin-side alternative.
|
|
@@ -9,9 +9,10 @@ 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 all of
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
editors), across all of THEIR machines, stored in a ClickHouse database called
|
|
13
|
+
**{{DB}}** (their "house"). The house outlives the transcripts on disk: editors
|
|
14
|
+
compact and delete local session files after weeks, and the house is then the only
|
|
15
|
+
copy.
|
|
15
16
|
Connection: read `{{ENV_FILE}}` (`MEMHOUSE_URL/USER/PASSWORD/DB`); query over
|
|
16
17
|
HTTP with `curl -u "$MEMHOUSE_USER:$MEMHOUSE_PASSWORD" "$MEMHOUSE_URL/?database=$MEMHOUSE_DB"`
|
|
17
18
|
and always add `SETTINGS final=1, join_use_nulls=1` to reads — the second matters
|
|
@@ -22,9 +23,12 @@ work that is not in your current context ("that session where…", "how did I so
|
|
|
22
23
|
X before", "did we ever…", "what was I working on last week"), search memhouse
|
|
23
24
|
FIRST. Only say you don't know after a search comes back empty.
|
|
24
25
|
|
|
25
|
-
**How to query.** Prefer the installed skills when present: `/
|
|
26
|
-
|
|
27
|
-
`/
|
|
26
|
+
**How to query.** Prefer the installed skills when present: `/mem:ask` (answer a
|
|
27
|
+
question from memory, with citations), `/mem:search` (full-text over messages),
|
|
28
|
+
`/mem:sessions` (list/filter sessions), `/mem:sql` (free-form read-only SQL),
|
|
29
|
+
`/mem:status` (what the system holds and its health), `/mem:share` and `/mem:users`
|
|
30
|
+
(read-only sharing between users), `/mem:invite` (mint a new member — inviting comes
|
|
31
|
+
before sharing), `/mem:hello` (introduce the system). Without skills, query directly.
|
|
28
32
|
|
|
29
33
|
**Table names.** The house's rooms are three plain, shared tables — `messages`,
|
|
30
34
|
`sessions`, `tool_calls` — in the connection's database. Everyone in the house writes
|
|
@@ -33,21 +37,41 @@ which machine. Query the whole house by default; add `WHERE user_id = '<name>'`
|
|
|
33
37
|
person. **There is no `sessions_v` object** — the rollup is a saved query, and
|
|
34
38
|
`memhouse sessions-query` prints it ready to drop into a `FROM (...) AS c`.
|
|
35
39
|
|
|
40
|
+
**One session can be stored more than once.** The shipper never deletes: when an editor
|
|
41
|
+
compacts or shortens a session, the new parse is written under a higher `epoch` and the
|
|
42
|
+
old one is retained. Every direct read of `messages`/`tool_calls` must filter to the
|
|
43
|
+
current parse, or counts double and a replay interleaves two versions of the session:
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
-- call this CUR below
|
|
47
|
+
WHERE origin != 'ship'
|
|
48
|
+
OR (session_id, user_id, epoch) IN (
|
|
49
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
50
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Drop CUR deliberately only when asked what a session said BEFORE it was compacted —
|
|
54
|
+
the retained epochs are the only place that content still exists.
|
|
55
|
+
|
|
36
56
|
- Find sessions about a topic (FTS, lowercase your terms):
|
|
37
57
|
`SELECT DISTINCT session_id, any(project), min(ts) FROM messages
|
|
38
|
-
WHERE hasToken(text_word, 'clickhouse') GROUP BY session_id
|
|
58
|
+
WHERE hasToken(text_word, 'clickhouse') AND <CUR> GROUP BY session_id
|
|
39
59
|
ORDER BY 3 DESC LIMIT 10 SETTINGS final=1, join_use_nulls=1 FORMAT PrettyCompact`
|
|
40
60
|
- Recent sessions: `SELECT session_id, source, project, started, first_prompt
|
|
41
61
|
FROM $(memhouse sessions-query) AS c ORDER BY started DESC LIMIT 20 SETTINGS final=1, join_use_nulls=1`
|
|
62
|
+
(the rollup applies the current-parse filter itself)
|
|
42
63
|
- Replay one session: `SELECT role, text FROM messages
|
|
43
|
-
WHERE session_id = '<id>' ORDER BY seq SETTINGS final=1, join_use_nulls=1`
|
|
64
|
+
WHERE session_id = '<id>' AND <CUR> ORDER BY seq SETTINGS final=1, join_use_nulls=1`
|
|
44
65
|
|
|
45
66
|
**Rules.**
|
|
46
|
-
- Memory is READ-ONLY for you
|
|
47
|
-
memhouse shipper alone.
|
|
67
|
+
- Memory is READ-ONLY for you: never INSERT/ALTER/DROP the ROOMS (sessions, messages,
|
|
68
|
+
tool_calls) — ingestion belongs to the memhouse shipper alone. The one exception is
|
|
69
|
+
membership administration through the `memhouse` CLI: `/mem:invite` mints a user and
|
|
70
|
+
`/mem:share` grants read access, both via the tool, not by you writing to the rooms.
|
|
48
71
|
- Quote retrieved content as *the user's past sessions*, and cite the session_id
|
|
49
72
|
when the user may want to dig deeper.
|
|
50
|
-
- Other
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
- Other users' HOUSES (other databases on the same server) are readable only when
|
|
74
|
+
shared with this credential (`/mem:share` on their side). Query a shared house by
|
|
75
|
+
qualified names — `polat.messages` — with the same CUR filter. An empty result
|
|
76
|
+
means "nothing visible", not "nothing ever happened".
|
|
53
77
|
- Do not paste credentials from `{{ENV_FILE}}` into responses, commits, or logs.
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: search, browse,
|
|
4
|
-
"version": "0.
|
|
5
|
-
"author": {
|
|
2
|
+
"name": "mem",
|
|
3
|
+
"description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: ask, search, browse, analyze, check, share, and invite across all 17 supported editors and machines. Skills install as /mem:hello, /mem:ask, /mem:search, /mem:sessions, /mem:share, /mem:invite, /mem:sql, /mem:status, and /mem:users.",
|
|
4
|
+
"version": "0.11.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Ramazan Polat"
|
|
7
|
+
},
|
|
6
8
|
"homepage": "https://github.com/agent-realm/memhouse",
|
|
7
|
-
"keywords": [
|
|
9
|
+
"keywords": [
|
|
10
|
+
"memory",
|
|
11
|
+
"clickhouse",
|
|
12
|
+
"search",
|
|
13
|
+
"sessions",
|
|
14
|
+
"analytics",
|
|
15
|
+
"agentlytics"
|
|
16
|
+
]
|
|
8
17
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ask
|
|
3
|
+
description: Answer a question FROM conversation memory — retrieve the relevant past sessions out of the memhouse ClickHouse store, read the actual transcripts, and synthesize an answer with citations. Use when the user asks something their past work already answered, e.g. "how did I fix X last time", "what approach did we settle on for Y", "why did we choose Z", "have I solved this error before", "what did the reviewer say about W". This is the retrieve-and-answer skill; /mem:search is find-the-session, /mem:sql is run-a-query.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "<question about past work>"
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:ask — answer from conversation memory
|
|
10
|
+
|
|
11
|
+
Answer the user's question from what the house holds, not from what you assume. The
|
|
12
|
+
house is the archive that OUTLIVES the transcripts on disk — Claude Code compacts and
|
|
13
|
+
deletes local session files, so for anything older than ~30 days memhouse is the only
|
|
14
|
+
place the answer still exists.
|
|
15
|
+
|
|
16
|
+
The synthesis is yours; the store is not. memhouse itself runs no LLM anywhere in its
|
|
17
|
+
write or read path — this skill retrieves rows and YOU read them. Never present a guess
|
|
18
|
+
as a retrieval: if the house has nothing on the question, say so.
|
|
19
|
+
|
|
20
|
+
## Connection
|
|
21
|
+
|
|
22
|
+
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no `env | grep
|
|
23
|
+
MEMHOUSE`, no `set -x` around these commands. Anything you print becomes part of a
|
|
24
|
+
transcript that memhouse itself ships into the house.
|
|
25
|
+
|
|
26
|
+
Credentials resolve as **flags > exported `MEMHOUSE_*` > `$MEMHOUSE_HOME/env`**
|
|
27
|
+
(default `~/.memhouse/env`) — the same order the `memhouse` CLI uses:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
31
|
+
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
32
|
+
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
33
|
+
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
34
|
+
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
35
|
+
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
36
|
+
echo "no memhouse house configured — nothing in $MH_ENV and no MEMHOUSE_URL/MEMHOUSE_USER set." >&2
|
|
37
|
+
echo "Run: memhouse install" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
42
|
+
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-mem}&final=1&join_use_nulls=1&readonly=1" <<'SQL'
|
|
43
|
+
<the query>
|
|
44
|
+
FORMAT JSONEachRow
|
|
45
|
+
SQL
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`readonly=1` is pinned on every request: this skill never writes.
|
|
49
|
+
|
|
50
|
+
## The method — retrieve, read, answer, cite
|
|
51
|
+
|
|
52
|
+
**Escaping, before anything touches SQL.** Search terms, session ids and user ids are
|
|
53
|
+
arbitrary text going inside single-quoted literals: escape backslashes then single
|
|
54
|
+
quotes (`\` → `\\`, `'` → `\'`) in every value you splice in. A term like `O'Reilly`
|
|
55
|
+
otherwise breaks the query — and crafted text could reshape it.
|
|
56
|
+
|
|
57
|
+
**1. Extract search terms from the question.** Not the question verbatim — the terms
|
|
58
|
+
that would appear in the ANSWER. "How did I fix the ClickHouse auth error?" → search
|
|
59
|
+
`ClickHouse auth`, `Authentication failed`, `ACCESS_DENIED` — error strings beat
|
|
60
|
+
paraphrases, identifiers beat prose. Run 2–3 term variants; the FTS columns are
|
|
61
|
+
lowercased, so lowercase your needle.
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
SELECT session_id, any(folder) AS folder,
|
|
65
|
+
formatDateTime(max(ts), '%Y-%m-%d') AS day, count() AS hits,
|
|
66
|
+
substring(anyIf(text, positionCaseInsensitive(text, 'NEEDLE') > 0), 1, 300) AS sample
|
|
67
|
+
FROM messages
|
|
68
|
+
WHERE text_ngram LIKE '%needle%'
|
|
69
|
+
GROUP BY session_id ORDER BY max(ts) DESC LIMIT 8
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**2. Read the strongest sessions — the actual turns, not just the hit line.** Pull a
|
|
73
|
+
window around the hits so you see the resolution, not the complaint:
|
|
74
|
+
|
|
75
|
+
```sql
|
|
76
|
+
SELECT seq, role, substring(text, 1, 2000) AS text
|
|
77
|
+
FROM messages
|
|
78
|
+
WHERE session_id = '<sid>' AND user_id = '<uid>'
|
|
79
|
+
AND seq BETWEEN <hit_seq - 3> AND <hit_seq + 12>
|
|
80
|
+
ORDER BY seq
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Join on `(session_id, user_id)` always — two housemates can collide on an
|
|
84
|
+
adapter-local session id.
|
|
85
|
+
|
|
86
|
+
**3. Answer, then cite.** Lead with the answer; under it list the sessions it came
|
|
87
|
+
from — `session_id`, date, project — so the user can reopen one (`memhouse resume
|
|
88
|
+
<session_id>` prints the command). Quote the decisive lines verbatim where the exact
|
|
89
|
+
wording matters (error text, a command, a config value).
|
|
90
|
+
|
|
91
|
+
**4. Nothing found is an answer too.** Say what you searched and where the house's
|
|
92
|
+
coverage ends (`SELECT min(ts), max(ts), count() FROM messages`) rather than padding.
|
|
93
|
+
|
|
94
|
+
## What can bite
|
|
95
|
+
|
|
96
|
+
- **Rows can hold more than one PARSE of a session** (`origin`/`epoch` are in the key —
|
|
97
|
+
the house retains superseded parses instead of deleting them). Counts and rollups must
|
|
98
|
+
filter to the current epoch:
|
|
99
|
+
```sql
|
|
100
|
+
WHERE origin != 'ship'
|
|
101
|
+
OR (session_id, user_id, epoch) IN (
|
|
102
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
103
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id)
|
|
104
|
+
```
|
|
105
|
+
For step 2's transcript reads, PREFER the filter as well — an unfiltered read of a
|
|
106
|
+
compacted session interleaves two parses. Drop it deliberately only when the question
|
|
107
|
+
is about what a session said BEFORE it was compacted; the superseded epochs are the
|
|
108
|
+
one place that content still exists.
|
|
109
|
+
- **`text` is truncated at 50,000 chars** and tool args at 20,000 — a missing tail is
|
|
110
|
+
truncation, not absence.
|
|
111
|
+
- The rollup (`sessions_v`) is a saved query, not a table — get it with
|
|
112
|
+
`memhouse sessions-query` if you need per-session totals.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hello
|
|
3
|
+
description: Introduce memhouse — what it is, what it remembers, and how to use it from here. Use when the user asks "what is memhouse", "what can my memory do", "how do I use this", invokes /mem:hello, or seems new to the memory system. Also the right first answer when another /mem skill fails because nothing is configured yet.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: ""
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:hello — welcome to your memory
|
|
10
|
+
|
|
11
|
+
**Invoking this skill IS the request. Give the introduction immediately — grounded in
|
|
12
|
+
this machine's actual state, not hypotheticals. Nothing to clarify first.**
|
|
13
|
+
|
|
14
|
+
## First: find out what state this machine is in
|
|
15
|
+
|
|
16
|
+
RUN this yourself with the Bash tool — do not print it for the user to run, and do not
|
|
17
|
+
show the env values it sources. Read its output, then write the introduction using the
|
|
18
|
+
ACTUAL numbers it returns (interpolate them; never emit the literal "N sessions").
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
22
|
+
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
23
|
+
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
24
|
+
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
25
|
+
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
26
|
+
if [ -n "${MEMHOUSE_URL:-}" ] && [ -n "${MEMHOUSE_USER:-}" ]; then
|
|
27
|
+
curl -sS -m 5 --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
28
|
+
--data-binary "SELECT uniqExact(session_id), count(), formatDateTime(min(ts),'%Y-%m-%d') FROM messages" \
|
|
29
|
+
"$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-mem}&final=1&readonly=1" 2>&1
|
|
30
|
+
else
|
|
31
|
+
echo "NOT_CONFIGURED"
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
(Do not print the env values, and do not narrate that you are withholding them — just use them.)
|
|
36
|
+
|
|
37
|
+
## Then introduce it — three states, three openings
|
|
38
|
+
|
|
39
|
+
**Configured and answering** — open with the numbers: "Your memory holds N sessions
|
|
40
|
+
(M messages) going back to <date> — every conversation you've had with coding agents
|
|
41
|
+
(Claude Code, Codex, Cursor, Gemini CLI and 13 more), across all your machines. It
|
|
42
|
+
outlives the transcripts on disk: editors delete their session files after weeks; this
|
|
43
|
+
doesn't." Then the tour below.
|
|
44
|
+
|
|
45
|
+
**Not configured** — say what memhouse IS (one paragraph: a personal, permanent archive
|
|
46
|
+
of every AI coding session, stored in a ClickHouse "house" you own), then how to start:
|
|
47
|
+
`memhouse onboard` (wizard) or `memhouse install --env <file>` if someone invited them
|
|
48
|
+
(the file came from `/mem:invite` on the inviter's side).
|
|
49
|
+
|
|
50
|
+
**Configured but unreachable** — say which URL refused, point at `memhouse doctor`,
|
|
51
|
+
stop. Never guess another endpoint.
|
|
52
|
+
|
|
53
|
+
## The tour (keep it to one screen)
|
|
54
|
+
|
|
55
|
+
What you can DO from this chat:
|
|
56
|
+
|
|
57
|
+
- `/mem:ask <question>` — answer from your past work, with citations ("how did I fix X
|
|
58
|
+
last time?")
|
|
59
|
+
- `/mem:search <terms>` — find the session ("that conversation about Y")
|
|
60
|
+
- `/mem:sessions` — browse and filter what's stored
|
|
61
|
+
- `/mem:sql <question or SQL>` — ad-hoc analytics (tokens, costs, rankings)
|
|
62
|
+
- `/mem:status` — is it healthy, how much is stored, which machines ship into it
|
|
63
|
+
|
|
64
|
+
And around other people — **inviting comes before sharing**:
|
|
65
|
+
|
|
66
|
+
- `/mem:invite <name>` — mint a friend's account + their own house on your ClickHouse,
|
|
67
|
+
hand them one env file; they install with it and have their own memory
|
|
68
|
+
- `/mem:share <name>` — once they exist, open a read-only window into YOUR memory
|
|
69
|
+
(`/mem:share revoke <name>` closes it); `/mem:users` shows who's around
|
|
70
|
+
|
|
71
|
+
Also worth naming: `memhouse start` serves a dashboard at http://localhost:4640 for
|
|
72
|
+
browsing and analytics outside the chat, and everything here is read-only for agents —
|
|
73
|
+
only the shipper writes.
|
|
74
|
+
|
|
75
|
+
End by offering one concrete action: a search for something they mentioned recently, or
|
|
76
|
+
`/mem:status` if they seem unsure it's working. One offer, not a menu.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: invite
|
|
3
|
+
description: Invite another person onto this memhouse ClickHouse — mint their user and their own house (database) with the right grants, producing the one env file their install needs. Use when the user says "invite X", "add X to memhouse", "get my friend set up". Inviting comes BEFORE sharing — /mem:share can only grant a user who exists.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "<name> [into <db>]"
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:invite — mint a member and hand them one file
|
|
10
|
+
|
|
11
|
+
This runs the `memhouse invite` CLI and nothing else. Inviting mints a ClickHouse user
|
|
12
|
+
and a house, which takes an ADMIN credential — and an admin password must never enter
|
|
13
|
+
this conversation, because memhouse ships the transcript into the house it just
|
|
14
|
+
provisioned. The CLI is the only safe path: the admin password is passed to one process
|
|
15
|
+
and never echoed. **Do not hand-run `CREATE USER` / `GRANT` with an admin password
|
|
16
|
+
substituted into a command — that burns the credential into the transcript on disk.**
|
|
17
|
+
|
|
18
|
+
## Do this
|
|
19
|
+
|
|
20
|
+
1. Confirm the `memhouse` binary is present: `command -v memhouse`. If it is not, tell
|
|
21
|
+
the user to install it first (`npm install -g memhouse`) — there is no in-skill
|
|
22
|
+
fallback, on purpose (see above).
|
|
23
|
+
|
|
24
|
+
2. Gather ONLY two things by asking:
|
|
25
|
+
- the invitee's **name** (validate `[A-Za-z][A-Za-z0-9_]*`; refuse otherwise)
|
|
26
|
+
- the **URL the invitee will reach the house at** — NOT localhost (that is the
|
|
27
|
+
invitee's own machine). A LAN IP, hostname, or tunnel.
|
|
28
|
+
|
|
29
|
+
Do NOT ask for an admin user or password yet — the next step usually needs neither.
|
|
30
|
+
Only ask about a **shared house** if the user brings one up; default is the invitee's
|
|
31
|
+
own house (omit --db; never pass `--db default` for "the default house").
|
|
32
|
+
|
|
33
|
+
3. TRY IT BARE FIRST — run this via the Bash tool. `memhouse invite` uses the caller's
|
|
34
|
+
OWN configured credential when it can manage users, which is the common case for
|
|
35
|
+
whoever set the house up, so no admin is needed:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
memhouse invite <name> --url <url> [--db <shared-house>]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If it prints `invite written: …`, go to step 4 — you are done, and no admin secret
|
|
42
|
+
ever touched this conversation.
|
|
43
|
+
|
|
44
|
+
ONLY if it fails with "your configured credential cannot create users" does
|
|
45
|
+
provisioning need a real admin. Do NOT take the admin password in chat — memhouse
|
|
46
|
+
archives this transcript. Hand the user the line to run THEMSELVES (a leading space
|
|
47
|
+
keeps it out of shell history):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
memhouse invite <name> --url <url> [--db <shared-house>] --admin-user <admin> --admin-password 'PASTE_IT_HERE'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
4. When it succeeds it prints `invite written: invite-<name>.env`. Relay to the user:
|
|
54
|
+
- hand `invite-<name>.env` to the invitee over a trusted channel (croc, a password
|
|
55
|
+
manager — not chat); the file IS a password
|
|
56
|
+
- the invitee runs `memhouse install --env invite-<name>.env` — it OFFERS to change
|
|
57
|
+
the password to one only they know (they were granted rotation on their own account),
|
|
58
|
+
then deletes the file; so the password you set stops working once they install
|
|
59
|
+
- once installed, sharing works: `/mem:share <name>` on either side
|
|
60
|
+
|
|
61
|
+
## Never
|
|
62
|
+
|
|
63
|
+
Never create a ClickHouse user by running raw SQL with an admin password you were given
|
|
64
|
+
in chat. Never invent an admin credential. Never suggest granting beyond what `memhouse
|
|
65
|
+
invite` does. If the binary is absent, stop and say so — do not improvise the SQL.
|
|
@@ -6,7 +6,7 @@ argument-hint: "<search terms> [in <project>] [last <N> days] [from <editor>]"
|
|
|
6
6
|
allowed-tools: Bash
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# /
|
|
9
|
+
# /mem:search — search conversation memory
|
|
10
10
|
|
|
11
11
|
Search the full message history in the memhouse house. Every room is named for your
|
|
12
12
|
ClickHouse user — `messages`, `sessions` — see **Room names** below.
|
|
@@ -6,7 +6,7 @@ argument-hint: "[N] [project <name>] [from <editor>] [today|week|month]"
|
|
|
6
6
|
allowed-tools: Bash
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# /
|
|
9
|
+
# /mem:sessions — browse session history
|
|
10
10
|
|
|
11
11
|
List/filter sessions from the session rollup — a saved query over your own rooms, not an
|
|
12
12
|
object; `memhouse sessions-query` prints it (session metadata +
|
|
@@ -154,11 +154,11 @@ FORMAT PrettyCompact
|
|
|
154
154
|
| "what was I working on yesterday?" | `WHERE ended > now() - INTERVAL 1 DAY` |
|
|
155
155
|
| "show my last 10 sessions in agentlytics" | `LIMIT 10 … WHERE project ILIKE '%agentlytics%'` |
|
|
156
156
|
| "list my cursor sessions this month" | `WHERE source = 'cursor' AND ended > now() - INTERVAL 30 DAY` |
|
|
157
|
-
| "which machine did I do the schema work on?" | search via /
|
|
157
|
+
| "which machine did I do the schema work on?" | search via /mem:search, or filter `host` here |
|
|
158
158
|
|
|
159
159
|
## Output
|
|
160
160
|
|
|
161
161
|
A compact table: when, name (or first prompt), source, host, project, msgs,
|
|
162
162
|
tokens. Offer to pull a full transcript next
|
|
163
163
|
(`SELECT role, text FROM messages WHERE session_id = '…' ORDER BY seq`) or to
|
|
164
|
-
run /
|
|
164
|
+
run /mem:search for a specific quote.
|