memhouse 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/memhouse.js +270 -22
- package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +2 -2
- package/memhouse/delivery/AGENT-INSTALL.md +2 -2
- package/memhouse/delivery/PROMPT.md +15 -9
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
- package/memhouse/delivery/plugin/reference/HOUSE.md +154 -0
- package/memhouse/delivery/plugin/skills/access/SKILL.md +148 -0
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +29 -0
- package/memhouse/delivery/plugin/skills/house/SKILL.md +99 -0
- package/memhouse/delivery/plugin/skills/recall/SKILL.md +119 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +73 -194
- package/memhouse/flags.js +1 -0
- package/memhouse/share.js +64 -0
- package/package.json +1 -1
- package/memhouse/delivery/plugin/skills/ask/SKILL.md +0 -131
- package/memhouse/delivery/plugin/skills/hello/SKILL.md +0 -121
- package/memhouse/delivery/plugin/skills/invite/SKILL.md +0 -130
- package/memhouse/delivery/plugin/skills/search/SKILL.md +0 -186
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +0 -164
- package/memhouse/delivery/plugin/skills/share/SKILL.md +0 -130
- package/memhouse/delivery/plugin/skills/status/SKILL.md +0 -139
- package/memhouse/delivery/plugin/skills/users/SKILL.md +0 -97
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# The house — connect, schema, and the three things that bite
|
|
2
|
+
|
|
3
|
+
Read this before writing any query. Every `/mem:*` skill points here rather than
|
|
4
|
+
repeating it, so there is one description of the store and it is either right everywhere
|
|
5
|
+
or wrong everywhere.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## What this is
|
|
10
|
+
|
|
11
|
+
A **house** is a ClickHouse **database**. Its **rooms** are three shared tables:
|
|
12
|
+
`sessions`, `messages`, `tool_calls`. Everyone granted on the database writes into the
|
|
13
|
+
same tables; the server stamps who. There is no per-user table and no cloud — the
|
|
14
|
+
database *is* the boundary, and a house's grants reach nothing outside it.
|
|
15
|
+
|
|
16
|
+
A **member** owns one house. An **administrator** owns the ClickHouse. `memhouse whoami`
|
|
17
|
+
says which you are holding.
|
|
18
|
+
|
|
19
|
+
Rows arrive from a **shipper** that parses local transcripts from 17 editors. It only
|
|
20
|
+
ever inserts. Nothing in memhouse runs an LLM over your data — these skills retrieve
|
|
21
|
+
rows, and *you* read them.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Connect
|
|
26
|
+
|
|
27
|
+
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no
|
|
28
|
+
`env | grep MEMHOUSE`, no `set -x`. Anything printed becomes part of a transcript
|
|
29
|
+
memhouse ships back into the house — the archive is insert-only, so a leaked credential
|
|
30
|
+
cannot be withdrawn.
|
|
31
|
+
|
|
32
|
+
Credentials resolve **flags > exported `MEMHOUSE_*` > `$MEMHOUSE_HOME/env`** (default
|
|
33
|
+
`~/.memhouse/env`) — the same order the CLI uses. The dance below snapshots the
|
|
34
|
+
environment first, because sourcing the file would otherwise clobber an already-exported
|
|
35
|
+
variable.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
39
|
+
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
40
|
+
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
41
|
+
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
42
|
+
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
43
|
+
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
44
|
+
echo "no memhouse house configured — run: memhouse install" >&2; exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
q() { # read-only by construction; every read path should use this
|
|
48
|
+
curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
49
|
+
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-$MEMHOUSE_USER}&readonly=1"
|
|
50
|
+
}
|
|
51
|
+
q <<'SQL'
|
|
52
|
+
SELECT 1 FORMAT PrettyCompact
|
|
53
|
+
SQL
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`readonly=1` is a ClickHouse-side setting: even a mis-generated `DROP` is refused by the
|
|
57
|
+
server, not by discipline.
|
|
58
|
+
|
|
59
|
+
**Reading someone else's house.** A share is a read-only `GRANT SELECT` on their
|
|
60
|
+
database. `SHOW DATABASES` lists what your credential may read; anything that is not
|
|
61
|
+
`system`, `information_schema`, `default` or your own `$MEMHOUSE_DB` was shared with you.
|
|
62
|
+
To read it, change `database=` in the URL and leave the table names bare. Say whose house
|
|
63
|
+
an answer came from.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Schema
|
|
68
|
+
|
|
69
|
+
**`messages`** — one row per turn.
|
|
70
|
+
|
|
71
|
+
| column | meaning |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `session_id` | `<source>:<adapter-local id>`, e.g. `claude-code:6b1f…` |
|
|
74
|
+
| `seq` | position in the session, from 0 |
|
|
75
|
+
| `source` | editor: `claude-code`, `codex`, `cursor`, … |
|
|
76
|
+
| `host` | the machine, `<hostname>-<8 hex>` — random per install, not derived |
|
|
77
|
+
| `ts` | when the turn happened (`DateTime64(3,'UTC')`) |
|
|
78
|
+
| `role` | `user`, `assistant`, `system` |
|
|
79
|
+
| `model`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens` | usage; **empty for several adapters** — see below |
|
|
80
|
+
| `text` | the turn, **truncated at 50,000 chars** |
|
|
81
|
+
| `text_ngram`, `text_word` | `MATERIALIZED lower(text)`, carrying the text indexes |
|
|
82
|
+
| `project`, `folder` | where it happened; `folder` is what `resume` needs |
|
|
83
|
+
| `user_id` | `MATERIALIZED currentUser()` — stamped by the server, unfakeable |
|
|
84
|
+
| `origin` | `ship` or `import` |
|
|
85
|
+
| `epoch` | which *parse* of the session this row belongs to |
|
|
86
|
+
|
|
87
|
+
**`tool_calls`** — `session_id, seq, idx, source, host, tool_name, args, ts, project,
|
|
88
|
+
folder, origin, epoch, user_id`. `args` is truncated at 20,000 chars. **There is no
|
|
89
|
+
result column** — what a tool *returned* is not stored, and adapters truncate any preview
|
|
90
|
+
of it to 300–500 chars inside `messages.text`.
|
|
91
|
+
|
|
92
|
+
**`sessions`** — one metadata row per session: `name` (first user prompt, 120 chars),
|
|
93
|
+
`source`, `host`, `project`, `folder`, `message_count`, timestamps, `extra`. Deliberately
|
|
94
|
+
**not** epoch-keyed: latest write wins.
|
|
95
|
+
|
|
96
|
+
`memhouse sessions-query` prints a self-contained per-session rollup (it carries its own
|
|
97
|
+
`FINAL`) — prefer it over hand-joining.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## The three things that bite
|
|
102
|
+
|
|
103
|
+
### 1. Epochs — a plain `SELECT` over-counts
|
|
104
|
+
|
|
105
|
+
The shipper never overwrites a diverging parse. When a re-parse comes back shorter, or
|
|
106
|
+
any overlapping turn hashes differently — which is what a **compaction** does to a long
|
|
107
|
+
session — it writes the new parse under `epoch + 1` and keeps the old rows. So the same
|
|
108
|
+
session can be present several times over.
|
|
109
|
+
|
|
110
|
+
Reads must resolve to the current epoch. **Every counting or aggregating query needs
|
|
111
|
+
this**, and the physical table name is the *raw* one, so nothing applies it for you:
|
|
112
|
+
|
|
113
|
+
```sql
|
|
114
|
+
WHERE origin != 'ship'
|
|
115
|
+
OR (session_id, user_id, epoch) IN (
|
|
116
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
117
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Measured on a real house: omitting it over-counted by **34%**. Drop it deliberately only
|
|
121
|
+
when the question is about what a session said *before* it was compacted — superseded
|
|
122
|
+
epochs are the one place that content still exists.
|
|
123
|
+
|
|
124
|
+
### 2. Join on `(session_id, user_id)`, never `session_id` alone
|
|
125
|
+
|
|
126
|
+
Two housemates can collide on an adapter-local session id. Every join, every `GROUP BY`
|
|
127
|
+
that identifies a session, takes both columns.
|
|
128
|
+
|
|
129
|
+
### 3. Escape before splicing, and lowercase the needle
|
|
130
|
+
|
|
131
|
+
Search terms, session ids and user ids are arbitrary text going into single-quoted
|
|
132
|
+
literals. Escape backslashes then single quotes (`\` → `\\`, `'` → `\'`) in **every**
|
|
133
|
+
value. `O'Reilly` otherwise breaks the query, and crafted text could reshape it.
|
|
134
|
+
|
|
135
|
+
The FTS columns are `lower(text)`, so lowercase what you look for:
|
|
136
|
+
|
|
137
|
+
```sql
|
|
138
|
+
WHERE text_ngram LIKE '%needle%' -- substring, any length
|
|
139
|
+
WHERE hasToken(text_word, 'needle') -- whole word, faster on common terms
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Known gaps — say these out loud rather than reporting a wrong number
|
|
145
|
+
|
|
146
|
+
- **Cost is derived**, from `model` + tokens. Several adapters ship **no tokens at all**
|
|
147
|
+
(`kiro`, `vscode`, `zed`, and `cursor` on one of its two storage paths), so their cost
|
|
148
|
+
reads `$0` — which is *unknown*, not free. `memhouse doctor` names them.
|
|
149
|
+
- **Per-message timestamps are interpolated** for most adapters — only the session bounds
|
|
150
|
+
are real. Hour-of-day and "peak time" charts inherit that.
|
|
151
|
+
- **`text` stops at 50,000 chars**, tool `args` at 20,000. A missing tail is truncation,
|
|
152
|
+
not absence.
|
|
153
|
+
- **Tool *results* are not stored at all.** If the question is "what did that command
|
|
154
|
+
print", the house cannot answer it.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: access
|
|
3
|
+
description: Bring someone onto this memhouse ClickHouse, or let them read your house — mint a new member with their own house and the env file their install needs, grant a housemate read-only access to yours, list who can currently read it, or revoke. Use when the user says "invite X", "add X to memhouse", "get my friend set up", "share my memory with X", "let X see my sessions", "who can read my memory", "stop sharing with X". Inviting comes BEFORE sharing — you can only grant a user who already exists. Read-only by construction on the sharing side: a share can never let anyone write to or delete your memory.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "invite <name> | share <name> | list | revoke <name>"
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:access — who exists, and who may read
|
|
10
|
+
|
|
11
|
+
Two things that look alike and are not:
|
|
12
|
+
|
|
13
|
+
- **Invite** mints a ClickHouse *user* and their own *house*. Needs an administrator.
|
|
14
|
+
- **Share** grants an existing user `SELECT` on *your* house. You can do this yourself.
|
|
15
|
+
|
|
16
|
+
**Read `../reference/HOUSE.md` first** for the connection block. One rule dominates
|
|
17
|
+
everything here: **a password must never enter this conversation.** memhouse ships this
|
|
18
|
+
transcript into the house, and the archive is insert-only — a credential pasted here
|
|
19
|
+
cannot be withdrawn.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Invite — mint a member
|
|
24
|
+
|
|
25
|
+
This runs the `memhouse invite` CLI and nothing else. **Do not hand-run `CREATE USER` /
|
|
26
|
+
`GRANT` with an admin password substituted into a command** — that burns the credential
|
|
27
|
+
into a transcript on disk. The CLI passes it to one process and never echoes it.
|
|
28
|
+
|
|
29
|
+
1. **Check the binary:** `command -v memhouse`. Absent → tell the user to
|
|
30
|
+
`npm install -g memhouse`. There is no in-skill fallback, on purpose.
|
|
31
|
+
|
|
32
|
+
2. **Gather only two things:** the invitee's **name** (validate `[A-Za-z][A-Za-z0-9_]*`;
|
|
33
|
+
refuse otherwise) and the **URL the invitee will reach the house at** — not localhost,
|
|
34
|
+
which is *their* machine. Do not ask about an admin credential yet; the next step
|
|
35
|
+
usually needs none. Default is their own house — omit `--db`.
|
|
36
|
+
|
|
37
|
+
3. **Run it from the directory the file should land in** (step 4), bare first:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
memhouse invite <name> --url <url> [--db <shared-house>]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`invite written: …` → go to step 4, and no secret touched this conversation.
|
|
44
|
+
|
|
45
|
+
Otherwise read WHICH refusal came back; they need different answers.
|
|
46
|
+
|
|
47
|
+
**"cannot reach `<url>` …"** — the address, not your privileges. A `deploy --local`
|
|
48
|
+
house is loopback-bound on purpose, so no LAN address reaches it and no invitee could
|
|
49
|
+
either. Ask for the address the invitee will actually use; do not retry with a guess.
|
|
50
|
+
|
|
51
|
+
**"`<user>` is a MEMBER of this ClickHouse, not an administrator"** — inviting needs
|
|
52
|
+
`CREATE USER` and `CREATE DATABASE`, which a member deliberately lacks. Ask which case
|
|
53
|
+
applies rather than assuming:
|
|
54
|
+
|
|
55
|
+
- *They run the ClickHouse.* Hand them the line to run THEMSELVES, with
|
|
56
|
+
`--admin-password` left OFF so it is prompted for — out of the process list and out
|
|
57
|
+
of shell history:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
memhouse invite <name> --url <url> --admin-user <admin>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**You cannot run that yourself:** the prompt needs a TTY your Bash tool lacks, so it
|
|
64
|
+
refuses with "no TTY to prompt on" — and the fix is NOT `--admin-password`, which
|
|
65
|
+
would put the credential in this transcript.
|
|
66
|
+
|
|
67
|
+
- *Someone else runs it.* They cannot invite, and no flag changes that. Print the
|
|
68
|
+
statements for whoever administers the server — this contacts nothing:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
memhouse invite <name> --url <url> --print-sql
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**"house `<db>` already exists and holds N messages"** — STOP. That house is somebody's
|
|
75
|
+
memory. Report the row and writer counts and let the user choose: a different handle,
|
|
76
|
+
a different `--db`, or `--adopt` if sharing that house is genuinely the intent. Never
|
|
77
|
+
pass `--adopt` on your own initiative.
|
|
78
|
+
|
|
79
|
+
4. **Put the file where they can attach it.** The CLI writes `invite-<name>.env` into the
|
|
80
|
+
current directory and the user sends it by hand — so run from their downloads
|
|
81
|
+
directory, or move it there. **Never leave it in a git repo.** Confirm it is present
|
|
82
|
+
and mode `600`, state the full path, and **never print its contents**.
|
|
83
|
+
|
|
84
|
+
5. **Say whether the house was fresh** — `SELECT count() FROM <db>.messages` reads 0 for a
|
|
85
|
+
new one. Second line of defence behind step 3's refusal.
|
|
86
|
+
|
|
87
|
+
6. **Write the message the user will send.** They are handing over a file plus an
|
|
88
|
+
explanation; compose it rather than making them. Cover: what memhouse is in a sentence
|
|
89
|
+
or two; that the attached file IS a password (do not forward, do not paste in chat);
|
|
90
|
+
`npm install -g memhouse` (Node 24+) then `memhouse install --env invite-<name>.env`;
|
|
91
|
+
that install offers to change the password to one only they know and then deletes the
|
|
92
|
+
file, so they should say yes; `memhouse onboard` to start shipping and `memhouse status`
|
|
93
|
+
to check; and that their house is theirs alone unless they run `/mem:access share`.
|
|
94
|
+
|
|
95
|
+
If they are REPLACING an existing credential, add `memhouse ship --full` — a plain
|
|
96
|
+
incremental pass skips sessions the new house has no record of, so their history would
|
|
97
|
+
not follow them.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Share — let someone read your house
|
|
102
|
+
|
|
103
|
+
Sharing runs the `memhouse share` CLI. **Do not hand-write `GRANT` or `CREATE ROW POLICY`
|
|
104
|
+
for this** — partial sharing has four ways to go wrong quietly, and the command handles
|
|
105
|
+
each one:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
memhouse share <user> the whole house
|
|
109
|
+
memhouse share <user> --only project=memhouse just one project
|
|
110
|
+
memhouse share <user> --only session=<id> just one conversation
|
|
111
|
+
memhouse share --list who can read it, and how much
|
|
112
|
+
memhouse share <user> --revoke withdraw
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Scopes: `session=`, `project=`, `folder=`, `host=`, `source=`, `since=`, `until=`, and
|
|
116
|
+
they combine — `--only project=memhouse,since=2026-08-01`. Report back the counts it
|
|
117
|
+
prints (`2121 of 22488 rows`), because that is what the person will actually see.
|
|
118
|
+
|
|
119
|
+
**Say what a full share exposes** before running one: every session in the house, from
|
|
120
|
+
every machine, project and editor, including anything ever pasted into one. If that is
|
|
121
|
+
more than the user meant, `--only` is the answer, not a warning.
|
|
122
|
+
|
|
123
|
+
**Never widen without being asked.** Re-running `memhouse share <user>` with no `--only`
|
|
124
|
+
on someone currently scoped turns their partial share into a full one — the command says
|
|
125
|
+
so, and you should surface that line rather than let it pass.
|
|
126
|
+
|
|
127
|
+
### Why the CLI and not SQL
|
|
128
|
+
|
|
129
|
+
- A permissive catch-all policy for everyone else is the obvious design and it **fails
|
|
130
|
+
open**: measured, a second scoped user saw all 22,500 rows instead of their 1,860.
|
|
131
|
+
- Partial sharing depends on `users_without_row_policies_can_read_rows`, which is server
|
|
132
|
+
config, not a query setting, and whose default has moved between versions. The command
|
|
133
|
+
measures the behaviour before creating the first policy and refuses on a server that
|
|
134
|
+
would blindfold your existing readers.
|
|
135
|
+
- Revoking the grant alone leaves the policies behind, and a later re-share silently
|
|
136
|
+
reinherits the old scope.
|
|
137
|
+
- A policy on `messages` but not `tool_calls` leaks. All three rooms move together.
|
|
138
|
+
|
|
139
|
+
If the command refuses because the server hides rows from unpolicied readers, relay its
|
|
140
|
+
message — a full share still works there; only `--only` is unavailable until an admin
|
|
141
|
+
sets that config.
|
|
142
|
+
|
|
143
|
+
## Never
|
|
144
|
+
|
|
145
|
+
Never accept a password typed into the conversation, and never print one. Never invent an
|
|
146
|
+
admin credential. Never suggest storing one on disk. Never grant beyond `SELECT` — write
|
|
147
|
+
access to your rooms is not a thing to hand out, and `ALL` would let the other side delete
|
|
148
|
+
your memory. If the binary is absent, stop and say so rather than improvising the SQL.
|
|
@@ -12,6 +12,9 @@ A member owns one database. An **administrator** owns the ClickHouse: every hous
|
|
|
12
12
|
account, every grant. This skill is the second one, and it is the only skill allowed to
|
|
13
13
|
act outside the caller's own house.
|
|
14
14
|
|
|
15
|
+
**Read `../reference/HOUSE.md` first** — connection, schema, and the traps every query
|
|
16
|
+
here inherits.
|
|
17
|
+
|
|
15
18
|
**An admin password must never enter this conversation.** memhouse ships this transcript
|
|
16
19
|
into the house you are administering, so a password pasted here is a password published
|
|
17
20
|
to the archive — and unlike a file you can delete, the archive is insert-only. Resolve
|
|
@@ -82,6 +85,32 @@ Reads worth knowing — reach for these before inventing SQL:
|
|
|
82
85
|
| running queries | `SELECT query_id, user, elapsed, formatReadableSize(memory_usage) AS mem, substring(query,1,120) AS q FROM system.processes` |
|
|
83
86
|
| unfinished mutations | `SELECT database, table, mutation_id, command, parts_to_do, latest_fail_reason FROM system.mutations WHERE NOT is_done` |
|
|
84
87
|
|
|
88
|
+
### Sharing on someone else's behalf
|
|
89
|
+
|
|
90
|
+
A member runs `memhouse share` against their own house. An administrator can do it for
|
|
91
|
+
any house — useful when someone asks you to open theirs, or to audit what is open:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# what every house has opened up, server-wide
|
|
95
|
+
adm "SELECT database, short_name, table, select_filter FROM system.row_policies ORDER BY database, short_name FORMAT PrettyCompact" "?readonly=1"
|
|
96
|
+
adm "SHOW GRANTS FOR <user>"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To change a share, prefer running the member's own command with their credential over
|
|
100
|
+
hand-writing SQL — the CLI handles the four traps (a permissive catch-all failing open,
|
|
101
|
+
the server setting that decides whether unpolicied readers see anything, revoke leaving
|
|
102
|
+
policies behind, and the three rooms drifting out of step). When you must do it as
|
|
103
|
+
admin, mirror exactly what the CLI does: a policy on **all three rooms** or none, and
|
|
104
|
+
`DROP ROW POLICY` on every room when withdrawing.
|
|
105
|
+
|
|
106
|
+
**Dropping a house does not drop its row policies.** They persist as orphans, and a house
|
|
107
|
+
later recreated under the same name silently inherits them. After any `DROP DATABASE`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
adm "SELECT short_name, table FROM system.row_policies WHERE database = '<db>'"
|
|
111
|
+
# then DROP ROW POLICY <name> ON <db>.<table> for each
|
|
112
|
+
```
|
|
113
|
+
|
|
85
114
|
**Prefer a CLI verb over raw SQL wherever one exists.** `memhouse invite <name> --url …`
|
|
86
115
|
provisions a member correctly — right grants, right pin, refuses a house that already
|
|
87
116
|
holds someone's messages — and it takes `--admin-user`/`--admin-password`, so it works
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: house
|
|
3
|
+
description: The state of your memory and who is around it — what memhouse is and how to use it, whether shipping is working, what the house holds (sessions, messages, tokens per editor), how fresh it is, which machines write into it, which houses you can read, and who can read yours. Use for "what is memhouse", "how do I use this", "is my memory working", "how much is stored", "when did it last ship", "which machines are shipping", "who can see my memory", "whose memory can I see", "who else is on this server" — and as the first answer when another /mem skill fails because nothing is configured yet, or before debugging why a search found nothing.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: ""
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:house — what this is, whether it works, and who is around it
|
|
10
|
+
|
|
11
|
+
**Invoking this IS the request. Run the report and present it; there is nothing to
|
|
12
|
+
clarify.** Read-only throughout.
|
|
13
|
+
|
|
14
|
+
**Read `../reference/HOUSE.md` first** — connection, schema, and the epoch filter every
|
|
15
|
+
count below depends on.
|
|
16
|
+
|
|
17
|
+
## Nothing configured yet? Answer that instead
|
|
18
|
+
|
|
19
|
+
If the connection block finds no house, do not report an error — explain the product and
|
|
20
|
+
stop:
|
|
21
|
+
|
|
22
|
+
> **memhouse** gives your coding agents a memory. A local shipper parses your sessions
|
|
23
|
+
> from 17 editors — Claude Code, Codex, Cursor, Zed, Gemini CLI and more — and writes
|
|
24
|
+
> them as typed rows into a ClickHouse database **you own**. Nothing is embedded or
|
|
25
|
+
> summarised; the transcripts are kept verbatim, and the house outlives the local files,
|
|
26
|
+
> which most editors delete after about 30 days.
|
|
27
|
+
>
|
|
28
|
+
> Then any agent can read it: `/mem:recall` answers from your own history instead of
|
|
29
|
+
> starting from zero.
|
|
30
|
+
>
|
|
31
|
+
> To set it up: `npm install -g memhouse` then `memhouse onboard`. No ClickHouse yet?
|
|
32
|
+
> `memhouse deploy --local` stands one up bound to loopback. Someone invited you?
|
|
33
|
+
> `memhouse install --env invite-<you>.env`.
|
|
34
|
+
|
|
35
|
+
## The report
|
|
36
|
+
|
|
37
|
+
Lead with the one-line verdict — *working and current*, *working but stale*, or *not
|
|
38
|
+
shipping* — then the detail.
|
|
39
|
+
|
|
40
|
+
**1. Identity and reach.** `memhouse whoami` — who this credential is and what it may do.
|
|
41
|
+
`memhouse status --json` — daemons, connection, counts, freshness. Prefer these over
|
|
42
|
+
hand-rolled queries; they already know the schema.
|
|
43
|
+
|
|
44
|
+
**2. What the house holds**, per editor. Epoch filter required or every number is inflated:
|
|
45
|
+
|
|
46
|
+
```sql
|
|
47
|
+
SELECT source,
|
|
48
|
+
uniqExact(session_id) AS sessions,
|
|
49
|
+
count() AS messages,
|
|
50
|
+
sum(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) AS tokens,
|
|
51
|
+
formatDateTime(max(ts), '%Y-%m-%d %H:%i') AS newest
|
|
52
|
+
FROM messages
|
|
53
|
+
WHERE origin != 'ship'
|
|
54
|
+
OR (session_id, user_id, epoch) IN (
|
|
55
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
56
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id)
|
|
57
|
+
GROUP BY source ORDER BY messages DESC
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**A source shipping zero tokens is not free — it is unmeasured.** `kiro`, `vscode`, `zed`
|
|
61
|
+
and one of `cursor`'s two storage paths extract no usage, so their cost reads `$0`. Say
|
|
62
|
+
"unknown", never "$0", and point at `memhouse doctor`.
|
|
63
|
+
|
|
64
|
+
**3. Which machines write here**, and whether this one is among them:
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
SELECT user_id AS member, host AS machine, count() AS rows,
|
|
68
|
+
formatDateTime(max(ingested_at), '%Y-%m-%d %H:%i') AS last_write
|
|
69
|
+
FROM messages GROUP BY user_id, host ORDER BY last_write DESC
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A machine silent for days is the usual reason a search finds nothing. Note that the
|
|
73
|
+
*fleet heartbeat* records that a pass CONNECTED, not that rows landed — so treat
|
|
74
|
+
`last_write` from the data above as the truth, and be sceptical of a healthy-looking
|
|
75
|
+
heartbeat beside a stale `last_write`.
|
|
76
|
+
|
|
77
|
+
**4. Who can read your house, and whose you can read.** Best-effort by design — ClickHouse
|
|
78
|
+
shows a credential only what it may see, so on someone else's server sections come back
|
|
79
|
+
thin. Say what could not be seen rather than guessing.
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
-- houses your credential can reach; anything that is not yours was shared with you
|
|
83
|
+
SHOW DATABASES
|
|
84
|
+
-- who you have granted (memhouse's own record of it)
|
|
85
|
+
SELECT substring(key, 7) AS user, value AS state
|
|
86
|
+
FROM house_meta FINAL WHERE key LIKE 'share:%' ORDER BY key
|
|
87
|
+
-- everyone on the server (names only — every member holds SHOW USERS)
|
|
88
|
+
SELECT name FROM system.users ORDER BY name
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That `house_meta` list is memhouse's own note-keeping, not ClickHouse's grant table —
|
|
92
|
+
a member cannot read `system.grants`. Present it as "what memhouse recorded", and say
|
|
93
|
+
that a grant made by hand would not appear.
|
|
94
|
+
|
|
95
|
+
## Closing
|
|
96
|
+
|
|
97
|
+
End with the one useful next step, not a menu: reopen a session (`memhouse resume <id>`),
|
|
98
|
+
fix a stale machine (`memhouse update` there), start shipping (`memhouse onboard`), or
|
|
99
|
+
bring someone in (`/mem:access`). If everything is healthy, say so in one line and stop.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: recall
|
|
3
|
+
description: Retrieve from past agent conversations and answer from them — search every session ever shipped from all 17 editors (Claude Code, Codex, Cursor, VS Code, Zed, Gemini CLI, …) across every project and machine, read the actual transcripts, and answer with citations. Use whenever the user refers to something not in the current context - "how did I fix X last time", "what approach did we settle on", "why did we choose Z", "find that conversation about Y", "did I ever try W", "what was I working on", "show my recent sessions", "sessions from last week", "remind me how I did it". Reach for this BEFORE saying you do not know about prior work. Also reads a housemate's memory when they name it ("how did yigit fix X"). For numbers rather than transcripts — spend, model mix, tool rankings — use /mem:sql.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
argument-hint: "<question, terms, or 'recent sessions'>"
|
|
6
|
+
allowed-tools: Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# /mem:recall — find it, read it, answer from it
|
|
10
|
+
|
|
11
|
+
The house is the archive that OUTLIVES the transcripts on disk. Claude Code compacts and
|
|
12
|
+
deletes local sessions, so for anything older than ~30 days this is the only place the
|
|
13
|
+
answer still exists.
|
|
14
|
+
|
|
15
|
+
**The synthesis is yours; the store is not.** memhouse runs no LLM in its write or read
|
|
16
|
+
path — this skill retrieves rows and YOU read them. Never present a guess as a retrieval:
|
|
17
|
+
if the house has nothing, say so.
|
|
18
|
+
|
|
19
|
+
**Read `../reference/HOUSE.md` first** — connection, schema, and the three traps
|
|
20
|
+
(epoch filtering, `(session_id, user_id)` joins, escaping). Every query below assumes it.
|
|
21
|
+
|
|
22
|
+
## Which shape is being asked for
|
|
23
|
+
|
|
24
|
+
One skill, three depths. Pick by what the user wants back, not by which words they used.
|
|
25
|
+
|
|
26
|
+
- **A question** ("how did I fix X") → retrieve, read the turns, answer with citations.
|
|
27
|
+
- **A conversation** ("find the chat about Y") → locate sessions, describe them, offer
|
|
28
|
+
to read one.
|
|
29
|
+
- **An orientation** ("what was I working on") → list recent sessions and stop.
|
|
30
|
+
|
|
31
|
+
When it is ambiguous, do the deepest one that fits — an answer with citations also tells
|
|
32
|
+
them which session it was, whereas a bare list does not answer anything.
|
|
33
|
+
|
|
34
|
+
## Answering a question
|
|
35
|
+
|
|
36
|
+
**1. Extract terms that would appear in the ANSWER, not the question.** "How did I fix
|
|
37
|
+
the ClickHouse auth error?" → `ClickHouse auth`, `Authentication failed`, `ACCESS_DENIED`.
|
|
38
|
+
Error strings beat paraphrases; identifiers beat prose. Run 2–3 variants — and search
|
|
39
|
+
each term SEPARATELY, because the columns hold text, not concepts: a needle of
|
|
40
|
+
`clickhouse ttl` matches only that exact adjacency. On a real house `%clickhouse ttl%`
|
|
41
|
+
found 8 messages where `%clickhouse%` AND `%ttl%` found 1,461.
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
SELECT session_id, user_id, any(folder) AS folder,
|
|
45
|
+
formatDateTime(max(ts), '%Y-%m-%d') AS day, count() AS hits,
|
|
46
|
+
substring(anyIf(text, positionCaseInsensitive(text, 'NEEDLE') > 0), 1, 300) AS sample
|
|
47
|
+
FROM messages
|
|
48
|
+
WHERE text_ngram LIKE '%needle%'
|
|
49
|
+
AND (origin != 'ship'
|
|
50
|
+
OR (session_id, user_id, epoch) IN (
|
|
51
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
52
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id))
|
|
53
|
+
GROUP BY session_id, user_id
|
|
54
|
+
ORDER BY hits DESC, max(ts) DESC
|
|
55
|
+
LIMIT 8
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`anyIf(...)` so the sample is a line that actually matched — `any(text)` returns an
|
|
59
|
+
arbitrary message from the session and misled two readers in three. **Order by `hits`,
|
|
60
|
+
not just recency**: a session mentioning the term 197 times outranks yesterday's passing
|
|
61
|
+
reference.
|
|
62
|
+
|
|
63
|
+
**2. Read the strongest sessions — the turns, not the hit line.** Pull a window around
|
|
64
|
+
the match so you see the resolution, not the complaint:
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
SELECT seq, role, substring(text, 1, 2000) AS text
|
|
68
|
+
FROM messages
|
|
69
|
+
WHERE session_id = '<sid>' AND user_id = '<uid>'
|
|
70
|
+
AND seq BETWEEN <hit_seq - 3> AND <hit_seq + 12>
|
|
71
|
+
ORDER BY seq
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Apply the epoch filter here too — an unfiltered read of a compacted session interleaves
|
|
75
|
+
two parses of the same conversation.
|
|
76
|
+
|
|
77
|
+
**3. Answer, then cite.** Lead with the answer. Under it list the sessions it came from —
|
|
78
|
+
`session_id`, date, project — so the user can reopen one (`memhouse resume <session_id>`
|
|
79
|
+
prints the command). Quote decisive lines verbatim where exact wording matters: an error
|
|
80
|
+
string, a command, a config value.
|
|
81
|
+
|
|
82
|
+
**4. Nothing found is an answer.** Say what you searched and where coverage ends
|
|
83
|
+
(`SELECT min(ts), max(ts), count() FROM messages`) rather than padding. Check the obvious
|
|
84
|
+
causes first: wrong case (the columns are lowercased), a phrase that never occurred
|
|
85
|
+
adjacently, or a house that simply has not shipped that project.
|
|
86
|
+
|
|
87
|
+
## Finding a conversation
|
|
88
|
+
|
|
89
|
+
Same first query, then describe what you found — project, editor, date, how many hits,
|
|
90
|
+
one representative line each. Offer to read the strongest. Do not dump ten rows and stop.
|
|
91
|
+
|
|
92
|
+
## Listing recent sessions
|
|
93
|
+
|
|
94
|
+
`memhouse sessions-query` prints a self-contained rollup (it carries its own `FINAL`);
|
|
95
|
+
prefer it over hand-joining. For a plain list:
|
|
96
|
+
|
|
97
|
+
```sql
|
|
98
|
+
SELECT session_id, name, source, host, project,
|
|
99
|
+
formatDateTime(started_at, '%Y-%m-%d %H:%i') AS started, message_count
|
|
100
|
+
FROM sessions
|
|
101
|
+
ORDER BY started_at DESC
|
|
102
|
+
LIMIT 20
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Narrow with `WHERE project = '…'`, `source = 'cursor'`, `host = '…'`, or a date range as
|
|
106
|
+
asked. In a shared house every member's sessions are here — add `AND user_id = '<name>'`
|
|
107
|
+
only when the user asks for one person's.
|
|
108
|
+
|
|
109
|
+
## What can bite
|
|
110
|
+
|
|
111
|
+
Beyond the three in the reference:
|
|
112
|
+
|
|
113
|
+
- **Ranking is yours.** There is no relevance score in the store. `hits` plus recency is
|
|
114
|
+
the whole of it — say so rather than implying the top row is "the best match".
|
|
115
|
+
- **Tool *results* are not stored.** "What did that command print" is unanswerable from
|
|
116
|
+
the house; only the invocation and its arguments survive.
|
|
117
|
+
- **`memhouse search`** exists as a CLI, but it joins your terms into ONE literal phrase
|
|
118
|
+
and orders by date alone. Prefer the SQL above; reach for the CLI only for a quick
|
|
119
|
+
one-word look.
|