Skip to main content
Glama
ClickHouse Loom

ClickHouse Loom

Official

Loom dashboard showing shared agent memories, a pinned fact and flagged conflicts

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, .docx and .pptx files the extractor service converts. Knowledge documents

  • A 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/local namespace. 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.sh

Run 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.

local (bge-small on CPU): semantic retrieval, weaker than a hosted model.

LLM

Provider, model and API key.

fake-echo-1: no meaningful extraction, summaries or answers.

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 loom

A 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@loom

Start 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:

Add Loom to Cursor

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.

Langfuse

Request traces, model costs, retrieval scores and prompt-version attribution.

ClickStack / HyperDX

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 gpt-5 reader (500 questions)

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

Apache-2.0 · License · Notice

Available Tools

13 tools
loom_activateA
Read-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_addA
Idempotent

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_packA
Read-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_deleteA
DestructiveIdempotent

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_getA
Read-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_historyA
Read-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_pinA
Idempotent

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_setA
Idempotent

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.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 13 tools

    Disambiguation3/5

    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.

    Naming Consistency4/5

    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.

    Tool Count5/5

    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.

    Completeness4/5

    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

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers