Quickstart
This page boots Loom on a laptop and saves a first memory over HTTP, Python and MCP. It
needs no API key and no account. A fresh install of the released stack embeds with a small
local model the image carries, so search returns meaningful hits with no network (an
upgraded one keeps the embedder its .env names); its LLM is fake, so extraction and
memory.ask answers produce nothing. A checkout's .env uses fake providers for both.
The stock Docker installation is single-user: no login, localhost-only access and
a default dev/local namespace. One person can connect multiple agents without enabling
multi-user mode. Team deployments are also supported in OSS, with authenticated
accounts and namespace access controls; start in multi-user mode
or follow Deployment → Identity modes.
Both modes support the same Loom features; real LLMs and embeddings need provider
configuration in either mode.
Prerequisites
| Need | For |
|---|---|
| Docker with the compose plugin | The stack: ClickHouse and the Loom server. |
| Python 3.12 or newer | Contributor-only loom CLI. The SDK supports 3.11+. |
| Node.js 20 or newer | The TypeScript SDK (optional). |
Free port 7777 |
Loom. Checkout development also exposes ClickHouse on 8123 and 9000. |
jq |
Reading the curl output in step 4 (optional). |
1. Boot the stack
Fast path: released image, no clone
Prerequisite: Docker Desktop or Docker Engine with Compose 2.20 or later. On Windows, run
the command in a WSL 2 terminal where docker works: Docker Desktop with its WSL
integration turned on for that distribution, or Docker Engine installed in it. The
installer is a POSIX shell script.
curl -fsSL https://github.com/ClickHouse/loom/releases/latest/download/install.sh -o loom-install.sh && sh loom-install.sh && rm loom-install.sh
The command saves the installer in the current directory and then runs that file, so run
it from your home directory, or another directory only you can write to: in a shared one,
someone else can replace loom-install.sh, or point it at another file, before it runs. A
failed download stops with curl's error and a non-zero exit; after a failed install, fix
what it reports and run the whole command again. The installer downloads
loom-stack.tar.gz from the latest GitHub Release, verifies its SHA-256, installs the
Compose and ClickHouse configuration under ~/.loom, pulls the matching
ghcr.io/clickhouse/loom:X.Y.Z and ghcr.io/clickhouse/loom-extractor:X.Y.Z (the
document extractor service) images, and waits for http://localhost:7777/v1/health. The
server image contains the API, workers, migrations and the built React dashboard at the
server root; no Python or Node installation is needed. Python, TypeScript and Go compiler
indexers are included and enabled, and so is the C++ one (scip-clang) on amd64. A
repository indexed with the TypeScript indexer gets its dependencies installed
automatically. Both images support Linux amd64 and arm64, including Docker Desktop
on Apple Silicon.
Once Loom answers, the installer:
- Checks a memory. A new install saves a test memory, reads it back and deletes it, which shows that writes and reads work and leaves no live memory (its deleted version shows under Recently deleted). A rerun after that check passed, or an upgrade, skips it.
- Learns whether sign-in is on. With sign-in on, it connects no agent and says where tokens come from.
- Offers to connect your coding agents. On a terminal it asks, for each one it finds:
Claude Code and Codex through their plugins, VS Code through
code --add-mcp.LOOM_CONNECTanswers instead, for a run without a terminal too, and only the clients it names are looked at: a comma list ofclaude-code,codexandvscode,all, ornonealone; any other value stops the install. Without a terminal and withoutLOOM_CONNECT, no agent is looked at or changed. A plugin or MCP server set up before (by the plugin,claude mcp add,codex mcp addorloom install) is left as it is; for a Claude Code plugin the installer prints the command that points it at this install. Claude Code's plugin runs the Node.js 18 or later on PATH, so without one the installer installs no plugin and prints theclaude mcp addline for the MCP server alone; Codex's finds Node.js through nvm and the like too, so it is installed, with thecodex mcp addline beside it. Codex's plugin always useshttp://localhost:7777, so for an install elsewhere it gets a link to its setup page instead. VS Code is not offered in a remote session or WSL. A clientLOOM_CONNECTnames that is not on PATH is named. - Opens the dashboard on a desktop, for a new install;
LOOM_NO_OPEN=1skips that.
Run from WSL, these steps act inside WSL: connect an agent that runs on Windows from Windows with the connect guide, and open the dashboard address the installer prints in a Windows browser.
Ctrl-C stops the run at any step; a clone an agent CLI started for its plugin may still finish in the background.
The stack runs its own ClickHouse container. To use a ClickHouse you already run, a
self-managed server or a ClickHouse Cloud service (26.4 or later, with a Keeper), pass its
URL; the installer asks for the password when the URL has none, creates the loom
database and a read-only reader user, or prints the SQL an admin has to run
(choosing the ClickHouse):
curl -fsSL https://github.com/ClickHouse/loom/releases/latest/download/install.sh -o loom-install.sh \
&& sh loom-install.sh --clickhouse https://loom@abc123.us-east-1.aws.clickhouse.cloud:8443 && rm loom-install.sh
Each release's install.sh installs that release, so the copy latest serves installs the
latest release, and a release's own URL, below, installs that release every time. Over a newer
install, an older release's copy refuses to install, rather than start an older Loom and
ClickHouse on newer data; to downgrade on purpose, follow
downgrading an installer install.
To pin a release, use its own URL:
curl -fsSL https://github.com/ClickHouse/loom/releases/download/v1.2.3/install.sh -o loom-install.sh && sh loom-install.sh && rm loom-install.sh
To inspect everything before starting, download the files and check them:
curl -fLO https://github.com/ClickHouse/loom/releases/latest/download/loom-stack.tar.gz
curl -fLO https://github.com/ClickHouse/loom/releases/latest/download/loom-stack.tar.gz.sha256
curl -fL https://github.com/ClickHouse/loom/releases/latest/download/install.sh -o loom-install.sh
sha256sum -c loom-stack.tar.gz.sha256
tar -tzf loom-stack.tar.gz
Then, once what they print looks right, install from the checked bundle:
LOOM_INSTALL_BUNDLE="$PWD/loom-stack.tar.gz" \
LOOM_INSTALL_CHECKSUM="$PWD/loom-stack.tar.gz.sha256" sh loom-install.sh && rm loom-install.sh
LOOM_PORT, LOOM_BIND_HOST, LOOM_IMAGE_REPOSITORY, LOOM_EXTRACTOR_IMAGE_REPOSITORY
and LOOM_CLICKHOUSE_IMAGE given to the installer are written to the install directory's
.env, so later docker compose commands there and later installer runs use the same
port and images. A LOOM_BIND_HOST other than loopback publishes Loom beyond this host,
and the server refuses to start that way without LOOM_PUBLIC_BASE_URL and sign-in:
install with LOOM_SKIP_START=1, set them in .env (below), then run the install command
again without LOOM_SKIP_START=1.
Each release runs the ClickHouse release that was latest when it was built, so running the
install command again for the same Loom version keeps its ClickHouse; installing a newer
Loom version moves it, unless .env pins LOOM_CLICKHOUSE_IMAGE (delete that line to
follow the releases again). Running the install command again updates the managed
Compose/configuration files and reconciles the containers. It preserves ~/.loom/.env,
adding the extractor's URL and a token to one without, and the named Docker volumes. To
stop without deleting data:
docker compose --project-directory "$HOME/.loom" down
Add --volumes only for an intentional, irreversible reset.
Try it in GitHub Codespaces
The repository's dev container runs the latest release in a codespace, with nothing on
your machine: it installs Loom with the installer above, starts it, and connects Claude
Code to it. The first start downloads about 3 GB of images and takes a few minutes. Open
the dashboard at the address the creation log's last lines print (Loom is running in the
codespace. Dashboard: https://<codespace name>-7777.app.github.dev, the Ports tab's
forwarded address for port 7777); the installer's http://127.0.0.1:7777/ above it is the
codespace's own address. In the codespace's terminal, run cd ~ && claude (outside the
checkout, whose instructions are for working on Loom itself), sign in, and say "Remember
that I never deploy on Friday"; in a new claude session, ask "When can I deploy?".
The try-out runs single-user, with no sign-in. In the browser, port 7777 is forwarded
privately: its address answers only your GitHub account. Keep it private: public, or
private to an organization, opens a server with no sign-in to everyone it is shared with.
Agents inside the codespace, such as its claude, use http://localhost:7777; the
dashboard's connection lines name the forwarded address instead, which needs a GitHub
sign-in that agents in the codespace do not have. Opened in desktop VS Code, the codespace
also forwards the port to your laptop's localhost:7777, where it is as open as a local
install to every program on the laptop, and agents on the laptop can reach it there; the
forward needs that local port free, so stop a local Loom first. Use the dashboard from the
browser address: from the laptop's localhost:7777 the page opens, but the server refuses
its requests, so it shows no data; only the forwarded address is this server's own page.
The install lives in /workspaces/.loom, so a rebuilt codespace keeps its .env and
Compose project, though its memories live in Docker volumes a rebuild may not keep; a
rebuild also runs the latest release's installer over it, which moves it to that release.
Wherever these docs name ~/.loom or "$HOME/.loom", a codespace uses
/workspaces/.loom. Codespaces offers to open port 7777 as soon as it listens; the
dashboard answers once the creation log prints "Loom is running in the codespace". If the
log ends with an error instead, run sh .devcontainer/start-loom.sh in the codespace's
terminal: it keeps what the first run set up.
Start in multi-user mode
For a fresh released-image installation with accounts and sign-in from the first boot, prepare the stack files without starting containers:
curl -fsSL https://github.com/ClickHouse/loom/releases/latest/download/install.sh -o loom-install.sh \
&& LOOM_SKIP_START=1 sh loom-install.sh && rm loom-install.sh
Edit ~/.loom/.env before starting Docker. Replace the existing identity and auth entries,
and add the signup setting:
LOOM_IDENTITY_PROVIDER=trust-network
LOOM_AUTH_REQUIRED=true
LOOM_SELF_SIGNUP=open
Keep the generated COMPOSE_PROJECT_NAME and LOOM_JWT_SECRET. Then pull and start
the stack (with your own ClickHouse, run ch-setup first, as the installer said):
docker compose --project-directory "$HOME/.loom" pull
docker compose --project-directory "$HOME/.loom" run --rm loom loom ch-setup # your own ClickHouse only
docker compose --project-directory "$HOME/.loom" up -d
After the health checks pass, open http://localhost:7777/ and
sign up. SDK and MCP clients need tokens; use the org and namespaces granted to your
account, not the single-user dev/local defaults in the examples below.
This enables multi-user authentication, not public networking: the stack still binds to localhost. Before sharing it with a team, configure operator access and TLS. Configure real models and API keys as well; selecting multi-user mode does not replace the fake LLM.
For an already running single-user stack, follow the switching instructions instead; container recreation preserves the existing data volumes.
Contributor path: build a checkout
Prerequisite: Docker Compose 2.34 or later, which the checkout's compose file needs to pull ClickHouse daily.
git clone https://github.com/ClickHouse/loom && cd loom
python3 -m venv .venv && . .venv/bin/activate
pip install -e .
loom up
loom up does three things:
- Creates the two files git does not track:
.envfrom.env.example, anddeploy/clickhouse-config/named_collections.xmlfrom its template. It never overwrites either; it gives an existing.envwithout an extractor token one. - Runs the dev compose stack with
--build. - Polls
http://localhost:7777/v1/healthuntil the server is ready, printing the pending-migration count. It gives up after--timeout(300 s) with the last health body and log lines.
The first contributor boot builds the image and applies every schema migration, so allow
a few minutes. loom up then prints the dashboard URL (http://localhost:7777/), a curl
call and a Python snippet that save a first memory, and a line that wires Claude Code.
When auth is on, it also mints a writer token and puts it in those snippets.
| Flag | Does |
|---|---|
--rebuild |
Rebuild after a code change. |
--no-build |
Reuse the cached image. |
--open |
Open the dashboard. |
--port |
Use another Loom port. |
--env-file |
Point at another env file. |
Re-running loom up on a running stack only re-checks health. make dev does the same
without the CLI and runs:
docker compose --env-file .env -f compose.yaml -f dev/docker-compose.dev.yml --profile bundled up -d --build
With compose.yaml first, Compose reads the repository root's .env for the
${VAR:-default} substitutions; --env-file .env only says so explicitly. loom down
stops the stack and preserves its volumes. loom down --delete-data permanently
deletes this installation's named volumes only after you type the generated Compose
project name; --yes is the non-interactive confirmation. The Makefile targets
do the same: make down preserves data and make down-all deletes it.
What the stock .env boots
The checkout and release-bundle .env templates both select single-user mode.
The release bundle sets LOOM_IDENTITY_PROVIDER=single-user explicitly; the checkout
leaves it empty and infers the same mode from LOOM_AUTH_REQUIRED=false.
| Setting | Value | Effect |
|---|---|---|
LOOM_AUTH_REQUIRED=false, LOOM_IDENTITY_PROVIDER=single-user (or empty) |
single-user |
No login. Default namespace dev/local. SDK and MCP calls need no token. |
LOOM_LLM_PROVIDER=fake, LOOM_LLM_MODEL=fake-echo-1 |
fake LLM | Extraction, entity tagging and memory.ask synthesis produce nothing. |
LOOM_EMBEDDING_MODEL=local, LOOM_EMBEDDING_DIM=384 (release bundle) |
bge-small | Semantic search on CPU, from the model files the image carries. |
LOOM_EMBEDDING_MODEL=fake-ngram-3072, LOOM_EMBEDDING_DIM=3072 (checkout) |
fake embedder | Search ranks by character n-grams. Enough to check wiring, not recall. |
Both installers generate a unique COMPOSE_PROJECT_NAME, a random JWT signing secret, a
random extractor token, and LOOM_BIND_HOST=127.0.0.1. Separate clones therefore do not
silently share ClickHouse volumes, and Loom plus ClickHouse are reachable only from this
computer.
For real providers, open Settings → Models & keys, choose your providers and models, enter their API keys, then click Test & apply. One OpenAI API key can serve both the LLM and embeddings. No Docker restart is needed; embedding changes trigger a verified rebuild, with progress and retry shown in the dashboard.
For environment-based deployment instead, replace the provider lines in ~/.loom/.env
(released stack) or .env (checkout). Setting OPENAI_API_KEY alone changes nothing:
the stock provider lines name their providers explicitly. Moving off the local or fake
embedder re-embeds what is stored.
LOOM_EMBEDDING_MODEL=text-embedding-3-large
LOOM_EMBEDDING_DIM=3072
LOOM_LLM_PROVIDER=openai
LOOM_LLM_MODEL=gpt-6-luna
OPENAI_API_KEY=sk-...
For Claude, OpenAI, hosted embeddings, subscription-only clients and deployments with
one LLM provider, follow Configure models and API keys.
Only after editing the released stack's .env, apply it with
docker compose --project-directory "$HOME/.loom" up -d --force-recreate loom;
a plain restart does not reload the container's environment. Advanced settings are in
Deployment → Providers.
Check the stack
curl -fsS http://localhost:7777/v1/health | jq '{status, fake_embedding, fake_llm, providers_degraded, problems}'
docker compose --project-directory "$HOME/.loom" exec loom loom doctor
/v1/healthsaysstatus: okwhen ClickHouse is reachable and no problem other thancode_tier_failing(a repository's failing code-index tier) is an error.fake_embedding,fake_llmandproviders_degradedsay which mode you are in;problemslists each issue with its fix;schema.pendinglists unapplied migrations.loom doctorprints one line per server-side check.--jsongives JSON. From a source checkout stack, rundocker compose --env-file .env -f compose.yaml -f dev/docker-compose.dev.yml exec loom loom doctor.
2. Open the dashboard
http://localhost:7777/ opens straight into the dashboard in single-user mode;
in multi-user mode, sign up or sign in first. In single-user mode
the dashboard and agent defaults use dev/local (LOOM_DEFAULT_NAMESPACE renames the
namespace). This is a convenience mode, not a user-isolation boundary: anyone who can
reach the server can access its data and administration. Keep it localhost-only.
To try sign-up instead (trust-network: email and password, and the first person to
claim a namespace name becomes its admin), replace these entries in ~/.loom/.env
(released stack) or .env (checkout):
LOOM_IDENTITY_PROVIDER=trust-network
LOOM_AUTH_REQUIRED=true
LOOM_SELF_SIGNUP=open
Setting only LOOM_AUTH_REQUIRED=true is insufficient when single-user is explicitly
selected: that mode keeps authentication off. Keep the installer's generated
LOOM_JWT_SECRET and existing volumes, then recreate the server to load the environment:
# Released stack:
docker compose --project-directory "$HOME/.loom" up -d --force-recreate loom
# Checkout stack (run from the repository root):
docker compose --env-file .env -f compose.yaml -f dev/docker-compose.dev.yml --profile bundled up -d --force-recreate loom
The dashboard then opens on its sign-in page, which has a Sign up tab, and every SDK
and MCP call needs a token. The identity modes are in Identities and
namespaces. Naming
trust-network is also what lets a non-loopback LOOM_BIND_HOST start with sign-up open,
so anyone who reaches the port can then sign up. Before making the server reachable by a
team, follow the deployment guide for
operator access and TLS.
3. Get a token
In single-user mode a token is optional. Minting one anyway keeps your scripts working
when auth is turned on, as long as they name their org (or send it blank), namespace
and agent: a call that sends a credential, as the SDK does when it finds any (Python
SDK → Credentials), gets none of them filled in,
and a blank org is the token's own.
- Dashboard: Settings → Tokens → New token. By default the token reads every namespace and repository you belong to (a restricted repository only when you add it), but no collection, and saves to the namespace you are in. Only what you choose limits it to the namespaces, collections and repositories you tick (none at first); the ticked ones are searched in the order listed, and you change it with a row's arrows, or on a desktop by dragging the row's grip. The result shows the token once, with the setup for your MCP client or a script.
-
CLI, as the operator: run it inside the server container, which holds the signing secret:
docker compose --project-directory "$HOME/.loom" exec loom loom mint-token --subject my-bot --org dev --namespaces local --roles writerAdd
--ttl-days 30for anything that leaves your machine.loom mint-token --helplists the scoping options (--namespace-role,--read-scope,--traffic-class).
- A bot that signs itself up (trust-network only):
loom init --agent nightly-triage --server-url http://localhost:7777creates the namespaceagent-nightly-triage, saves a writer token to~/.loom/tokenand the URL to~/.loom/config.json, and prints a claim code. A person enters it once at/oauth/agent-claimwith a password of their own to take ownership.
Save the token where the SDK, the CLI and the plugins look for it:
mkdir -p ~/.loom && (umask 077; printf '%s' "<token>" > ~/.loom/token)
export LOOM_API_KEY="<token>"
Claims, roles and grants are in Tokens and grants.
4. First memory over HTTP
Every operation is a POST /v1/<op> with a JSON body. A fresh install with the stock
.env, from the installer or a checkout, is single-user: no token, and an everyday memory
or search call (memory.set, memory.get, memory.search and a few more) that names no
org or namespace runs in the install's own, org dev and namespace local, the ones
MCP and the dashboard use; a memory.set that names no agent is recorded as agent
http, and a search reads every agent. So the calls below need only their own fields
(which ops). If a call without a token answers 401
AUTH_INVALID, auth is on: add -H "Authorization: Bearer $LOOM_API_KEY" and name your
account's org, namespace and agent; a header with an empty token is refused.
curl -sX POST http://localhost:7777/v1/memory.set -H 'Content-Type: application/json' \
-d '{"memory_key":"deploy_window","value":"Tue and Thu only; never Friday."}'
curl -sX POST http://localhost:7777/v1/memory.get -H 'Content-Type: application/json' \
-d '{"memory_key":"deploy_window"}' | jq '{found, value}'
curl -sX POST http://localhost:7777/v1/memory.search -H 'Content-Type: application/json' \
-d '{"query":"when can we deploy?"}' | jq '.results[0].content_excerpt'
The memory.set response carries memory_id, content_sha256 and status.
memory.get reads one key back: found says whether the key holds a live value, and
value is the value as written. The search response carries results and abstain,
which says whether the best hit is strong enough to answer from. Search reads every
agent's memories in the namespace, including those added in the dashboard; name an
agent to read only that agent's.
GET /v1/operations lists every operation, and http://localhost:7777/docs lets you try
each one from the browser. Shapes are in the HTTP API reference.
5. First memory from Python
pip install clickhouse-loom # or, from a clone: pip install ./sdk/python
A release not yet on PyPI installs from its assets (installing from a GitHub Release).
The example names org and namespace, so it works with or without the token from step
3, which the SDK sends when it finds one. On a single-user server, a call with no token
may leave them out: Agent(agent="quickstart", base_url=...) runs in dev/local too.
import asyncio
from loom import Agent
async def main():
async with Agent(
org="dev",
namespace="local",
agent="quickstart", # names the writer in the audit log
base_url="http://localhost:7777",
) as a: # sends a credential it finds; none needed in single-user mode
await a.memory.set("standup_time", "Daily standup is at 9:30.")
row = await a.memory.get("standup_time") # None when the key is unset
print(row["value"] if row else "unset")
for hit in await a.memory.search("when is standup?"):
print(hit.content_excerpt)
asyncio.run(main())
Where the SDK finds its server and credential is in
Python SDK → Credentials.
The TypeScript SDK installs with npm install @clickhouse/loom
(from a clone).
The client surfaces are in the Python SDK and TypeScript SDK references.
6. First memory from a coding agent
If the installer connected your agent, skip to the last paragraph of this section.
For Claude Code, install the Loom plugin. It adds hooks (context at session start, capture at the end) and skills on top of the MCP tools:
claude plugin marketplace add ClickHouse/loom
claude plugin install loom@loom
On a single-user server on this machine, keep the default server URL and leave the token empty when Claude Code asks for the plugin's settings.
Without the plugin, add the MCP entry directly:
claude mcp add --transport http loom "http://localhost:7777/mcp?toolset=coding" -s user
Both reach a single-user server on this machine without a token of yours.
With auth on, create a token in Settings → Tokens and choose Claude Code under
Connect: the dialog shows the setup for the plugin and for a claude mcp add entry,
with the token in it. Then:
- Overwrite
~/.loom/config.jsonwith the dialog'sconfig.jsonblock plus the namespace the token saves to ("namespace": "<namespace>"): anorgornamespacea single-user session left in the file outranks the token, and without one the plugin writes to the token's first namespace other than*, orcoding. Add back anycode_repooragentyou had set. - Leave
LOOM_API_KEY(step 3 may have exported one:unset LOOM_API_KEY) and the plugin'stokenoption empty, or set them to the new token: the plugin reads both before that file (configuration). - Remove a
claude mcp addentry made without a token (claude mcp remove loom -s user) before running the dialog's command, which refuses a name that exists.
loom install --client <name> writes the MCP entry for Claude Desktop, Claude Code,
Codex, Cursor, VS Code, Cline, Windsurf and Zed; with no flag it wires every client it
finds. Connect a coding agent covers Codex and Cursor.
Start a new session and say "Remember that I never deploy on Friday." The agent calls
loom_memory_set. In a fresh session, ask "When can I deploy?" The agent calls
loom_search and answers from the stored fact. The model decides when to save and
search from the tool descriptions; there are no trigger words.
7. See it in the dashboard
| Page | Shows |
|---|---|
/memories |
The namespace's memories, with writer and time. |
/admin/activity |
The audit log: which agent wrote which key, when. |
/admin/usage |
Cost per operation and agent. |
The rest is in Dashboard.
Optional: index a repository on the released stack
Adding a repository from the dashboard is refused on this stack with SANDBOX_UNAVAILABLE:
it needs the repository-job sandbox,
which this stack leaves off because Docker's default confinement blocks it. Only list
repositories you trust: their build steps (TypeScript installs, Go module downloads,
CMake) run without the sandbox, in the loom container, whose environment holds the JWT
secret and provider keys.
LOOM_CODE_REPOS_JSON is one line holding every repository as a JSON array
(Configure the entry).
In ~/.loom/.env, uncomment that line (add it if it is missing) and edit it.
dev is the org a single-user stack runs as, and the org multi-user accounts sign up
into (LOOM_SELF_SIGNUP_ORG); if you changed that, use your account's org here and in
--org below:
LOOM_CODE_REPOS_JSON=[{"org":"dev","namespace":"code-myrepo","repo":"acme/myrepo","git_branch":"main","public":true}]
- Loom picks the compiler-tier indexers from the repository's build files (
go.mod,package.json,pyproject.toml, a rootCMakeLists.txt, …);scip_producernames one instead (Compiler tier). C/C++ also needs a configured build tree, so without one a C/C++ repository gets only the parser tier. - For a private repository, add a GitHub token (
LOOM_CODE_GITHUB_TOKENin.env, orgithub_tokenon the entry) and drop"public":true: it is for repositories public on their host, and lets anyone in the org join the index as a reader at once from Repositories. Decide before the firstloom code init: it sets the namespace's join policy frompublic, and editingpubliclater does not change it (a maintainer changes it withcode.repo.set_access).
Then recreate the loom container and run the first index:
docker compose --project-directory "$HOME/.loom" up -d --force-recreate loom
docker compose --project-directory "$HOME/.loom" exec loom loom code init --namespace code-myrepo --org dev
docker compose --project-directory "$HOME/.loom" exec loom loom code backfill --namespace code-myrepo --org dev --embed
backfill --embed runs on the released stack's local embedder. Under a fake embedder (a
checkout's .env, or an upgraded install that still names fake-ngram-*) it refuses and
exits; to try the wiring anyway, add --allow-fake-embedder. Choosing a model later in
Settings → Models & keys re-embeds the code index; after an .env change it needs its
own re-embedding, as Models and keys describes.
In multi-user mode a listed repository has no maintainer, so without "public":true a
reader's join request waits with nobody to approve it. After loom code init, give
someone access as the operator with a token minted in the container: admin makes them
the maintainer, who approves readers from then on, and viewer lets them read. The
subject is the namespace the account signed up with (its login handle, the subject of
its tokens); a self-signup email is never verified, so a grant to it admits no one:
ADMIN_TOKEN=$(docker compose --project-directory "$HOME/.loom" exec -T loom loom mint-token --subject operator --org dev --namespaces code-myrepo --roles admin --ttl-days 1)
curl -s http://localhost:7777/v1/grants.create -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"org":"dev","namespace":"code-myrepo","subject":"your-namespace","role":"admin"}'
Optional: ask a question about a document
loom demo
loom demo uploads this repository's AGENTS.md, ingests it with knowledge.ingest (the
stack's reference extractor parses it) and asks a question about it with
knowledge.synthesize, printing
the answer and the chunks it cited. With the fake LLM it prints the top
knowledge.search hits instead. --file and --question point it at your own document.
Common problems
| Symptom | Cause and fix |
|---|---|
set_from_messages writes no facts |
The LLM is the fake one. Follow Models and keys to configure one. |
| Search results look arbitrary | The embedder is a fake one (fake-ngram-* in .env). Switch it as Models and keys says, which re-embeds what is stored. Do not delete your volumes to change models. |
401 AUTH_INVALID after turning auth on |
No token, or one signed with another LOOM_JWT_SECRET. Re-mint with the Compose command in Get a token. |
| The dashboard returns 503 | The server runs from source (make server) without a built frontend. Run make frontend-build. |
| Released stack does not become healthy | Run docker compose --project-directory "$HOME/.loom" logs --tail 100 loom clickhouse. See Troubleshooting. |
Adding a repository answers SANDBOX_UNAVAILABLE |
The stack runs without the repository-job sandbox. Index the repository through LOOM_CODE_REPOS_JSON (Index a repository). |
The installer says port 7777 is in use |
Another program, or another Loom, listens there. Stop it, or run the install command again with LOOM_PORT=7778 in front of sh loom-install.sh; .env keeps the port. |
The installer says port 7777 does not answer |
A program holds it without answering, or LOOM_BIND_HOST, LOOM_PORT or a firewall keeps this host from reaching it. |
The installer could not check port 7777 |
Curl failed for the reason its exit code names; check LOOM_BIND_HOST and LOOM_PORT. |
Under Docker Desktop on WSL, the install stops with Ports are not available |
Windows holds or reserves the port, which the installer's check inside WSL cannot see. netsh interface ipv4 show excludedportrange protocol=tcp lists the reserved ranges; run the install command again with a LOOM_PORT outside them in front of sh loom-install.sh. |
A clone's port 8123 or 9000 is taken |
Remap the ClickHouse host ports (local stack). |
| The installer could not pull the images | Docker reaches the registry itself: behind a proxy, give the Docker daemon the proxy settings. |
Next
- Evaluate Loom: turn this smoke test into a workload-specific quality, latency and cost evaluation.
- Connect a coding agent: Claude Code, Codex or Cursor against this stack.
- Your first agent: identity, abstain handling, sessions, context packs and runs from the SDK.
- Deployment: running Loom for a team.