Every coding-agent session on every machine you install it on — Claude Code, Codex, Cursor, Gemini CLI, Devin, Zed and a dozen more — parsed on your machine and shipped to one typed ClickHouse store you own. Then your next agent can read what the last one already worked out.
npm install -g memhouse
then memhouse onboard — discover, configure, ship, start.
no flags, no build step, nothing native. needs Node 24+.
One shipper · seventeen adapters · whatever you switched to last month
// shown in dark mode — the dashboard ships both themes, like this page. It is agentlytics' UI, unforked: memhouse serves the same bundle over its own typed store
01 / The problem
Every agent you run already writes down what it did — into its own directory, in its own format, on whichever laptop you happened to use. Which means the answer you worked out three weeks ago is on disk and out of reach: not to you, and definitely not to the agent that is about to re-derive it.
| Session files as they ship | Export + index (RAG) | memhouse | |
|---|---|---|---|
| Where it lives | A dozen app directories, per machine | A vector store you now operate | One ClickHouse you already trust |
| Coverage | Whatever one editor wrote | Whatever exports cleanly to text | 17 adapters, JSONL and SQLite-backed editors |
| Shape of the data | Raw JSONL, or an app's private SQLite | Chunks and embeddings | Typed rows: messages, sessions, tool calls |
| Answering a question | grep, if you remember which folder | Approximate recall, no exact filters | SQL and full-text — exact, filterable, joinable |
| A second machine | A second pile of files | Re-export and re-index it | Install there too, same credential — it ships into your rooms, tagged with a stable host |
| Sharing with a teammate | Paste a transcript | Copy the whole index, or nothing | One GRANT — theirs stays theirs |
02 / Alternatives
Agent memory is a crowded field, and the projects in it do not disagree about the goal — they disagree about where you tap the stream. That choice decides everything downstream: what gets captured, whether your existing history counts, and who your prompts pass through on the way to the model.
| Nothing the files as they ship |
Proxy at the model boundary | Memory framework vector / graph store |
memhouse | |
|---|---|---|---|---|
| Where it taps in | Nowhere — each app writes its own files | In front of the LLM API; you point agents at it | SDK calls you add to your own app | Reads the session files each editor already writes |
| Your history from before you installed it | On disk, unread | Gone — only new traffic is seen | Gone, unless you write an importer | Ingested — that is the whole point |
| What it can see | Everything, unusably | The model conversation | Whatever you remembered to pass it | Tool calls, timing, project, model, cost — what actually ran |
| Where your prompts travel | Nowhere | Through the proxy, then upstream | To an embedding API, usually | Nowhere — parsing is local, rows go to your ClickHouse |
| Infrastructure it adds | None | Several services to run and keep up | A vector DB, often a graph DB too | One ClickHouse — or the one you already run |
| Who enforces isolation | The filesystem | Application code, over an app-level store | Application code, over an app-level store | The database — GRANT and currentUser(), not a service you have to trust |
| Cost of coverage | — | One integration catches every agent | One integration, but only your own app | 17 adapters to maintain — deliberately paid |
03 / How it works
memhouse is a house — a ClickHouse database — plus one resident: the shipper. Everything else in the system only reads, which is why there is so little of it.
The shipper runs all 17 adapters locally — JSONL transcripts and the private SQLite databases Cursor, Zed, Goose, OpenCode and Antigravity keep — and emits typed rows. Nothing raw leaves the disk.
messages, sessions, tool_calls as real columns, plus one
extra JSON escape hatch so no adapter field is ever lost. Incremental by default;
a re-ship of a grown session replaces the stale rows instead of duplicating them.
A dashboard for eyes, memhouse search for terms, plain SQL for everything else,
and a skills payload so an agent queries its own past sessions before it claims it doesn't know.
04 / Who it's for
Same install, same rooms. The only difference is who holds a grant.
You switch editors. You switch machines. You do not switch memory — point each machine at the same house with the same credential and every session lands in the same three rooms, each row tagged with the machine it came from. "How did I fix that" is then one query, not an archaeology dig through four app directories on two laptops.
memhouse deploy --local stands up a loopback-bound ClickHouse in one command.
A team's house is a database — team_a.messages, team_a.sessions,
team_a.tool_calls — and every housemate's shipper writes into the
same tables with their own credential. The server stamps
user_id on every row; the install fingerprint stamps host.
memhouse invite alice mints the user, the database and the grants, and writes a portable env file — the admin credential is never stored, and Alice runs one memhouse install --env to be a full owner of her own house.WHERE user_id = 'alice' is one person.ALL reaches nothing outside it.memhouse share opens a SELECT-only window into your house for a housemate who should see it, not own it.
Provenance is two columns, and neither can be faked by a client.
user_id is MATERIALIZED currentUser() — computed by the server
during the insert, with async inserts pinned off on the user so the stamp cannot be
skipped. host is a fingerprint minted once per install, so two laptops with
the same hostname stay two machines and renaming one does not split its history.
WHERE user_id = 'alice'. One machine:
WHERE host = 'macbook-4127a95b'. The whole house: no filter.origin; the
shipper's clear binds it, and imported history is never its to delete.user_id
however it labels itself — a client-supplied tag is a label, not provenance. Give each
person (or each agent) their own credential and the server does the rest.
# the fast way — mints user + database + grants, no admin credential stored $ memhouse invite alice --url https://ch.example.com:8443 \ --admin-user root --admin-password ***** ✓ alice.env written — hand it to alice, she runs: memhouse install --env alice.env # or the statements a DBA already knows, by hand $ CREATE DATABASE team_a; $ CREATE USER alice IDENTIFIED BY '…'; $ GRANT ALL ON team_a.* TO alice; # repeat per housemate # everyone ships into the same rooms; the server says who wrote what $ SELECT user_id, host, count() FROM team_a.messages $ GROUP BY user_id, host; alice macbook-4127a95b 64258 bob thinkpad-0fc96a7c 64258
05 / What you get
Because it is one.
Physical columns for what other tools derive at query time, a tool_calls table most don't have at all, and one extra JSON field per table so unnormalized adapter data is never dropped.
memhouse search <terms> across every session you have ever had, on any machine, in any editor — ClickHouse text indexes, not a grep over files that may already be rotated away.
memhouse resume <session-id> prints the command that reopens that conversation in the editor it came from, working directory included — Claude Code and Codex both scope their session lists by it. It prints rather than runs: a resume from the wrong directory does not fail, it quietly starts a new session, and the transcript you were after is still gone.
Everyone in a house writes into the same three rooms, and the server stamps every row with who wrote it — attribution no client can fake. Groups that should not see each other get separate houses; a house's ALL reaches nothing outside its own database.
Every machine you own ships into the same rooms, and each install stamps its rows with a fingerprint written once — so WHERE host = … tells the desktop from the laptop. It is random rather than derived from the hostname, because two machines that both answer to MacBook-Pro would otherwise share one identity, and renaming a machine would cut its history in half.
Every command is dual-mode: interactive for humans, --yes / flags / --json for agents. An agent can install its own memory and its own skills, unattended.
A cost engine over per-model token sums, and a dashboard showing sessions, spend, model mix, peak hours and tool usage across all your agents at once.
memhouse service install writes a real user service — systemd --user on Linux, a launchd agent on macOS — and takes over from the pidfile daemons.
memhouse update does the two thirds a bare npm i -g leaves undone: it re-passes the native-binding flag, restarts the daemons, and checks whether your house is missing a column the new shipper needs. The daemons also notice on their own — each compares the installed version against the one it booted with and hands over, exiting under systemd or launchd so the supervisor restarts it, re-executing itself when nothing is supervising. A shipper here once ran for a day and a half out of a directory that had been moved.
06 / Where the memory lives
memhouse does not embed a database and does not run a cloud you have to trust. Point it at a ClickHouse — or let it stand one up for you.
| Path | What it runs | Who it's for |
|---|---|---|
| deploy --local | Stock ClickHouse in docker or podman, bound to loopback. Since 0.4.2 onboard offers it when discover has just proved there is nothing to point at, instead of demanding a URL you do not have. It labels what it creates and refuses to touch a container or volume it did not create, so a name collision costs you an error rather than somebody else's data. |
One machine — or several members later. |
| kernel install | An agency house on an ultimagent kernel: house, owner, credential, and members provisioned with grants on their own rooms. | A team, provisioned centrally. |
| your own | Any reachable ClickHouse — a server you run, ClickHouse Cloud, whatever you already pay for. | You already have one. |
A re-ship that comes back shorter used to replace the old rows — collapse a longer parse
down to a shorter one, permanently. Rows now carry an epoch: unchanged sessions stay on
the epoch they were on, a session that changed writes under a new one, and the old epoch is kept,
not overwritten. Reads default to the current epoch; nothing is gone.
memhouse migrate runs whatever this
house still needs — copy, atomic swap, nothing deleted — and --dry-run shows the
plan first.memhouse relocate --to <url> copies the whole house
server-to-server and repoints the install; the shipper does not re-ingest, and the source is
left untouched until you say otherwise.# see what a migration would do before it runs $ memhouse migrate --dry-run sessions: schema 2 → 3, adds `origin`, `epoch` plan: copy → atomic swap → keep sessions_pre_epoch $ memhouse migrate --yes ✓ house record: schema 3, no migration pending # moving to a new ClickHouse — copy, then repoint $ memhouse relocate --to https://new.example.com:8443 --yes
07 / The CLI
Config resolves flags > MEMHOUSE_* env > ~/.memhouse/env > defaults.
# install $ npm install -g memhouse # no ClickHouse yet? onboard offers to stand one up $ memhouse deploy --local # the wizard: discover → configure → ship → start $ memhouse onboard ✓ 17 adapters · 9 with sessions on this machine ✓ sessions shipped · dashboard on http://localhost:4640 # then, any time $ memhouse search clickhouse ttl $ memhouse stats $ memhouse status --json $ memhouse doctor
discover is a read-only preflight: which editors
it found, how many sessions are sitting on this disk, and which ClickHouses it can reach. It writes nothing.
npx -y memhouse discover
discover report zero editors — one absent native module threw before any adapter ran. Since 0.4.3 the eleven adapters that need no native code work regardless, and the five that do are named as skipped.doctor diagnoses the whole pipeline, and both it and discover say out loud when the binding is missing rather than leaving you to infer it from a low number.08 / For agents
A house full of past sessions is only worth it if the next session can read it. memhouse ships the delivery kit that makes that the default behaviour rather than a thing you remember to do.
"Did we ever solve this before?" — asked before the answer is re-derived, not after.
/mem:ask answers from memory, /mem:search and /mem:sessions find and browse, /mem:sql runs read-only analytics, /mem:status and /mem:users report on the house, and /mem:invite / /mem:share bring in or read from a housemate — an agent can onboard a teammate without an admin at the keyboard.memhouse plugins install claude drops them into every Claude Code config directory on the machine — ~/.claude, whatever CLAUDE_CONFIG_DIR points at, and each playbook under ~/.claude-playbooks/ — all selected by default, because a skill installed into only one of them is missing from the instance you actually work in, and a missing skill never announces itself. onboard offers this at the end.memhouse prompt prints the system-prompt block that tells an agent to search memory before claiming ignorance.memhouse prompt --install probes for a house, a container engine and an existing config, then states the one route that applies — including "already installed, do not reinstall" and "configured but the house does not answer, diagnose before repointing". An agent gets an instruction, not a decision tree to walk and a branch to pick wrong.user_id and host, so "whose session was this" is a WHERE clause, not a guess.# give an agent its own memory, unattended $ memhouse install --yes \ --url https://ch.example.com:8443 \ --user alice --password ***** --db mem ✓ rooms ready: sessions, messages, tool_calls in mem ✓ verified as alice $ memhouse plugins install claude ✓ skills installed: ask · hello · invite · search · sessions · share · sql · status · users $ memhouse prompt >> CLAUDE.md # or hand an agent the install itself $ memhouse prompt --install
One command to see what every agent on your machines has actually been doing — and to let the next one read it.
npm install -g memhouse
09 / Questions
Only what you point it at. Parsing happens locally, and the shipper writes typed rows to the
ClickHouse you configure — which can be a container on the same laptop
(memhouse deploy --local, bound to loopback). There is no memhouse cloud and no phone-home.
No. npm install -g memhouse with nothing after it is the complete install, and
nothing in it compiles.
It was not always so. Five adapters — Cursor, Goose, OpenCode, Zed and Antigravity — read
sessions out of SQLite files, and that used to mean better-sqlite3, whose native
binding is built by an npm install script. npm 12 blocks install scripts by default, so the install
needed --allow-scripts=better-sqlite3 — a flag npm never remembered, that had to be
re-typed on every upgrade, and whose omission cost you those five editors with no error at all.
Up to 0.4.3 it was worse still: the missing module threw at import time, before any adapter ran, so
discover reported zero editors on a machine that had plenty.
SQLite now comes from node:sqlite, which is part of Node itself and stable in 24 —
hence the Node 24 floor. There is no binding to build, no flag to forget, and no way for an adapter
to go dark for want of one.
Upgrade with memhouse update, and read the refusal it prints. 0.4.4 fixed a
data-loss bug that matters if you ever
imported history into your house: a ship pass clears and re-inserts a session's rows so a
shorter re-parse cannot leave a stale tail, and that clear was scoped to the session alone — so it
removed rows the adapters never wrote and cannot rewrite. Measured on a real house of 135,307
imported messages, one pass took a source from 126,098 rows to 98,150, and would have continued a
pass at a time.
Rooms now carry an origin column, and it is in the sorting key — guarding
only the delete leaves ReplacingMergeTree to collapse an imported row against a shipped one sharing
the same key, which is the same loss reached through the merge. ORDER BY cannot be
altered in place, so a house built earlier needs its rooms rebuilt; the shipper refuses to write
into one and prints the steps, and it checks on every pass rather than at schema time. A house with
no imported rows can simply be re-shipped from scratch.
EACCES?Check ls -ld "$(npm prefix -g)" — the prefix root is what decides, not the path
and not the failing file. Owned by root: a system-managed Node, so re-run with
sudo. Owned by you (Homebrew, fnm, nvm, volta): the prefix is yours and the root-owned
file npm tripped on is a stray from an earlier sudo npm — another sudo just
adds more. Fix that one path with chown instead.
memhouse doesn't chunk and embed — it parses. What lands in the house is typed rows you can filter, aggregate and join: by project, by model, by editor, by cost, by tool call. Full-text search is a ClickHouse text index over those rows, so a query is exact rather than approximate, and the same data also answers "what did I spend on Opus last month".
Sharing is the design, not a feature. A team's house is one database; every housemate's shipper writes into the same tables, the server stamps who, the fingerprint stamps which machine, and the dashboard shows the whole house with no configuration. Groups that should not see each other run separate houses — the database is the boundary.
Seventeen adapters, covering: Claude Code, Codex, Cursor, Cursor Agent, VS Code, VS Code Insiders, GitHub Copilot (CLI and JetBrains), Gemini CLI, Devin, Devin Next, Zed, Goose, OpenCode, Kiro, Antigravity, Codebuff, Command Code and GSD Workflow.
The adapters, dashboard and cost engine are built on agentlytics by Fatih Kadir Akın (MIT); the repo's history carries the full lineage. memhouse adds the typed store, per-member rooms, the shipper and the agent delivery kit. It is part of the Ultimagent constellation, where it installs as an agency.