memhouse 0.16.0 → 0.18.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 +97 -31
- package/bin/memhouse.js +657 -187
- package/editors/antigravity.js +2 -0
- package/editors/claude.js +3 -1
- package/editors/codebuff.js +2 -0
- package/editors/codex.js +4 -1
- package/editors/commandcode.js +2 -1
- package/editors/copilot-jetbrains.js +2 -1
- package/editors/copilot.js +2 -1
- package/editors/cursor-agent.js +2 -1
- package/editors/cursor.js +2 -0
- package/editors/gemini.js +2 -1
- package/editors/goose.js +2 -1
- package/editors/index.js +8 -1
- package/editors/kiro.js +3 -2
- package/editors/opencode.js +2 -1
- package/editors/scope.js +126 -0
- package/editors/vscode.js +2 -0
- package/editors/zed.js +2 -1
- package/memhouse/DESIGN.md +31 -8
- package/memhouse/channel.js +50 -0
- package/memhouse/delivery/AGENT-INSTALL-PROMPT.md +4 -3
- package/memhouse/delivery/AGENT-INSTALL.md +3 -3
- package/memhouse/delivery/INVITATION.md.tpl +130 -0
- package/memhouse/delivery/PROMPT.md +21 -14
- package/memhouse/delivery/kernel-install.md +4 -2
- package/memhouse/delivery/plugin/.claude-plugin/plugin.json +2 -2
- package/memhouse/delivery/plugin/reference/HOUSE.md +173 -0
- package/memhouse/delivery/plugin/skills/access/SKILL.md +148 -0
- package/memhouse/delivery/plugin/skills/admin/SKILL.md +30 -1
- 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 +6 -4
- package/memhouse/house/HOUSE.md +25 -11
- package/memhouse/house/house.js +110 -27
- package/memhouse/house/migrate.js +7 -6
- package/memhouse/house/relocate.js +2 -2
- package/memhouse/house/schema.sql.tpl +3 -3
- package/memhouse/provision.js +100 -0
- package/memhouse/share.js +64 -0
- package/memhouse/shipper/ship.js +87 -28
- 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,130 @@
|
|
|
1
|
+
# You have been invited to a memhouse
|
|
2
|
+
|
|
3
|
+
{{INVITER}} set up a **house** — a ClickHouse database where a team keeps its coding-agent
|
|
4
|
+
memory — and made you a member of it. This page walks you from the file you were sent to a
|
|
5
|
+
working install, and shows what you get.
|
|
6
|
+
|
|
7
|
+
You received two things:
|
|
8
|
+
|
|
9
|
+
| file | what it is |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `{{FILE}}` | **your credential.** One-time: install consumes it and deletes it. Treat it like a password until then. |
|
|
12
|
+
| this page | the steps. Nothing secret in here. |
|
|
13
|
+
|
|
14
|
+
Do it yourself, or hand this page to your coding agent. From the directory holding both
|
|
15
|
+
files:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
claude "Read MEMHOUSE-INVITATION.md and follow it. Ask me before each step that changes this machine."
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Any agent that can run shell commands works the same way; the rules it must keep are at
|
|
22
|
+
the end of this page.
|
|
23
|
+
|
|
24
|
+
## 1. Install memhouse
|
|
25
|
+
|
|
26
|
+
{{INSTALL}}
|
|
27
|
+
|
|
28
|
+
On most Linux distros the global install needs `sudo`; if `npm` says `EACCES`, that is why.
|
|
29
|
+
`memhouse --version` should print `{{VERSION}}`.
|
|
30
|
+
|
|
31
|
+
## 2. Join the house
|
|
32
|
+
|
|
33
|
+
From the directory holding `{{FILE}}`:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
memhouse install --env {{FILE}}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
What that does, in order: connects to `{{URL}}` as `{{NAME}}`, **rotates your password to
|
|
40
|
+
one only your machine knows**, creates your rooms, writes your config to `~/.memhouse/env`,
|
|
41
|
+
deletes `{{FILE}}`, and starts shipping in the background. The password in the file stops
|
|
42
|
+
working the moment install finishes, so a copy left in a chat is dead.
|
|
43
|
+
|
|
44
|
+
## 3. Check it is working
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
memhouse status # your rooms, how much is in them, the shipper's pulse
|
|
48
|
+
memhouse doctor # every line a check mark = healthy
|
|
49
|
+
memhouse discover # what this machine ships, and where each editor's sessions were found
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`memhouse status` shows a session count that grows for a few minutes on a machine with
|
|
53
|
+
history; the first pass ships everything you have. After that it ships new turns as they
|
|
54
|
+
happen.
|
|
55
|
+
|
|
56
|
+
## 4. What you own, and who can see it
|
|
57
|
+
|
|
58
|
+
Your memory lives in five tables named for you inside `{{DB}}` — `{{DB}}.{{NAME}}_messages`,
|
|
59
|
+
`{{DB}}.{{NAME}}_sessions`, `{{DB}}.{{NAME}}_tool_calls` and two bookkeeping tables — and you
|
|
60
|
+
hold exactly one grant, on `{{DB}}.{{NAME}}_*`. Housemates cannot read your rooms, list
|
|
61
|
+
them, or grant them to anyone; you cannot read theirs. `memhouse whoami --json` reads that
|
|
62
|
+
grant back from the server. What can always see everything is the account that administers
|
|
63
|
+
the ClickHouse, which is {{INVITER}}'s — the same as any database you use at work.
|
|
64
|
+
|
|
65
|
+
Everything your editors write to disk ships verbatim: prompts, replies, tool calls and their
|
|
66
|
+
arguments, including anything an agent ever pasted into a session. The house is as
|
|
67
|
+
sensitive as your shell history. If some of your work should not go there, say so before
|
|
68
|
+
shipping — see "which sessions ship" below.
|
|
69
|
+
|
|
70
|
+
## 5. Things to try
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
memhouse search "the error you fixed last week"
|
|
74
|
+
memhouse resume <session-id> # prints the command that reopens that session
|
|
75
|
+
memhouse plugins install claude # /mem:recall, /mem:sql, /mem:house, /mem:access, /mem:admin
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then, inside Claude Code:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
/mem:recall how did I set up the ClickHouse tunnel
|
|
82
|
+
/mem:sql what did I spend on Opus this month
|
|
83
|
+
/mem:house
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Sharing is yours to give and take back, no operator involved:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
memhouse share <housemate> everything you own
|
|
90
|
+
memhouse share <housemate> --only project=<name> one project
|
|
91
|
+
memhouse share --list
|
|
92
|
+
memhouse share <housemate> --revoke
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Which sessions ship
|
|
96
|
+
|
|
97
|
+
Everything memhouse finds on the machine — every supported editor, every Claude Code
|
|
98
|
+
config directory. `memhouse discover` lists what it found and the variable that narrows
|
|
99
|
+
each one. Two lines in `~/.memhouse/env` are the common case:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
MEMHOUSE_EDITORS='claude'
|
|
103
|
+
MEMHOUSE_CLAUDE_ROOTS='~/.claude-playbooks/<the one instance you mean>'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## If something refuses
|
|
107
|
+
|
|
108
|
+
Every refusal says what to do next. The two you might meet: `EACCES` on install means
|
|
109
|
+
`sudo`, and `needed rights this credential does not hold` means the grant on the server is
|
|
110
|
+
narrower than the one memhouse issues — ask {{INVITER}} to run `memhouse install --print-sql
|
|
111
|
+
--member {{NAME}}` and apply what it prints.
|
|
112
|
+
|
|
113
|
+
## For an agent following this page
|
|
114
|
+
|
|
115
|
+
You are installing memhouse for the person who handed you this file. Five rules; three of
|
|
116
|
+
them exist because an agent broke them:
|
|
117
|
+
|
|
118
|
+
1. **Never print `{{FILE}}` or any `MEMHOUSE_*` value.** No `cat`, no `echo $MEMHOUSE_PASSWORD`,
|
|
119
|
+
no `set -x`. This very session will be shipped into the house; a password printed here is
|
|
120
|
+
a password archived.
|
|
121
|
+
2. **Run the commands as written, in order.** Do not substitute `CREATE USER`, `GRANT` or any
|
|
122
|
+
SQL of your own; there is nothing to fix on the server from this side.
|
|
123
|
+
3. **Ask before `memhouse install --env`.** It rotates the credential, deletes `{{FILE}}`,
|
|
124
|
+
and starts shipping this machine's transcripts. The person decides when that happens.
|
|
125
|
+
4. **A ✗ line is the end of that step, not an invitation to work around it.** Read the line
|
|
126
|
+
after it — every refusal names its remedy — do that or report it. `sudo` for `EACCES` is
|
|
127
|
+
the one you may apply yourself.
|
|
128
|
+
5. **Report what `memhouse status` and `memhouse doctor` say**, verbatim, when you are done.
|
|
129
|
+
A green install is those two commands, not your summary of them.
|
|
130
|
+
|
|
@@ -23,18 +23,25 @@ work that is not in your current context ("that session where…", "how did I so
|
|
|
23
23
|
X before", "did we ever…", "what was I working on last week"), search memhouse
|
|
24
24
|
FIRST. Only say you don't know after a search comes back empty.
|
|
25
25
|
|
|
26
|
-
**How to query.** Prefer the installed skills when present:
|
|
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.
|
|
26
|
+
**How to query.** Prefer the installed skills when present:
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
- `/mem:recall` — find past sessions and answer from them, with citations. This is the
|
|
29
|
+
one you want for almost every "have I seen this before" question.
|
|
30
|
+
- `/mem:sql` — read-only SQL when the answer is a number: spend, model mix, tool
|
|
31
|
+
rankings, activity over time.
|
|
32
|
+
- `/mem:house` — what the system is, whether it is shipping, what it holds, and who can
|
|
33
|
+
read it.
|
|
34
|
+
- `/mem:access` — bring someone onto the server, or grant a housemate read-only access.
|
|
35
|
+
- `/mem:admin` — server-wide administration; needs an administrator credential.
|
|
36
|
+
|
|
37
|
+
Without skills, query directly.
|
|
38
|
+
|
|
39
|
+
**Table names.** The rooms are `messages`, `sessions`, `tool_calls` in the connection's
|
|
40
|
+
database — unless this is a shared house, where they carry the member's own prefix
|
|
41
|
+
(`alice_messages`). `memhouse rooms` prints what yours are actually called; a bare
|
|
42
|
+
`FROM messages` is not a table you have in a shared house. `user_id` (server-stamped)
|
|
43
|
+
says whose row it is and `host` says which machine. To read across several members, UNION ALL over the rooms you hold — naming a room
|
|
44
|
+
you do not hold is an error rather than a silent omission. **There is no `sessions_v` object** — the rollup is a saved query, and
|
|
38
45
|
`memhouse sessions-query` prints it ready to drop into a `FROM (...) AS c`.
|
|
39
46
|
|
|
40
47
|
**One session can be stored more than once.** The shipper never deletes: when an editor
|
|
@@ -66,12 +73,12 @@ the retained epochs are the only place that content still exists.
|
|
|
66
73
|
**Rules.**
|
|
67
74
|
- Memory is READ-ONLY for you: never INSERT/ALTER/DROP the ROOMS (sessions, messages,
|
|
68
75
|
tool_calls) — ingestion belongs to the memhouse shipper alone. The one exception is
|
|
69
|
-
membership administration through the `memhouse` CLI: `/mem:
|
|
70
|
-
`/mem:
|
|
76
|
+
membership administration through the `memhouse` CLI: `/mem:access` mints a user and
|
|
77
|
+
`/mem:access` grants read access, both via the tool, not by you writing to the rooms.
|
|
71
78
|
- Quote retrieved content as *the user's past sessions*, and cite the session_id
|
|
72
79
|
when the user may want to dig deeper.
|
|
73
80
|
- Other users' HOUSES (other databases on the same server) are readable only when
|
|
74
|
-
shared with this credential (`/mem:
|
|
81
|
+
shared with this credential (`/mem:access` on their side). Query a shared house by
|
|
75
82
|
qualified names — `polat.messages` — with the same CUR filter. An empty result
|
|
76
83
|
means "nothing visible", not "nothing ever happened".
|
|
77
84
|
- Do not paste credentials from `{{ENV_FILE}}` into responses, commits, or logs.
|
|
@@ -80,8 +80,10 @@ user so the stamp cannot be skipped). Her machines are told apart by `host`.
|
|
|
80
80
|
**A house is shared by its housemates.** The tables are common; `user_id` and `host` say
|
|
81
81
|
who wrote what, and `WHERE user_id = 'alice'` is one person. The boundary is the
|
|
82
82
|
DATABASE: a member of `mem` holds nothing on any other database, so two agencies on one
|
|
83
|
-
kernel ClickHouse cannot read each other.
|
|
84
|
-
|
|
83
|
+
kernel ClickHouse cannot read each other. WITHIN a house, members hold their own rooms
|
|
84
|
+
and nothing else — housemates are isolated from each other by grant, not by trust, and
|
|
85
|
+
each can verify it with `SHOW GRANTS`. A separate house remains the boundary between
|
|
86
|
+
groups that should not know of each other at all.
|
|
85
87
|
|
|
86
88
|
Row policies are still the only way to share a *subset* of rows, which is owner-mediated
|
|
87
89
|
and documented in `SHARING.md` — they are not how isolation works.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mem",
|
|
3
|
-
"description": "Conversation memory for AI agents over the memhouse typed ClickHouse store:
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "Conversation memory for AI agents over the memhouse typed ClickHouse store: recall past sessions and answer from them, run analytics, check the house, bring people in, and administer the server. Skills install as /mem:recall, /mem:sql, /mem:house, /mem:access, and /mem:admin.",
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ramazan Polat"
|
|
7
7
|
},
|
|
@@ -0,0 +1,173 @@
|
|
|
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** — on the user's machine, their server, or a
|
|
12
|
+
hosted ClickHouse; memhouse runs no service of its own. Its **rooms** are tables —
|
|
13
|
+
`sessions`, `messages`, `tool_calls` — and the server stamps who wrote every row. A
|
|
14
|
+
house's grants reach nothing outside it.
|
|
15
|
+
|
|
16
|
+
Every member's rooms are named for them — `mem.alice_messages`, `mem.alice_sessions`,
|
|
17
|
+
`mem.alice_tool_calls` — and one grant covers exactly those: `ON mem.alice_*`. Housemates
|
|
18
|
+
keep their own rooms beside yours and cannot read them, list them, or grant them onward;
|
|
19
|
+
you cannot read theirs. `SHOW GRANTS` shows you the one line you hold. **A bare
|
|
20
|
+
`FROM messages` names nothing you have** — the `q()` below prefixes room names with your
|
|
21
|
+
member name for you, and `memhouse rooms` prints what yours are called.
|
|
22
|
+
|
|
23
|
+
An **operator** owns the ClickHouse itself; an **admin credential** is what proves it.
|
|
24
|
+
`memhouse whoami` says which you are holding.
|
|
25
|
+
|
|
26
|
+
Rows arrive from a **shipper** that parses local transcripts from 17 editors. It only
|
|
27
|
+
ever inserts. Nothing in memhouse runs an LLM over your data — these skills retrieve
|
|
28
|
+
rows, and *you* read them.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Connect
|
|
33
|
+
|
|
34
|
+
**Never print this file or the variables in it.** No `cat "$MH_ENV"`, no
|
|
35
|
+
`env | grep MEMHOUSE`, no `set -x`. Anything printed becomes part of a transcript
|
|
36
|
+
memhouse ships back into the house — the archive is insert-only, so a leaked credential
|
|
37
|
+
cannot be withdrawn.
|
|
38
|
+
|
|
39
|
+
Credentials resolve **flags > exported `MEMHOUSE_*` > `$MEMHOUSE_HOME/env`** (default
|
|
40
|
+
`~/.memhouse/env`) — the same order the CLI uses. The dance below snapshots the
|
|
41
|
+
environment first, because sourcing the file would otherwise clobber an already-exported
|
|
42
|
+
variable.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
MH_ENV="${MEMHOUSE_HOME:-$HOME/.memhouse}/env"
|
|
46
|
+
_u=${MEMHOUSE_URL-}; _s=${MEMHOUSE_USER-}; _p=${MEMHOUSE_PASSWORD-}; _d=${MEMHOUSE_DB-}
|
|
47
|
+
set -a; [ -f "$MH_ENV" ] && . "$MH_ENV"; set +a
|
|
48
|
+
[ -n "$_u" ] && MEMHOUSE_URL=$_u; [ -n "$_s" ] && MEMHOUSE_USER=$_s
|
|
49
|
+
[ -n "$_p" ] && MEMHOUSE_PASSWORD=$_p; [ -n "$_d" ] && MEMHOUSE_DB=$_d
|
|
50
|
+
if [ -z "${MEMHOUSE_URL:-}" ] || [ -z "${MEMHOUSE_USER:-}" ]; then
|
|
51
|
+
echo "no memhouse house configured — run: memhouse install" >&2; exit 1
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
q() { # read-only by construction; every read path should use this
|
|
55
|
+
sed -E "s/([[:space:](]|^)(FROM|JOIN)[[:space:]]+(sessions|messages|tool_calls|meta|events)([[:space:];,)]|\$)/\1\2 ${MEMHOUSE_USER}_\3\4/g" \
|
|
56
|
+
| curl -sS --fail-with-body --user "$MEMHOUSE_USER:${MEMHOUSE_PASSWORD:-}" \
|
|
57
|
+
--data-binary @- "$MEMHOUSE_URL/?database=${MEMHOUSE_DB:-$MEMHOUSE_USER}&readonly=1"
|
|
58
|
+
}
|
|
59
|
+
q <<'SQL'
|
|
60
|
+
SELECT 1 FORMAT PrettyCompact
|
|
61
|
+
SQL
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`readonly=1` is a ClickHouse-side setting: even a mis-generated `DROP` is refused by the
|
|
65
|
+
server, not by discipline.
|
|
66
|
+
|
|
67
|
+
**The `sed` is what makes every query below work in both layouts** — write room names
|
|
68
|
+
bare and it supplies the prefix when there is one. It rewrites only after `FROM`/`JOIN`,
|
|
69
|
+
so a needle like `LIKE '%messages%'` is untouched, and `FROM yigit_messages` is left
|
|
70
|
+
alone — the room name must follow whitespace, and there is none inside `yigit_messages`.
|
|
71
|
+
With no prefix set it expands to nothing and the query passes through verbatim.
|
|
72
|
+
|
|
73
|
+
It is spelled with `[[:space:]]` classes rather than `\b` because **BSD `sed` does not
|
|
74
|
+
support `\b`**: on macOS the `\b` form matched nothing and sent the query through
|
|
75
|
+
unprefixed. That fails loudly as `UNKNOWN_TABLE` rather than quietly reading wrong rows,
|
|
76
|
+
which is the right way round — but it fails.
|
|
77
|
+
|
|
78
|
+
**Reading a housemate's memory.** A share is `GRANT SELECT ON mem.<them>_*` — their
|
|
79
|
+
rooms, not the database. `SHOW TABLES` lists exactly what you may read: your own rooms plus
|
|
80
|
+
any shared with you. Name a housemate's rooms in full (`FROM yigit_messages`); the `q()`
|
|
81
|
+
rewrite leaves an already-prefixed name alone, since applying yours would point at the
|
|
82
|
+
wrong rooms. Say whose memory an answer came from.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Schema
|
|
87
|
+
|
|
88
|
+
**`messages`** — one row per turn.
|
|
89
|
+
|
|
90
|
+
| column | meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `session_id` | `<source>:<adapter-local id>`, e.g. `claude-code:6b1f…` |
|
|
93
|
+
| `seq` | position in the session, from 0 |
|
|
94
|
+
| `source` | editor: `claude-code`, `codex`, `cursor`, … |
|
|
95
|
+
| `host` | the machine, `<hostname>-<8 hex>` — random per install, not derived |
|
|
96
|
+
| `ts` | when the turn happened (`DateTime64(3,'UTC')`) |
|
|
97
|
+
| `role` | `user`, `assistant`, `system` |
|
|
98
|
+
| `model`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_write_tokens` | usage; **empty for several adapters** — see below |
|
|
99
|
+
| `text` | the turn, **truncated at 50,000 chars** |
|
|
100
|
+
| `text_ngram`, `text_word` | `MATERIALIZED lower(text)`, carrying the text indexes |
|
|
101
|
+
| `project`, `folder` | where it happened; `folder` is what `resume` needs |
|
|
102
|
+
| `user_id` | `MATERIALIZED currentUser()` — stamped by the server, unfakeable |
|
|
103
|
+
| `origin` | `ship` or `import` |
|
|
104
|
+
| `epoch` | which *parse* of the session this row belongs to |
|
|
105
|
+
|
|
106
|
+
**`tool_calls`** — `session_id, seq, idx, source, host, tool_name, args, ts, project,
|
|
107
|
+
folder, origin, epoch, user_id`. `args` is truncated at 20,000 chars. **There is no
|
|
108
|
+
result column** — what a tool *returned* is not stored, and adapters truncate any preview
|
|
109
|
+
of it to 300–500 chars inside `messages.text`.
|
|
110
|
+
|
|
111
|
+
**`sessions`** — one metadata row per session: `name` (first user prompt, 120 chars),
|
|
112
|
+
`source`, `host`, `project`, `folder`, `message_count`, timestamps, `extra`. Deliberately
|
|
113
|
+
**not** epoch-keyed: latest write wins.
|
|
114
|
+
|
|
115
|
+
`memhouse sessions-query` prints a self-contained per-session rollup (it carries its own
|
|
116
|
+
`FINAL`) — prefer it over hand-joining.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## The three things that bite
|
|
121
|
+
|
|
122
|
+
### 1. Epochs — a plain `SELECT` over-counts
|
|
123
|
+
|
|
124
|
+
The shipper never overwrites a diverging parse. When a re-parse comes back shorter, or
|
|
125
|
+
any overlapping turn hashes differently — which is what a **compaction** does to a long
|
|
126
|
+
session — it writes the new parse under `epoch + 1` and keeps the old rows. So the same
|
|
127
|
+
session can be present several times over.
|
|
128
|
+
|
|
129
|
+
Reads must resolve to the current epoch. **Every counting or aggregating query needs
|
|
130
|
+
this**, and the physical table name is the *raw* one, so nothing applies it for you:
|
|
131
|
+
|
|
132
|
+
```sql
|
|
133
|
+
WHERE origin != 'ship'
|
|
134
|
+
OR (session_id, user_id, epoch) IN (
|
|
135
|
+
SELECT session_id, user_id, max(epoch) FROM messages
|
|
136
|
+
WHERE origin = 'ship' GROUP BY session_id, user_id)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Measured on a real house: omitting it over-counted by **34%**. Drop it deliberately only
|
|
140
|
+
when the question is about what a session said *before* it was compacted — superseded
|
|
141
|
+
epochs are the one place that content still exists.
|
|
142
|
+
|
|
143
|
+
### 2. Join on `(session_id, user_id)`, never `session_id` alone
|
|
144
|
+
|
|
145
|
+
Two housemates can collide on an adapter-local session id. Every join, every `GROUP BY`
|
|
146
|
+
that identifies a session, takes both columns.
|
|
147
|
+
|
|
148
|
+
### 3. Escape before splicing, and lowercase the needle
|
|
149
|
+
|
|
150
|
+
Search terms, session ids and user ids are arbitrary text going into single-quoted
|
|
151
|
+
literals. Escape backslashes then single quotes (`\` → `\\`, `'` → `\'`) in **every**
|
|
152
|
+
value. `O'Reilly` otherwise breaks the query, and crafted text could reshape it.
|
|
153
|
+
|
|
154
|
+
The FTS columns are `lower(text)`, so lowercase what you look for:
|
|
155
|
+
|
|
156
|
+
```sql
|
|
157
|
+
WHERE text_ngram LIKE '%needle%' -- substring, any length
|
|
158
|
+
WHERE hasToken(text_word, 'needle') -- whole word, faster on common terms
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Known gaps — say these out loud rather than reporting a wrong number
|
|
164
|
+
|
|
165
|
+
- **Cost is derived**, from `model` + tokens. Several adapters ship **no tokens at all**
|
|
166
|
+
(`kiro`, `vscode`, `zed`, and `cursor` on one of its two storage paths), so their cost
|
|
167
|
+
reads `$0` — which is *unknown*, not free. `memhouse doctor` names them.
|
|
168
|
+
- **Per-message timestamps are interpolated** for most adapters — only the session bounds
|
|
169
|
+
are real. Hour-of-day and "peak time" charts inherit that.
|
|
170
|
+
- **`text` stops at 50,000 chars**, tool `args` at 20,000. A missing tail is truncation,
|
|
171
|
+
not absence.
|
|
172
|
+
- **Tool *results* are not stored at all.** If the question is "what did that command
|
|
173
|
+
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.** `invite` wrote TWO files beside each other:
|
|
88
|
+
`invite-<name>.env` (the credential, one-time) and `MEMHOUSE-INVITATION.md` (the steps,
|
|
89
|
+
nothing secret — install, verify, what they own, things to try). The guide does the
|
|
90
|
+
explaining, so the message is short: what memhouse is in a sentence; that the `.env` IS a
|
|
91
|
+
password (do not forward, do not paste in chat); open `MEMHOUSE-INVITATION.md` and follow
|
|
92
|
+
it. If `memhouse --version` shows a pre-release (a `-` in it), the invitee also needs the
|
|
93
|
+
tarball this build came from (`memhouse nightly --out …`); the guide says so and names it.
|
|
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> everything you own
|
|
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.
|
|
@@ -8,10 +8,13 @@ allowed-tools: Bash
|
|
|
8
8
|
|
|
9
9
|
# /mem:admin — operate the house as its administrator
|
|
10
10
|
|
|
11
|
-
A member owns one
|
|
11
|
+
A member owns a house, or rooms in one. An **administrator** owns the ClickHouse: every house, every
|
|
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
|