ClickHouse Loom
OfficialLoom is a shared long-term memory and retrieval MCP server for AI agents, with tools to store, manage, search and activate agent context.
Save facts, preferences, decisions and current state as shared memories, with tags, confidence, TTL/expiry, sensitivity and provenance.
Fetch a memory by exact key, update existing facts, and view full version history.
Pin standing facts and lock memories against changes or deletion.
Delete/tombstone memories and bulk-preview/apply changes across sets of memories: delete, expire, lock, label or demote.
Search across memories, profile, knowledge documents and past conversations, with paging, tags, retrieval recipes and ranking explanations.
Build startup/task/debug/handoff context packs containing profile, relevant memories, procedures, docs, decisions and recent runs.
Activate Loom to load always-on persona/preferences/goals and route later memory operations through it.
Add blacklist patterns to refuse future memory writes and optionally sweep existing matches.
Run additional Loom operations through a generic op interface.
Supports OpenAI as an LLM and embedding provider, enabling AI-generated extraction, summaries, answers, and semantic retrieval; also supports OpenAI-compatible embedding endpoints.

Loom is an open-source, self-hosted MCP server that gives your agents long-term, shared memory: what one agent learns in a session, the next agent or tool can find.
Agents connect through MCP, HTTP, Python or TypeScript. Self-host with Docker or on Kubernetes with the Helm chart; agent context and index data live in ClickHouse, and a deployment can choose Postgres at first boot for live memory and knowledge state. The dashboard brings memories, repositories, tasks and runs into one place.
What Loom is for
Memory shared across your coding agents, with plugins that load context at session start and capture the session as it runs. Connect a coding agent
Long-term memory for an agent application, per user, from the Python or TypeScript SDK. Your first agent
A multi-user MCP memory server with namespaces, roles and tokens. Multi-user mode · Namespaces and sharing
A code index of your repositories that coding agents query. Code index
Document search with citations, including PDF,
.docxand.pptxfiles the extractor service converts. Knowledge documentsA record of agent work, with searchable conversations (when recording is on), session briefs to resume from, and runs with their goals, outcomes and model costs when supplied. Conversations · Runs
Loom is not an in-process library for one agent's short-lived memory, nor an agent framework. When Loom does not fit
Related MCP server: mcp-clickhouse
Measured results
Evaluation | With Loom | Without Loom | Efficiency gain | Scope |
Coding-agent answer quality | 0.84 | 0.78 | 20% lower model cost | 24 questions × 3 paired runs |
Coding-agent file localization | 96.4% | 91.4% | 21% lower model cost | 36 questions × 10 paired runs |
Memory vs full-history answers | 94% | 90% | 91% fewer reader prompt tokens | 100 paired LongMemEval-S questions |
Both coding setups use Claude Code (claude-sonnet-5) on the ClickHouse codebase;
cost is the whole session's model bill, excluding Loom's server-side model calls, hosting
and indexing. Answer quality
measures coverage of a verified answer's key points, with a contradiction penalty.
File localization counts the right file among three named files.
The memory row compares retrieved memories with full conversation history using
the same gpt-5 reader. Token savings exclude memory construction; the 94% vs 90%
accuracy difference is descriptive, not statistically established.
Separately, Loom's memory.ask configuration scored 96.6% answer accuracy on all
500 LongMemEval-S questions. This is a product evaluation with its own configured
answer models, not the fixed-reader comparison above.
Results are specific to the tested workloads.
Protocols and reproducible artifacts · detailed results · evaluate on your workload
Get started
1. Choose your Docker setup
Personal use—the default: no login, localhost-only access and a
dev/localnamespace. One person can connect multiple agents.Team use: authenticated accounts and namespace access controls. Start in multi-user mode from the first boot.
Both modes support the same Loom features, including configured LLMs and embeddings. Single-user is not a reduced-feature edition. You can also switch an existing installation without deleting its data; grant new accounts access to the existing namespaces.
For the default personal installation, with Docker installed:
curl -fsSL https://github.com/ClickHouse/loom/releases/latest/download/install.sh -o loom-install.sh && sh loom-install.sh && rm loom-install.shRun it from your home directory, or another directory only you can write to. The installer starts Loom and ClickHouse, applies migrations and prints the dashboard URL: http://localhost:7777/. The image includes the server, workers, dashboard and Python, TypeScript and Go compiler indexers; TypeScript dependencies are prepared automatically. Quickstart and pinned releases
The installation lives in ~/.loom (LOOM_INSTALL_DIR chooses another directory): the
Compose file and the startup settings in ~/.loom/.env. Running the install command again
upgrades in place and keeps your .env settings.
Keep single-user mode localhost-only. For shared access, follow the team deployment guide, including TLS.
2. Configure models and API keys
The stock installation boots without keys or network. Storage, the dashboard, structural code indexing and semantic search work, on a small local embedding model the image carries; the fake LLM gives no AI-generated answers.
Component | Configure for real use | Without configuration |
Embeddings | Provider, embedding model, dimensions and API key. |
|
LLM | Provider, model and API key. |
|
Open Settings → Models & keys, choose both providers and models, enter their
keys, then click Test & apply. No Docker restart is needed; changing the embedding
space rebuilds the existing index. The dashboard's embedding choices are hosted
providers: to keep local and add an LLM, set the LLM lines in ~/.loom/.env instead
(Models and keys).
One OpenAI API key can serve both roles. Claude needs a separate embedding provider. OpenAI, Gemini and OpenAI-compatible embedding endpoints are supported. Your agent's CLI login or subscription does not supply Loom's provider keys.
Prefer environment configuration? Edit ~/.loom/.env, then recreate the server:
docker compose --project-directory "$HOME/.loom" up -d --force-recreate loomA key alone does not change the pinned models (the local embedder and the fake LLM); set
the provider and model too.
Saved dashboard settings take precedence over .env. Follow
models and API keys for provider recipes
and health checks.
Everything else is in one file. loom.example.yaml lists
every setting with its default and what it does, by area: the ClickHouse connection,
timeouts and budgets, rate limits, workers, and the *_enabled switches that turn
features on and off. Set any of them as LOOM_<NAME> in ~/.loom/.env and recreate the
server; a deployment that mounts a config file can set them in a copy of this file
instead (how settings are read). One marked
live also changes while Loom runs: loom settings set <name> <value>, or Admin → Deployment
settings. loom settings list --changed shows what differs from the defaults and
where each value came from (runtime settings).
3. Connect your agents
On the machine running a personal installation, the plugins need no token:
# Claude Code
claude plugin marketplace add ClickHouse/loom
claude plugin install loom@loom
# Codex
codex plugin marketplace add ClickHouse/loom
codex plugin add loom@loomStart a new session; the first one connects to http://localhost:7777. In Codex, run
codex interactively once and approve the plugin's hooks; until then only the tools
work. Tell the agent something worth keeping, then ask about it in the next session.
For Cursor on the machine running a personal installation, the button below adds the local MCP entry, which needs no token:
For a team server, pass --config server_url=… --config token=… to Claude Code, or
point Codex at it as described in
connect a coding agent. Create
tokens in Settings → Tokens. Cursor and other MCP clients, and
the SDK, are covered there too.
Why Loom
Memory that carries forward. Share decisions and findings across agents and sessions. Each fact records who wrote it, version history and conflict review make corrections traceable, and a namespace can be rolled back to any point in time. Memory
Code intelligence with evidence. Find definitions, callers, change impact and relevant tests; connect code to commits, pull requests and issues. Results identify their revision, source location and completeness. Code index
Grounded retrieval. Search permitted memories, documents, conversations and tracker records. Answers cite their sources; low-confidence retrieval can abstain. Retrieval
Multi-agent handoffs. Assign work through durable task inboxes with leases, retries, review rules and outcomes. Your agents execute the workflow; Loom preserves the handoffs. Task queues
Control over agent data. Manage namespace access, provenance, retention, audit records and verified erasure. Context remains queryable in ClickHouse. Security
Integrations
Integration | What it connects |
Claude Code, Codex, Cursor and other MCP clients (all integrations) | Memory and code intelligence in your existing agent workflow. |
Python, TypeScript and HTTP | Your own agents and applications. |
Request traces, model costs, retrieval scores and prompt-version attribution. | |
OpenTelemetry traces, logs and metrics in ClickHouse. |
Langfuse and ClickStack are optional integrations, not bundled services in the Docker stack.
Benchmark details
Detailed retrieval, reader and code-search results
Evaluation | Result |
LongMemEval-S evidence retrieval (500 questions) | 98.6% of questions with at least one supporting session in the first 30 memories. |
LongMemEval-S fixed | 92.0% answer accuracy from the top 200 memories. |
LoCoMo (all 10 conversations) | 89.6% answer accuracy; 90.8% correct abstentions on adversarial questions. |
MemoryAgentBench fact-question sample (500 questions) | 99.0% SubEM; 95.7% token-overlap F1 on generated answers. |
Conceptual code search (36 questions) | Gold-file recall 91.2% at 5 / 97.2% at 10; MRR 0.91. |
SWE-bench Verified file localization (200 issues) | Issues with a changed file in the first 10: 82% vs 57% for BM25; 76.5% in the first 5. |
Tracker duplicate detection (104 pairs) | Recorded older duplicate ranked first 87.5% of the time. |
These measure different tasks, not one aggregate score. SWE-bench measures file localization, not issue resolution. The results ledger includes scoring definitions, small-sample evaluations, weaker and negative findings, and the full protocols, models and artifacts.
Methodology · coverage and known gaps · run the benchmarks
Documentation and contributing
For shared agent context, code intelligence and permissioned retrieval, start with the documentation. For deployment requirements, see the prerequisites.
Deployment · FAQ ·
Contributing · Security reporting ·
llms.txt
Available Tools
13 toolsloom_activateARead-onlyIdempotent
Loom activation — call this tool IMMEDIATELY, without any other tool calls or clarifying questions, whenever the user says any of:
'use loom'
'activate loom'
'switch to loom' / 'switch to loom mode'
'loom on' / 'loom first' / 'enable loom'
'use loom from now on'
Do NOT call tools/list or browse other Loom tools first. Do NOT ask 'what would you like to look up?' These phrasings are activation triggers, not search queries. Take no arguments.
The response is a primer with the user's always-loaded context (persona / preferences / goals) inlined as a <block> fragment, plus instructions for routing subsequent memory and recall calls through Loom. Internalize the primer and apply it for the rest of the conversation: adopt the tone implied by the persona block, route future memory ops to Loom, do not re-fetch the primer.
Calling this is a one-shot setup gesture for the conversation. Subsequent user messages should be answered using the primer's guidance — not by calling this tool again.
Inputs: no named parameters.
loom_blacklist_addAIdempotent
WHAT: Every later memory write in the pattern's scope (this namespace by default) whose value matches pattern is refused. Refused to a token bounded to one user: a pattern refuses every user's writes.
WHEN: An explicit 'never remember X' or 'don't store anything about Y'. Pass sweep=true only when the user also asks to forget what is already saved: it tombstones the matching rows up to the sweep cap, other writers' too. swept_truncated: true or a non-empty sweep_error means it did not finish: tell the user, and do not add the pattern again.
WHEN NOT: Forgetting one saved fact is memory.delete.
ARGS: pattern; optional pattern_type (substring | regex); scope: namespace (default), org (needs org admin) or global (needs platform admin); reason; sweep.
Inputs: pattern (required), scope, sweep, reason, +3 more.
loom_context_packARead-onlyIdempotent
Startup context pack for this session, once near the start of a substantial task: profile, relevant memories, matching procedures, optionally docs/conversations, in one prompt-sized bundle. include_runs=true adds a manifest of your recent sessions and runs (goal, outcome, tool calls, files) — shortlist there before opening one with memory.evidence or memory.digests (callable by name). For one fact, prefer loom_search; for activation only, loom_activate.
Inputs: mode, query, top_k, run_id, +11 more.
loom_memory_applyA
WHAT: One change over many EXISTING memories, previewed first; it never creates one (save facts with loom_memory_set, one per fact). target names exactly one of keys | filter (tags_any/tags_all, memory_type, key_prefix, created_before/after, confidence_lte, source_trace) | search {query}; limit is required for filter/search; action is delete | expire | lock | label | demote with its block (expire {ttl_seconds|until, on_expire}, lock {mode}, label {tags, mode add|replace|remove}, demote {confidence_factor | confidence_delta}); reason is required. dry_run defaults to TRUE and lists every row it would touch (would_apply / refused); call again with dry_run=false to write. A locked row comes back as status 'refused', never an error. WHEN: anything that names a set rather than one key — 'forget everything about the Q3 migration', 'these are getting stale, lower their confidence', 'tag every runbook as ops', 'lock everything I saved about the incident', 'expire the meeting notes in 30 days'. WHEN NOT: One key (memory.delete / memory.set / memory.lock). A whole agent or user (memory.clear, admin, via loom_op); a user's request to delete all their data is erasure.request (via loom_op where it is not listed). Never skip the dry run when the user has not seen the list.
Inputs: action (required), reason (required), target (required), lock, +7 more.
loom_memory_deleteADestructiveIdempotent
WHAT: Tombstone a memory by key (14-day recovery in the dashboard). The row is found across every agent and session in the namespace; found: false = no live row — tombstoned: false is never success.
WHEN: Explicit user request — 'forget that I…', 'delete X in Loom'.
WHEN NOT: Not as cleanup or on inferred intent; to fix a wrong memory, memory.update it with the corrected value.
ARGS: memory_key. Server fills identity.
Inputs: memory_key (required), run_id, namespace.
loom_memory_getARead-onlyIdempotent
WHAT: Fetch one memory by exact memory_key.
WHEN: The user names a key explicitly ('what is foo_bar in Loom?', 'show me the value of user_employer'). The response shape includes found: true/false + value.
WHEN NOT: Don't call for natural-language recall — that's loom_search. On found: false, fall through to loom_search; the key might have a slightly different name.
ARGS: Just the memory_key. Server fills the identity tuple. fields: [value, tags, status, …] returns only those output fields (plus found) when the whole row is not needed. follow: true serves the live canonical of a duplicate row (resolved_from = the key asked for, canonical_key = the key served).
Inputs: memory_key (required), as_of, fields, follow, +4 more.
loom_memory_historyARead-onlyIdempotent
WHAT: Every recorded version of one memory_key, newest first, tombstoned and redacted versions included; empty when the key was never written. WHEN: 'what did I say about X before', 'when did this change', or what a key held earlier. WHEN NOT: The current value alone is memory.get. ARGS: memory_key; optional limit (default 50).
Inputs: memory_key (required), limit, run_id, namespace.
loom_memory_lockA
WHAT: Lock one memory against a class of writes. read_only refuses a changed value, a changed expiry and deletion; no_delete lets the value change but refuses tombstone/redact/clear/consolidate-away. Guarded ops answer 423 LOCKED with the reason. Only an admin loosens. WHEN: 'make sure this never changes', 'freeze the deploy window', 'this must not be deleted / forgotten', 'protect this fact', 'legal hold on X', 'keep this exactly as is'. WHEN NOT: To keep a fact ahead of later conflicting writes (that is memory.pin) or to share it (memory.promote, via loom_op). Never lock on inferred intent. ARGS: memory_key, mode (read_only | no_delete), reason (required), optional until / ttl_seconds. Server fills identity.
Inputs: reason (required), memory_key (required), mode, until, +4 more.
loom_memory_pinAIdempotent
WHAT: Save a standing fact and protect it. A pinned memory is never evicted by a later conflicting write: the newer fact is stored beside it and answers 409 PINNED_CONFLICT, and pinned rows rank ahead of unpinned ones in file context. Same arguments as memory.set; pinned is forced on. WHEN: The user marks a fact as permanent or asks for it to be kept explicitly: 'always remember X', 'never forget this', 'pin this', a hard constraint stated as standing ('I am allergic to penicillin', 'deploys freeze on Fridays', 'the prod DB is read-only'). WHEN NOT: An ordinary fact, preference or state — that is memory.set, the default for almost every save; a fact that must not change or be deleted at all (memory.lock); a topic that must not be remembered (blacklist.add, refused to a token bounded to one user). ARGS: memory_key, value; optional tags, ttl_seconds, on_expire. Server fills identity.
Inputs: value (required), memory_key (required), tags, files, +16 more.
loom_memory_setAIdempotent
Save a fact to Loom — shared across every AI tool the user runs; native memory is the Claude-only scratchpad.
Call this proactively whenever the user states a personal fact, decision, preference, or current state — even without 'remember' or 'save'. Declarative statements ARE save triggers: 'my X is Y', 'I prefer X over Y', 'we decided X', 'we're at X', 'I deploy on X' — one call per fact.
Don't search before saving. A stated fact is a write, not a recall; the server dedups. Revision policy: if a read this session returned the key of a fact that has now changed or got more precise, update it under that same key with memory.update — never save a second copy and never delete + re-save. A fact about a new subject or scope gets its own memory_key (include the project / scope token in the key).
Auto-tag from scope words: 'for billing, X is Y' → tags=['billing']; same for person / topic mentions.
Do NOT save: questions, code help, world knowledge, acknowledgements, hypotheticals, echoes of your own words.
Writes land in ONE namespace (the caller's home); pass namespace=<name> only to write elsewhere. Don't override memory_type (defaults semantic); use tags for classification. Cite the response's namespace and memory_key in your confirmation; conflict_verdict, peers and next say what the store held (dry_run=true checks without writing). value is stored verbatim — never base64 (kills search; see advisories).
A fact with a natural lifetime gets one at write time. A deadline, a temporary state ('on call this week'), a rotating value: pass ttl_seconds AND on_expire (tombstone | demote | anonymize) in the SAME call — the sweep applies it when the time passes. Without on_expire the row just stops being current.
Inputs: value (required), memory_key (required), tags, files, +17 more.
loom_memory_updateA
Update a saved fact by memory_key: the operation for "the fact changed or got more specific".
WHEN: a read (memory.get or loom_search) returned this key and the user now states a new value, a correction, or a more precise version of it — 'the deploy day moved to Thursday', 'the budget is actually 40k'. It keeps the row's tags, confidence, expiry, lock and history; memory.set would write a new version blind. Revision policy: if a read this session returned the key of a fact that has now changed or got more precise, update it under that same key with memory.update — never save a second copy and never delete + re-save. A fact about a new subject or scope gets its own memory_key (include the project / scope token in the key). Do NOT use it for a genuinely new fact — call memory.set.
Resolves the key across the namespace; pass agent/user_id/session_id only to narrow when the key is live under several writers (409 AMBIGUOUS_KEY lists them). updated=false with reason=not_found means the key has no live row — save it with memory.set instead. Pass if_match_sha256 from the read for a no-clobber update. Given a memory_id rather than a key, call memory.update_by_id (via loom_op where it is not listed).
Inputs: memory_key (required), tags, value, reason, +7 more.
loom_opA
Run an op this list doesn't show (such as memory.related, callable by name). op is the dotted name, arguments its arguments; op="help" with an optional query lists matching ops and their args. Prefer a listed tool when one fits.
Inputs: op (required), query, arguments.
loom_searchARead-onlyIdempotent
Unified search across the user's Loom content — memory (facts, preferences, decisions), profile (always-loaded persona sections), knowledge (uploaded documents) and past conversations — four searches, one round-trip; under a code scope, also the repository's docs and decisions. This is THE search tool: call it before answering any 'what do I / what did we decide / have we discussed X' question.
One call per question. If the answer is in the response, use it; if it is empty, fall through to native memory rather than declaring 'nothing in Loom'. Re-call on a NEW question, never on the same question in the same turn.
Paging. memory.candidates_total is how many memories matched; memory.results is the top of that list. When memory.next_page is set AND the answer is not in hand (a list, a count), call again with page = memory.next_page to read the next slice. Stop when next_page is empty or the answer is complete.
Namespaces are selected server-side (your own memory-kind namespaces, up to a cap; code/QA indexes have their own tools); no namespace argument. Do NOT call for general knowledge, arithmetic, or code/syntax help.
Inputs: query (required), page, tags, top_k, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
The set has several overlapping write/mutate paths — memory_set vs memory_update vs memory_pin vs memory_apply vs memory_lock — that all touch stored facts, and delete vs blacklist_add vs apply(delete) all remove data. The WHAT/WHEN/WHEN NOT structure does a lot of work to separate them, but an agent must read carefully, and the descriptions refer to phantom names (memory.set, memory.update, memory.delete) that are not the actual tool names, adding a misselection hazard.
All tools are snake_case under a uniform loom_ prefix, and the memory CRUD family (loom_memory_set/get/update/delete/history) is consistently verb-last. Minor deviations exist — loom_activate and loom_search drop the resource segment while loom_context_pack is noun-only — and the bodies reference tools by a different dotted naming scheme than the actual tool names.
13 tools is well within a healthy range for a memory service covering reads, writes, bulk apply, locking, history, and session bootstrap. Each tool earns its place: no obvious duplicates that could simply be folded together.
Core lifecycle is strongly covered — create (set/pin), read (get/search/history), update, delete, bulk apply, locking, blacklist, plus a context pack and activation primer. Gaps (whole-namespace clear, erasure requests, related/evidence/digests) are handled only through the generic loom_op escape hatch, whose arguments are not documented in the tool surface.
Maintenance
Related MCP Connectors
A second brain for your AI agent. Shared memory for your team's AI agents, plus public and private knowledge bases — works with ChatGPT, Claude, Cursor and any MCP client.
Shared control plane for AI coding agents — tasks, memory, decisions, file locks. 12 tools.
Shared memory and mail for your AI agents. Verified with Claude Code; other MCP clients in testing.
Persistent memory, knowledge graph, task management and more for AI agents via MCP.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceProvides semantic memory and persistent storage for Claude, leveraging ChromaDB and sentence transformers for enhanced search and retrieval capabilities.32,064 PyPI2,110Apache 2.0

mcp-clickhouseofficial
AlicenseAqualityAmaintenanceClickHouse database integration with schema inspection and query capabilities3903Apache 2.0
Mem0 MCP Serverofficial
AlicenseAqualityFmaintenanceAn MCP server that integrates with mem0.ai to help users store, retrieve, and search coding preferences for more consistent programming practices.9662Apache 2.0
Chroma MCP Serverofficial
AlicenseAqualityDmaintenanceA server that provides data retrieval capabilities powered by Chroma embedding database, enabling AI models to create collections over generated data and user inputs, and retrieve that data using vector search, full text search, and metadata filtering.1387,391 PyPI602Apache 2.0