@loompad/cli
v1.3.0
Published
Loom — local-first control plane for coding agents: one CLI, shared memory, baton handoffs and routes across Claude Code, OpenCode, and friends.
Maintainers
Readme
Loom
The shared-memory layer for AI dev environments. Every coding agent — Claude Code, OpenCode, Antigravity, Codex, … — keeps its own brain in its own files. Loom makes them one brain: connect your ADEs, and their memory, decisions, and context become a single shared thread that flows from one agent to the next.
Today that means Claude Code, Codex, OpenCode, Grok Code and Antigravity as full agents, each verified against a real version, plus Kiro driven through its own window — see Supported agents for exactly how far each one goes, and How memory actually reaches a model for the part most tools gloss over.
An agent doesn't have to be a CLI any more. A model agent
is an HTTP endpoint — any OpenAI-compatible provider, a model you pick from a list,
nothing to install — which means the cheap work can go somewhere cheap, and on a
provider's free tier it costs nothing at all. Pin one to a
thread and two conversations run on two models
at once; ask several and one prompt fans out to
all of them.
And because a fleet you can't see is a fleet you can't trust, every turn, handoff and route step is exported as OpenTelemetry — traces, metrics and logs — and read back into an in-app Observatory.
Loom is not another IDE. It's the thin layer between your agents — the continuity and memory they don't share on their own.
CLAUDE.md AGENTS.md .antigravity/ ← each ADE's native memory
│ │ │
└──────────────┼───────────────┘ import
▼
╔═══════════════════╗
║ ONE SHARED BRAIN ║ decisions · imported ADE memory · the thread
╚═════════╤═════════╝
│ projected on every handoff
┌──────────────┼───────────────┐
Claude Code ──▶ OpenCode ──▶ Claude Code ← baton carries the brain forward
(plan) (execute) (review)One link — a teammate, their agents, your repo
Click Invite in a project, or run loom invite. Loom makes sure you're on a
team that shares this repo (it creates the team and shares the repo if you
haven't) and gives you one https link to send.
Your teammate clicks it. If they already have Loom, it opens in their Loom and one Join does the rest. If they don't, the page shows one command that installs Loom and joins:
- Sign in with GitHub.
- Join the team, and keep the team key the link carries.
- Get the repo. Loom uses a clone they already have, or clones it. For a private repo, your Loom adds them as a collaborator the moment they join, and theirs accepts GitHub's invitation.
- Open it with their agents — whichever of Codex, OpenCode, Claude Code and the rest are installed on their machine.
- Share it with the team, so you see each other's live agents, goals and files.
- Set up your crews, filled from their own agents.
loom invite # in a project: the link, a QR, and a message to paste
loom join '<link>' # the same thing from a terminalThe invite is the part of the link after #. Browsers never send that part to
a server, including the join page's. A link works once and expires in a day.
It carries the team key, so send it the way you'd send a password.
Quota Relay — a limit doesn't stop the work
Hit Claude's 5-hour window halfway through a task, and the work normally stops: Claude Code parks the turn until the reset, and Codex ends it with "try again at 3:04 PM". Loom recognises the limit and responds:
- It hands the work on. It stops the parked turn and passes the baton to the next agent, with a briefing: "claude ran out of quota; its changes are in the working tree, continue from them". It re-sends the same request in the same chat.
- It routes new prompts. While an agent is out, anything sent to it goes to the agent standing in.
- It gives the chat back. When the window resets, the baton returns.
- It waits if it has to. If no other agent is free, the request is queued for the moment the limit lifts.
The thread records each step ("claude hit its usage limit — codex carries on;
claude is back around 19:00"), and your phone gets a push. By default only
your other CLI agents take over, never a metered API model. Set the order
with relay.to. relay.auto: false only reports the limit, and
relay.handBack: false keeps the stand-in. Limits are read from what Claude
and Codex report about their usage windows, and from the error text any CLI
gives ("usage limit", "rate limited", "quota exceeded", with "try again at…"
or "resets in…").
Proof — is "done" true?
Agents say "all tests pass" when three of fourteen ran, or when none did. When an agent's reply claims the tests, build, typecheck or lint pass, Loom checks the claim three ways:
- What it said. The claims in its final message, including any numbers ("all 14 tests pass").
- What it ran. The commands from that turn and their exit codes. A claim with no test run behind it, or with a failed one, doesn't hold.
- A clean re-run. Loom runs your tests again in a fresh worktree containing exactly the agent's uncommitted changes and new files. Dependency folders are linked, not reinstalled. Your working tree is never touched.
The verdict goes into the thread as a card: Verified, Didn't hold (with the reason, such as "said 14 pass; 11 did"), or Unverified. If a "done" doesn't hold, your phone gets a push. Every turn card also has Check it, to verify any turn on demand.
loom proof # check the newest turn (exits 1 if it didn't hold)
loom proof --test "npm run test:unit"The test command is the one the turn itself ran and passed (a leading
cd <project> && dropped), else one detected from the project (npm test,
cargo test, go test ./..., pytest through the project's .venv or
python3, make test). Override it with proof.testCommand in
.loom/config.json; proof.auto: "off" turns off automatic checks. A re-run
that can't start (exit 126/127, "command not found") is unverified, never
"the tests fail".
Pass/fail counts are read from vitest, jest, node:test, mocha, pytest, cargo,
go test, rspec and phpunit output.
Crews — agents with roles, on one goal
A crew is a few of your agents with jobs: a lead who plans, builders, a reviewer, a tester. You give the crew a goal. The Lead turns it into cards, and you approve the plan. Each card is then built, reviewed (changes go back to the same builder), and tested, in the goal's own worktree and branch. When it's done, you apply it.
loom crew create ship # templates: ship · fix · research · solo — seats follow your agents' roles
loom crew create ship --builders claude-code,claude-2 --reviewer codex # or name them
loom crew goal "Add rate limiting to the public API"
loom crew approve # or: loom crew say "smaller cards, please"
loom crew # cards by stage; questions a teammate asked you
loom crew apply # merge the goal's branch into yoursThe Crew tab shows the same thing. Every commit carries Loom-Crew,
Loom-Goal, Loom-Card and Loom-Teammate trailers. Phase 1 works one card
at a time; docs/proposals/agent-teams.md has
the rest of the plan.
Orchestra — one conductor, many agents, in parallel
Give Loom a goal and let one agent run the others. The orchestrator can be any agent: Claude Code, Codex (ChatGPT), Antigravity, Grok or OpenCode.
- It reads the repository and splits the goal into a task graph.
- Loom runs every ready task at the same time on the worker agents the plan names. Each task gets its own thread and its own git worktree.
- Loom merges each finished task into one integration branch.
- The orchestrator reviews the results. It can accept them, send follow-ups, spawn more tasks, or finish.
loom orchestrate "add OAuth login with tests" -o claude-code -w codex,antigravity,claude-code -p 6 ┌──────────── orchestrator (any agent) ────────────┐
goal ──▶ plan │ t1 → codex t2 → antigravity t3 → codex │ ◀── review, follow up, done
└──────┬──────────────────┬────────────────┬──────┘
▼ ▼ ▼ (after t1)
worktree t1 worktree t2 worktree t3
└──────── merged into loom/orchestra/<run>/main ──▶ apply to your branchAny agent can orchestrate. The orchestrator never runs shell commands. It answers in a small JSON block that Loom parses, so an agent in a sandbox or with no tools at all can still lead.
Many workers, mixed or the same kind. Three Codex tasks and two Antigravity tasks run side by side as separate CLI sessions, capped by
--parallel(1–12).Real dependencies. A task with
dependsOnstarts only after those tasks are merged, and it branches from a tip that already contains their work.Nothing touches your branch until you say so. All work lands on
loom/orchestra/<run>/main. You merge it withloom orchestra:apply <run>, or with the Apply button in the app. Merge conflicts go back to the orchestrator, which can ask the worker to resolve them.You're in the loop. When the orchestrator needs a decision it asks, and the run waits for your reply (
loom orchestra:reply <run> "…", or the app on your phone). You can steer a run mid-flight the same way.Everything is visible. Every step is an
orchestraevent. The web app, desktop app, phone and CLI all watch the same run live, and each task's stream sits in its own thread. The project's budgets, quarantine and shared brain apply to every worker.Race instead of plan.
--race(or the Race switch in Orchestrate) gives every worker the whole goal in its own worktree, with no orchestrator. The entrants finish side by side, each with its diff, and you keep one: Pick this one in the app, orloom orchestra:apply <run> --task race-<agent>.A restart doesn't lose the run. If Loom stops mid-run, the run says it was interrupted. Resume (or
loom orchestra:resume <run>) picks up the tasks that were running and briefs the orchestrator again.
Under the hood: src/core/orchestra.ts.
node scripts/verify-orchestra.mjs runs a real orchestra against your installed
CLIs and checks the result on disk.
Codex & GPT‑5.6
Loom is built around orchestrating OpenAI Codex as a first‑class agent — and in this
build every Codex turn runs GPT‑5.6: Codex's model, pinned in Loom's per‑project agent
config (.loom/config.json → codex → model: "gpt-5.6-terra") at high reasoning effort.
The slug is
gpt-5.6-terra, not a baregpt-5.6— that is the id Codex actually accepts, and asking forgpt-5.6is a 400 (not supported when using Codex with a ChatGPT account). Loom's model picker asks the CLI what it supports (codex debug models) rather than shipping a list that goes stale, which is exactly how this got caught.
- Codex holds the baton like any other agent. The adapter
(
src/providers/codex/adapter.ts) keeps a warmcodex app-server(JSON-RPC) per chat: it opens a thread, streams Codex's items (agentMessage/commandExecution/fileChange), its context usage and compaction, and keeps the same thread across turns (resuming it after a restart) so Codex keeps its own context between handoffs. - Codex reads and writes the shared brain. Before a Codex turn, Loom projects the
unified memory (imported ADE memory + decisions + the thread) into its briefing; its
replies and memory writes land back in the one shared store. So a handoff
Claude Code → Codex carries the full context, and the next agent inherits what Codex
learned. (Verified end‑to‑end: Codex recalled a value another agent set one turn
earlier, and its turns emit
run_complete+memory_add.) - Voice, on real hardware. The LoomPad is a physical ESP32‑S3 macropad, and the
loop is: press the Codex key to hand Codex the baton, hold the mic and speak, hear
the reply spoken back through the pad. It lives in this repo under
hardware/orchestrator-pad/— parametric CAD, printable STLs, the wiring diagram, the Arduino firmware and the voice backend — with the daemon-side proxy endpoints (/api/loompad/health,/api/loompad/connect) and the status pill that reads them. See Hardware to print and wire one. - Codex as a dev agent, too. Because Codex is a full agent, you can hand it real work
inside Loom —
loom route ship "…"routes plan → Codex executes → review, the brain flowing hop to hop.
In short: GPT‑5.6, via Codex, is one of the interchangeable minds Loom keeps in sync — start a thread in Claude Code, hand it to Codex mid‑task, and it picks up with the whole shared context intact.
Observability
Loom exports its agent activity as all three OpenTelemetry signals, and then reads them back into an in-app Observatory so you never have to leave to answer "what is the fleet doing, and what did it cost".
Backend-agnostic. Loom speaks OTLP/HTTP and nothing else — no vendor SDK, no
vendor-specific code path. Point OTEL_EXPORTER_OTLP_ENDPOINT at a Collector, Grafana,
Jaeger, SigNoz, Honeycomb, anything with a /v1/traces endpoint. An ingestion key
rides in through the standard OTEL_EXPORTER_OTLP_HEADERS, so a new destination is a
config change and never a code change.
| Signal | What Loom puts in it |
|---|---|
| Traces | every turn as gen_ai.agent.turn (model, tokens, cost, duration), tool calls, baton handoffs, route steps, memory folds — one trace per turn |
| Metrics | tokens, turn duration, turns, cost, active agents, handoffs — GenAI semantic conventions |
| Logs | every message, tool call, edit, decision and error, correlated to the span it happened inside |
The Observatory tab renders it back: a live fleet canvas, per-agent burn rate and budgets, Span Replay with a trace waterfall, a Decisions explorer, a Logs view, Time-Travel Replay that refolds the whole run from the event log, and Ask — a question about your fleet, answered from its own telemetry.
Self-heal closes the loop: point your monitoring's webhook at
POST /api/webhooks/alerts (the Alertmanager shape SigNoz, Prometheus and Grafana all
send) and a firing alert takes the named agent out of rotation until it resolves. That
is the part a dashboard can't show you — it knows the alert fired, only Loom knows the
fleet reacted.
Two rules run through all of it: nothing is estimated, and absent beats invented. Tokens and cost are what each agent's own CLI reported; an adapter that reports tokens but no dollar figure (codex) produces no cost datapoint rather than a zero, because zeros are indistinguishable from real cheap turns once summed.
Off with DO_NOT_TRACK=1. Full details, every variable, and how to verify it's flowing:
docs/observability.md.
Why
Every coding agent keeps its own brain. Claude Code's memory can't be read by OpenCode; Antigravity doesn't know what you decided with Claude an hour ago. Switch tools and you re-explain your project every time.
Other multi-agent tools answer this by keeping agents apart — each in its own worktree, run in parallel, compare and merge. Loom makes the opposite bet: keep the agents' memory together so work continues across them instead of forking.
- One brain across every ADE — Loom imports each agent's native memory
(
CLAUDE.md,AGENTS.md, …) into a unified store, merges it with your decisions and the shared thread, and hands the whole thing to whoever picks up next.loom memory. - The baton — exactly one agent may write at a time; passing it carries the context (interrupt-safe, memory projected, briefing armed). Not isolation — continuation.
- Routes — let Loom drive the chain:
loom route ship "add dark mode"runs plan → execute → review as one command, the brain flowing hop to hop; orloom route autolets an LLM pick each next agent. - Every surface, one daemon — a full-screen TUI, a web app, a desktop window, and a phone app (voice input, per-prompt diffs, push) — each a paired client of the same local daemon over your tailnet.
- Local-first & yours — one
npm i -g, no account, MIT, runs headless on a server.
Install
Requires Node ≥ 22.5 (Loom's event log uses the built-in node:sqlite).
On Windows, install Node.js and Git for Windows, then use PowerShell or Command Prompt. Bash and WSL are not required. From a source checkout:
git clone https://github.com/nickthelegend/loom.git
cd loom
npm.cmd ci
npm.cmd link
loom.cmd doctorFor a managed source installation, run scripts/install.ps1 in PowerShell
(it clones into %USERPROFILE%\.loom-src, builds, and links the CLI). Use
loom.cmd when PowerShell's execution policy blocks npm's .ps1 shim.
Install agents separately: npm.cmd i -g @openai/codex opencode-ai, then
sign in to the agents you use. Claude Code's native Windows installer is also
detected under %USERPROFILE%\.local\bin.
Normal chat, shared memory, routes, Orchestra worktrees and crews use the
standard runtime on Windows. The optional native continuity mode
(brain.continuity: true) needs verified process containment. On Windows,
every harness runs inside a Job Object, and Loom checks the containment
works on this machine before it offers the mode; if the check fails, the
agent says why and stays on the standard runtime. Leave it off for Orchestra
and crews, which also run outside native continuity on other OSes.
npm install -g @loompad/cli # → `loom` on your PATHOther paths:
# one-liner from source (clones ~/.loom-src, builds, links; re-run to update)
curl -fsSL https://raw.githubusercontent.com/nickthelegend/loom/main/scripts/install.sh | bash
# straight from git
npm install -g github:nickthelegend/loom
# hackable checkout
git clone https://github.com/nickthelegend/loom.git && cd loom
npm install && npm run build && npm linkThen verify the setup:
loom doctor # checks node, agents, tailscale, daemon (and what it sees vs your shell), providers, and your projectSurfaces, all talking to the same daemon:
- TUI / CLI —
loom(default),loom chat,loom send, … - Desktop app (Loom Desktop) — prebuilt for
macOS, Linux, and Windows
(
.dmg·.AppImage·.exe; the macOS dmg is ad-hoc signed, so right-click → Open the first time), or build fromdesktop/:cd desktop && npm install && npm start. Either way it opens a native window that starts the daemon and pairs itself. - Phone app (LoomPad) — install the newest
Android APK (allow unknown sources; Google
Play is coming), open Loom, and Scan QR code from the desktop's Connect a phone.
Voice input, per-prompt diffs, push. Or build from source
(
app/):cd app && npx expo install && npx expo start. - Web app — no install;
loom pair→ open the link. Same workspace in the browser.
Fan out, retry, reap
Three moves the fleet learned for when one turn isn't the right shape:
- Sub-agents — a turn can fan a subtask out to a child that runs alongside
it and never holds the baton:
loom spawn "audit the tests" --agent codex. The child gets a narrowed briefing (its task, the project's rules, retrieval scoped to its own work — not the parent's thread), reports back assubtask_done, and everything it learns lands in the one brain. Capped at four per project, because each child is a real CLI process. - Retry on another agent — when a turn fails,
loom retry <agentId>re-runs the failed prompt on the agent you choose, with the failure attached as a briefing instead of pasted into the message: the thread shows a clean prompt twice, and the second agent is told what was tried and what it died of. - Reap a hung session — a CLI that hangs holds
busyforever and blocks every dispatch.loom stalenames what's hung (busy past ten minutes);loom reap <agentId>interrupts, stops, respawns from config, and releases the baton if the corpse held it.
Native context continuity (opt-in)
The new deterministic Brain path supports sequential switching between checked Codex/Claude CLI profiles with scoped native sessions, protected user context, SQLite search, source-backed checkpoints and delivery diagnostics. It requires no embedding model or inference package. Enable Native context continuity in Settings → Preferences; the legacy workflow remains the default.
Unknown CLI versions, OpenCode/bridges, attachments and parallel execution are gated in this mode. Overflow saves a reviewable request instead of silently dropping user intent. See native continuity, current architecture and remaining verification gates.
Several sessions, one brain
loom agents:add claude-code a second time doesn't error — it adds
claude-code-2: its own session, its own context window, the same project
brain. Name them for their jobs (--as reviewer --role reviewer) and pick
per message. Memory import dedupes by file and units are project-scoped, so
what one session learns the others retrieve. One baton still rules them all:
two sessions of a kind never means two writers at once.
The Browser tab
Agents write Playwright specs constantly and could never watch them run. The
Browser tab (next to Console in the dock) lists the project's specs
(*.spec.*, *.e2e.* — never inside node_modules), runs one on click with
the reporter streaming line by line, records pass/fail in the Console, and on
failure stages the reporter's own words in the composer to hand back to
whichever agent should fix it. Playwright stays the project's dependency (npx
--no-install) — a project without it is told so plainly.
Your dev servers live there too. Declare them under servers in
.loom/config.json (Loom suggests them from package.json and waits for you
to accept) and the rail shows each one's real state: running means a port
answered, starting means the process is up but nothing is listening yet,
and crashed carries the exit code it died with. Start, stop and restart
from the row; click the row to point the preview at it; open its output under
the page, which is where the reason for a blank frame usually is. A server
that dies while an agent works against it says so in the thread, and every
server Loom started is stopped when the daemon goes.
From the terminal: loom servers, loom servers start|stop|restart <name>,
loom servers logs <name> -n 100.
The page can talk back. Click a server and Loom previews it through its own proxy, which forwards everything (including the socket hot reload rides on) and injects one small script. The Page pane then shows what that page logged and what it fetched, live — and a click puts any line into the composer as context, which is the whole point: the loop used to be see the error, select it, retype it. Pick turns the cursor into an element picker: click the thing that's wrong and its selector, text, size, markup and computed styles (fonts, colours, spacing, layout) land in the composer, along with a picture of just that element, so any agent can see what you mean. The picture is taken at your width and colour scheme with the project's own Playwright. Nothing about the page is rewritten; the script only reports.
The preview itself emulates a width (Fit / 375 / 768 / 1280, remembered per project, scaled down when the dock is narrower), reloads itself when an agent changes files — a toggle, on by default — and has a camera button that puts a picture of what you're looking at into the composer. The page is another origin, so the browser won't let it photograph itself: the daemon takes the shot with the project's own Playwright, and a project without Playwright is told exactly that rather than handed an empty file.
Dark mode, on the page being previewed — Auto / ☀ / ☾, independent of
Loom's own theme and remembered per project. No API lets one document set
another's prefers-color-scheme; Loom isn't outside the page, though — the
proxy served it and its stylesheets — so the bridge re-points the page's own
@media (prefers-color-scheme: …) rules, sets the UA colour-scheme, and makes
matchMedia answer the way you asked (firing change, so an app that themes
itself in JavaScript re-renders). Every rewritten rule remembers the media text
it came with, so Auto is the page exactly as it shipped; stylesheets Loom
doesn't serve can't be read and aren't claimed. A page you typed into the
address bar rather than previewing through Loom says so instead of offering a
switch that does nothing — and the screenshot drives a real browser, where the
scheme is honoured either way. Both conditions ride along with what you send:
Screenshot taken at 375×812, dark mode, and an element pick names them too.
The workspace
On a wide screen the web app (and the desktop shell around it) is a full workspace for driving agents — still not an editor: Loom shows you the context and the agents do the writing.
┌───────────┬────────────────────────────┬──────────────┐
│ projects │ Thread · Board · Brain │ Explorer │
│ └ chats │ │ Search │
│ │ the conversation, with │ Source ctl │
│ New task │ Update(n files) cards ────┼─▶ diff opens │
│ New proj │ │ Agents │
│ Search ├────────────────────────────┤ │
│ │ terminal (a real shell) │ │
└───────────┴────────────────────────────┴──────────────┘
live · host · baton · spend ← status bar- Projects + chats in the left rail: a project holds as many conversations as you want, and they share one brain, one baton and one working tree. The agents live in the right panel — click one to aim your next message at it, click its role to rename the job (roles are free text: "architect", "the one that writes docs", anything).
- Thread is the shared conversation; Brain is the unified memory; Board is everything in flight.
- Board is one board with three sources — GitHub, Projects, Linear —
switched from a segmented control (see GitHub & Linear, native).
GitHub is four live columns (working → needs you → in review → ready to merge); cards
come from yours (
+ Task), Loom (which agents are running or blocked), and your repo's PRs (draft, CI failed, changes requested, approved — read through your owngh). Search issues and PRs in GitHub's own query language; Start hands an issue to an agent. Dragging your own card really moves it; dragging a PR card only moves where you see it — the badge keeps telling the truth, because a drag can't approve a review or turn CI green. Each card wears its agent's own logo. - Click any change — an
Update(n files)card, or a file in Source Control — and the diff opens to the right of the chat. It stays closed until you ask for it. - Explorer / Search / Source Control / Tasks in the right panel; every column is drag-resizable (double-click a handle to reset).
- Terminal (
Ctrl+`) is a real terminal in the project directory — a proper pty, so the shell draws its own prompt andvim,less,htop,^Cand^Zall behave.node-ptyis optional: without it (Linux with no build toolchain) you get a pipe-backed shell instead, wherecdand variables still persist and^Cstill works, driven a line at a time. - New task (
n) picks a project, a task, and one agent — or several, which run it as a pipeline, hop to hop. New project (p) adds a repo — a native folder picker in the desktop app — and reports which ADEs it found on the host.
Settings, in one place
Everything about a Loom lives behind the gear in the bottom-left: Setup (what the
machine still needs to run agents), Diagnostics (loom doctor, run live on the
daemon), Updates (build rev, and how far the checkout is behind its remote),
Preferences (theme, the brain extractor, handoff brief style, default agent),
Devices (paired clients — revoke one, or pair another), and About.
Split: several views in one window
⌘D splits the window side by side, and ⌘⇧D one above the other. Each pane is a whole Loom view (a project, a chat, any tab), so you can follow several agents' threads at once.
- Arranging: drag a chat from a pane's sidebar onto another pane's edge to open it there, or drag a pane by its grip to move it.
- Closing: a pane's × closes it, and closing the last but one leaves split mode on the pane that's left.
- Saving: the layout is saved, so it's there when you come back.
Panes keep their contents when the layout changes, so splitting never reloads what you were looking at. The approach comes from Nuphos; the layout model is ported from its desktop app.
GitHub & Linear, native
Browse pull requests, issues, and GitHub Project boards in-app; open a worktree from any task; review and approve PRs; and file Linear issues with a team selector — no context switch, no second browser tab.
- Browse PRs, issues, and Project boards. The GitHub source is the live kanban; the Projects source lists the owner's GitHub Project (v2) boards and lays a project's items out by Status; search takes github.com's own query language.
- Review and approve PRs in place. Open a PR's diff without leaving Loom and post the
three things a reviewer does — comment, request changes, approve. The review
is signed as you (through your
gh); approve asks first, because it publishes. - Open a worktree from any task. One click cuts a checked-out branch in its own directory, a sibling of the repo: a PR worktree checks the branch out — forks included — and an issue worktree cuts a fresh branch to start it. An agent can work a PR while your main tree stays exactly where you left it.
- Create Linear issues with a team selector. Pick a team, write a title and description, file it — the new issue's identifier comes straight back.
Loom holds no token of its own. GitHub goes through your gh CLI; Linear reads
LINEAR_API_KEY from the daemon's own environment (you export it) and never stores,
logs, or transmits it anywhere else. No key → an honest "not connected", never a dead
form — the same bet the agent adapters make by shelling out to the CLIs you already have.
Quickstart
cd your-project
loom init # detects the agents installed on this machine, assigns roles
loom # opens the TUI — a tabbed workspace (Thread · Board · Brain · Diff) ██ ▄████▄ ▄████▄ ▄█▄▄█▄
██ ██ ██ ██ ██ ██▀▀██
██ ██ ██ ██ ██ ██ ██
██████ ▀████▀ ▀████▀ ██ ██
one thread · every agent
1 Thread 2 Board 3 Brain 4 Diff ↑12 pgup/pgdn
10:44 claude-code here's the plan: …
⟶ baton: claude-code → opencode
10:45 opencode implementing step 1 …
╭──────────────────────────────────────────────╮
│ › Ask anything… "/route ship: add dark mode" │
│ opencode · executor ⟵ baton │
╰──────────────────────────────────────────────╯
shift+tab view · tab agent · ctrl+p palette · esc back/interrupt
~ my-project · baton opencode ➤ ship 2/3The TUI is a tabbed workspace, not just a thread:
- Thread — the conversation, streamed.
tabshifts the active agent/IDE (claude-code → opencode → back); the handoff (interrupt-safe, memory projected, briefing armed) happens when you hit enter, so switching is one keystroke, not a ceremony. - Board — agents, your cards, issues and PRs in the four flow columns.
- Brain — the memory the project has learned, grouped by kind (failures first), each tagged with who learned it.
- Diff — the working tree: changed files and a colourised patch.
shift+tab cycles the tabs (or /board, /brain, /diff, /thread, or the
palette); pgup/pgdn scrolls the view. ctrl+p opens the command palette
(fuzzy-filtered: jump to a view, shift to any agent, launch a named route, decision,
interrupt, pair…). esc steps back to the thread or interrupts the running turn; /help
lists the slash commands.
Prefer plain line-mode (SSH, scripts)? loom chat is the same thread as a classic REPL,
and every action also exists as a one-shot command (loom send, loom handoff, …).
Routing — multi-hop pipelines
Handoffs are unlimited and manual by default. Routes automate a chain of them:
loom route auto "add a dark-mode toggle" # DYNAMIC: an LLM picks each hop
loom route ship "add a dark-mode toggle" # named pipeline from config
loom route planner,executor "fix the flaky test" # ad-hoc: roles…
loom route claude-code,opencode,claude-code "…" # …or agent ids, any lengthauto is dynamic routing: after every hop, a router looks at the task, the hop
history, and the last replies, then picks the next agent — or declares the task done.
The router is Claude (headless, small model, JSON out) with a deterministic
plan→execute→review rules engine as automatic fallback, so routes never stall on a
router failure. Every decision is logged with its reason
(➤ hop 2 → opencode (plan ready — execute it)), a hop budget caps runaways
(--max-hops, default 8), and --router rules skips the LLM entirely.
What happens per hop: interrupt-safe handoff → shared-memory projection → briefing → the step's role instruction. Then:
- step finishes cleanly → Loom advances to the next agent automatically;
- the agent asks a question → the route pauses (
waiting_human), you get a notification,loom route --statusand the board show the question; you answer in the shared thread (loom send "…") and the route resumes by itself; - an agent errors or a step times out (45 min default) → the route fails loudly —
unless the step carries an
onFail, which sends it back to an earlier step instead; - a step carrying a condition the previous turn
didn't meet (
reviewer?lines>200) is skipped, with the numbers that decided it; - you always outrank the route: any manual
handoff/interruptcancels it, andloom route --abortstops it and interrupts the in-flight turn.
--detach returns immediately (fire-and-notify); following with Ctrl-C also leaves the
route running server-side. One route per project at a time (the baton is one write
lock); run routes across different projects in parallel freely.
Define named pipelines in .loom/config.json — steps are roles or agent ids, and any
step can carry its own focus:
"routes": {
"ship": ["planner", "executor", "reviewer"],
"api-only": [
{ "step": "planner", "instruction": "design the endpoint contract only" },
{ "step": "executor", "instruction": "only touch src/api — no schema changes" },
{ "step": "reviewer", "when": "lines>200", "onFail": "executor" }
]
}Per-step instructions are appended to the role guidance for exactly that step — the
next hop never sees them. when runs a step only if the previous turn met a condition
Loom measured, and onFail sends a failing step back to an earlier one; both are
explained here. loom init seeds a ship route automatically when it
detects at least two roles.
Commands
| Command | What it does |
|---|---|
| loom | The TUI — tabbed workspace (Thread · Board · Brain · Diff), shift+tab switches view, tab shifts agents, /-commands + ctrl+p palette inline |
| loom init | Make the current directory a Loom project (auto-detects agents); in a git repo it offers to add .loom/ to .gitignore (--gitignore / --no-gitignore to skip the question) |
| loom chat | Same thread as a plain line REPL (/handoff, /interrupt, @agent) |
| loom send <text> | One-shot message (-a <agent> to address someone specific). --wait blocks until the turn ends and prints the reply and cost (--json for one object, --timeout <s>); it exits 0 done, 1 failed, 2 the agent asked a question, 124 timed out — so a script can drive an agent without reading .loom/log.db. Before resuming a session of 100k+ tokens whose prompt cache has likely expired, it warns with the re-read's estimated cost; --fresh starts the agent's session in that chat over |
| loom handoff <agent> | Pass the baton — interrupts, projects memory, briefs the target |
| loom route <spec> "<task>" | Run a pipeline (name, or a,b,c ids/roles); --status / --abort / --detach |
| loom routes | List named pipelines defined for this project |
| loom interrupt | Stop the current holder's turn (cancels an active route) |
| loom update [--check] [--yes] | Is there a newer Loom — and install it, in the commands it prints first |
| loom servers [start\|stop\|restart\|logs] [name] | The project's dev servers: real state, start/stop, and their output |
| loom queue | What's lined up behind the running turn — position, who takes it, why it's waiting |
| loom queue add "<text>" [--to <agent\|orchestrate\|auto>] [--plan] | Line a prompt up; it goes when nothing is in its way |
| loom queue edit / to / move / rm / clear | Rewrite one, send it to someone else, reorder it, drop it, empty the queue |
| loom queue pause / loom queue resume | Hold the queue where it is · let it run again |
| loom queue at <when> "<text>" / after landed:<run>\|green:<run> | Queue a prompt for a time, or for a goal landing / going green |
| loom queue save <name> / run <name> / recipes | Keep a queue as a recipe and replay it on any project |
| loom digest [--since <hours>] | What happened in this project while you were away |
| loom decision <text> | Record a decision into shared memory |
| loom memory [import] | The unified brain — one memory across every connected ADE |
| loom log [-f] | Show (or follow) the project event log |
| loom costs | Project spend: total + per-agent turns, $ and agent time |
| loom agents / loom projects / loom status | Agent roster, project board, daemon health |
| loom providers | Where model agents send their turns, and whether each has a key |
| loom providers:set <id> [--key --base-url --header] | Point Loom at a provider — the key goes to ~/.loom/providers.json (0600) or the env, never a project |
| loom providers:rm <id> | Forget a provider's key and settings |
| loom models [agentId] [--provider --free --refresh] | With an agent: the models it reports. Without: what every configured provider has right now |
| loom ask "<text>" [--models a,b,c \| --free --limit n] | One prompt to several models at once, a thread each, side by side |
| loom up [--tailnet] [--restart] / loom down / loom daemon | Daemon lifecycle (--tailnet binds to your Tailscale IP) |
| loom pair | QR deep link that pairs a phone (single-use token) |
| loom clients [--revoke <id>] [--ping] | Paired devices: list, revoke, or send a test push |
| loom orchestra:clean [--dry-run] | Remove orchestra worktrees left by projects that are no longer registered (loom doctor points them out). Finished runs' worktrees are removed by themselves after 7 days; a project on another disk keeps its worktrees beside it (orchestra.worktreeDir to choose) |
| loom doctor [--json] [--fix] | Diagnose env, daemon, binding, and project config — and where the daemon sees something different from your shell (an agent missing from its PATH, a sign-in, an API key exported only in a terminal), whether each provider answers with the daemon's key, and whether a model agent pinned to a :free model keeps getting 429/503 (it suggests pool:free). --fix repairs what has exactly one safe repair |
| loom orchestrate "<goal>" | One orchestrator plans, many workers build in parallel worktrees (-o, -w, -p, --no-watch) |
| loom orchestrate --plan "<goal>" | Plan mode: the plan and each task's spec land as markdown under plans/<run>/ for any agent to pick up |
| loom orchestra [run] [--watch] | Orchestra runs in this project, or one run's task graph live |
| loom orchestra:reply / :apply / :abort / :cleanup <run> | Answer or steer the orchestrator · merge the run into your branch (a race: --task <entrant>) · stop it · remove its worktrees |
| loom orchestrate --race "<goal>" -w a,b | Race: each agent takes the whole goal in its own worktree, and you keep the one you like |
| loom orchestra:resume <run> | Carry on a run Loom stopped by restarting |
| loom stats | Today across every project: turns, how many failed, the time and the spend |
| loom export [--chat <id\|title>] [--out file] | A chat as Markdown: prompts, replies, tool summaries and turn footers |
| loom backup [--out file] | Everything Loom knows in one archive: ~/.loom and every project's .loom (logs copied as consistent snapshots) |
| loom completion zsh\|bash | Shell completion: loom completion zsh >> ~/.zshrc |
| loom hub [--host --port --secret] | Run a self-hosted Team Hub for your team |
| loom invite / loom join <link> | One link: a teammate joins the team and gets this repo, their agents and your crews |
| loom crew [status\|create\|goal\|say\|approve\|stop\|resume\|apply\|diff\|remove] | Crews: agents with roles (lead, builders, reviewer, tester) working one goal |
| loom team [status\|signin\|create\|invite\|join\|share\|unshare\|brain\|landing\|doctor\|adopt\|deploys\|release-notes\|webhook\|remove\|leave] | Loom Teams: see teammates' live agents and goals; the shared team brain; landing goal PRs; GitHub webhooks; membership and key rotation |
| loom land [runId] | Land a goal's PR: fresh main in, fast tests, push, merge when GitHub's rules pass |
| loom runner [status\|pair\|join\|start\|stop\|token\|doctor\|install\|revoke\|goal\|move\|back\|jobs] | Runners: your always-on Loom that takes goals while you're away |
| loom cloud [status\|enable\|disable\|rotate] | Loom Cloud relay: reach this daemon from any network, end-to-end encrypted |
| loom spawn "<task>" | Fan a subtask out to a child agent — the parent keeps the baton |
| loom review <agents…> [--file f \| --diff \| --text "…"] [--focus "…"] | Several agents review the same change at once — read-only (their own plan mode where they have one), nobody takes the baton, so nobody is asked to hand off for a read. It waits and prints every finding in one list, worst first; two reviewers on the same line show as one finding they agree on. The same card lands in the app with Accept / Reject on each finding. --diff (the uncommitted changes) is the default; --no-wait, --json. A reviewer that takes longer than review.timeoutMin (default 15) is stopped and left out, so the others' findings still land |
| loom council "<goal>" --planner <id> --reviewers <ids> | One planner, several reviewers, seven stages in one chat (and one card in the app that fills in as they go): the planner plans (no edits) → the reviewers backtest the plan in parallel, read-only → the planner accepts or rejects each finding, recorded as decisions → only the planner builds → Proof re-runs the tests in a clean checkout → the reviewers forward-test the diff → the planner fixes what it agrees with, and Proof again. loom council:status [id], loom council:stop |
| loom review:accept <id…> / loom review:reject <id…> [--note why] | Decide on review findings — an accepted one becomes a project decision every agent is briefed with. loom reviews [id] lists them |
| loom subtasks | Subtasks running right now |
| loom agents:add <kind> [--as name] [--role r] | Add an agent session — repeat for a second session of the same kind, same brain |
| loom agents:add model --model <id> [--tools --write --run "npm test"] | An agent that is a model: no CLI, and what it may read, write or run |
| loom agents:rm <id> / loom agents:available | Remove a session · what this machine can drive and how many are here |
| loom watch [--all] [--json] | Tail a project's events live — turns, handoffs, memory, subtasks |
| loom retry <agentId> | Re-run the last failed turn on a different agent, failure attached |
| loom stale / loom reap <agentId> | Sessions that look hung · respawn one fresh (baton released) |
| loom budgets / loom budget <id> [usd] | Daily USD caps: measured spend vs cap · set or clear one |
| loom brain:search "<query>" [--explain] | Retrieval exactly as a briefing sees it — the score, and the arithmetic behind it |
| loom brain:forget <memoryId> | Drop one unit from the project's memory |
| loom brain:export [file] / brain:import <file> | The project's memory as a portable file — import dedupes |
| loom brain:conflicts | Units that likely contradict each other, with the signal that tripped each |
| loom snapshot [file] / loom restore <file> | Checkpoint brain+board+config · bring one back (brain merges) |
| loom rewind [checkpoint] | Points the working tree can go back to · put the files back to one |
| loom routes:save <name> <steps...> / routes:rm | Define a named pipeline, validated against the roster · remove one |
| loom task "<title>" / loom tasks | Put a card on the board (--agent, --blocked-by) · list yours |
| loom specs / loom specs:run <file> | The project's Playwright specs · run one through the daemon |
| loom mcp:health | Each MCP server's live state from the background poll |
| loom costs --series [days] | The spend ledger day by day, per agent |
| loom open / loom version | Open this project in the web app · what's installed |
| loom rename <name> / loom find <query> | Rename the project (id never moves) · search the thread |
Supported agents
| Agent | Tier | Transport | Status |
|---|---|---|---|
| Claude Code | adapter (full-duplex) | Claude Agent SDK driving the installed claude (stream-json control protocol), resume by session, briefing appended to Claude Code's system prompt | ✅ verified against 2.1.278 with SDK 0.3.283 (2026-09-28) |
| Codex | adapter (full-duplex) | codex app-server JSON-RPC, thread/resume; found on PATH or inside Codex.app | ✅ verified against codex-cli 0.153.4 (2026-09-28) |
| OpenCode | adapter (full-duplex) | opencode serve HTTP + SSE (/prompt, /interrupt, /event) | ✅ verified against 1.17.20; re-verified 1.18.31 (2026-09-18) |
| Grok Code | adapter (full-duplex) | grok -p --output-format json, -r <session> | 🔶 verified against 0.2.54 — answers only, no tool or edit events (see below) |
| Antigravity | adapter (full-duplex) | agy -p headless, --conversation <id> to resume | ✅ verified against agy 1.1.6; re-verified 1.2.6 (2026-09-18) |
| Cursor | — | cursor-agent --print | 🔜 coming soon |
| Model (API) | adapter (full-duplex) | POST /v1/chat/completions, streamed — any OpenAI-compatible provider | ✅ no CLI to install; tools are read-only unless you allow more (see below) |
| Echo | adapter (demo/tests) | in-process | ✅ |
| Kiro | bridge (driveable) | Chromium debug port — types into the real chat panel and reads the panel back | 🔶 mechanism verified; its selectors are not (see below) |
Context and limits. For Claude Code and Codex, the composer shows the chosen agent's context in use against its model's window, shows compacting… while the harness compacts (with a "compacted its context · 180k → 12k tokens" line in the thread when it's done), and calls out a usage-limit window once it passes 80%. Hover for every window and when it resets.
The Claude Agent SDK package carries a platform build of Claude Code as an optional dependency (about 215 MB installed on macOS arm64). Loom never runs it: it always points the SDK at your own
claude. Installing with--omit=optionalskips it.
Four of those need their asterisks spelled out, because the table row is shorter than the truth:
Codex reports tokens, never money. Its turn.completed carries
input_tokens / output_tokens and no dollar figure, so Loom shows tokens and
no cost for Codex turns. A USD number derived from a price table we'd have to
keep current is fiction with a decimal point in it.
Grok can't show you its steps. --output-format streaming-json sounds like
it would help and doesn't: it emits thought and text deltas and a final
end, with no tool calls and no file edits — not even when the turn
demonstrably ran a shell command and wrote a file. So a Grok turn in the thread
is what it said, and git status is what it did. Inferring the edits by diffing
the tree would put guesses in the event log dressed as facts. Its permission
mode also defaults to bypassPermissions, because headless with no TTY to ask,
every other mode ends the turn Cancelled having written nothing.
Antigravity used to be a bridge, and isn't any more. Loom drove the IDE's
chat panel over the DevTools protocol: it needed the app open with a debugging
port, it could only watch, and it could never hold the baton. agy does the
whole turn headless, so Antigravity is a full agent and the GUI stopped being a
dependency. It reports no dollar cost — the CLI hands us none — so its turns
show a model and a duration and honestly nothing else. The CDP bridge is still
in the tree and still builds, so projects that name it still open; it just isn't
offered any more.
Kiro is driven, not routed. It's an Electron app with no
API; Loom connects to the debugging port, finds the chat box, types through the
input pipeline and reads back what the panel gained — the approach
antigravity_phone_chat
takes, and for the same reason: never touch the provider APIs, drive the app
that's already signed in. Launch it with
--remote-debugging-port=9222 first.
The driver refuses more than it accepts, on purpose. Kiro is VS Code
family and Monaco — the editor holding your source file — is a
contenteditable. Anything under .monaco-editor is never a candidate, a
candidate must be labelled like a chat box, and zero-or-several matches is a
refusal that names the fix (options.selectors.composer). Typing a prompt into
your code and pressing Enter is not a mistake an error message repairs.
What's verified is the mechanism, against a real Chromium. What is not
verified is Kiro's actual chat DOM: it shows no chat panel until you open one,
so there was no composer to read the selectors from. Reachable and driveable are
separate questions, and Loom answers both — an app with no chat panel open
replies to CDP cheerfully and reports
driveable: false — no chat box on screen.
Adapters run a turn to completion headless and may hold the baton. Bridges only observe and receive shared-memory projections — they never hold the write lock. That's a design decision, not a gap: GUI agents without a stable API can't be trusted with interrupt-safe writes. See docs/integration-notes.md for the verified surfaces.
Any ACP agent
One agent kind, acp, drives any agent that speaks the
Agent Client Protocol over stdio. That includes Claude Code
and Codex through their ACP adapters, Gemini CLI, OpenCode and Grok.
loom agents:add acp --preset gemini --as gemini # gemini --experimental-acp
loom agents:add acp --preset claude --as claude-acp # npx @agentclientprotocol/claude-agent-acp
loom agents:add acp --cmd "my-agent --acp" --as mine- Sessions: each Loom chat is one ACP session, and it resumes with
session/loadwhen the agent supports it. - What you see: the agent's thoughts, its plan, each tool call and the diffs it reports show up in the thread like any other agent's. Diffs appear as file edits.
- Permissions are Loom's:
- Bypass picks the agent's own "allow" option.
- Auto runs Loom's Auto policy on the call.
- Always ask puts each request on an approval card in Loom and on your phone.
- A request that fails or is interrupted is answered "cancelled", never allowed.
Agents that are models
Every other agent in Loom wraps a CLI. A model agent is an HTTP endpoint:
loom providers:set openrouter --key sk-… # or $OPENROUTER_API_KEY
loom models --free # what costs nothing today
loom agents:add model --as cheap --role reviewer \
--model "google/gemma-4-31b-it:free" --toolsIt streams into the thread like any other agent, shows reasoning as reasoning, reports its token counts, and holds the baton. Out of the box it is a thinker rather than an editor — planning, reviewing, summarising, answering, routing, which is most of what a fleet does between edits and what free quota is very happy to pay for — and it becomes an editor only when you say so, below.
It can read the project, if you let it. --tools adds read_file,
list_files and search — read-only, inside the project, with .git and
.loom off limits and every path proven contained before anything opens it.
Each call shows in the thread, and the loop is bounded at eight hops.
And write, if you say so. With --tools already on, --write adds
write_file and --run "npm test" adds run with that allow-list. (Either
without --tools is refused — they're the writing half of it, not a
replacement for it.) Every write and every command is a card you
allow or deny first:
write_file — write src/app.ts: 40 lines (replacing 12) — fix the port [Allow] [Deny]
It asks in auto as well as ask, which is stricter than the CLI mapping and
on purpose: a CLI in your roster is one you installed and signed into, and a
model agent is a name you picked off a provider's list an hour ago. bypass
is the only mode that doesn't ask. With nobody to ask, the answer is no.
The allow-list is a prefix match on whole commands and there is no shell — so
npm test permits npm test --watch and refuses npm testify,
npm test; rm -rf ~, backticks, pipes and redirection — and a tool is only
offered to the model when it can actually be used.
Providers Loom knows by name — and anything else that speaks
/v1/chat/completions works with a base URL:
| provider | base URL | free tier |
|---|---|---|
| openrouter | https://openrouter.ai/api | models whose id ends :free |
| agentrouter | https://agentrouter.org | all of it — but see the note below |
| groq | https://api.groq.com/openai | — |
| openai | https://api.openai.com | — |
| ollama | http://127.0.0.1:11434 | local, no key |
| anything | loom providers:set mine --base-url … | whatever you tell it |
Keys are never project config. .loom/config.json names a provider;
the key lives in the environment or in ~/.loom/providers.json (mode 0600),
and nothing — no route, no log, no listing — hands it back. You get the last
four characters, which is enough to tell two keys apart.
A fallback chain, for when the free pool is dry:
{ "id": "cheap", "kind": "model", "role": "reviewer",
"options": { "provider": "openrouter",
"model": "google/gemma-4-31b-it:free",
"fallbacks": ["qwen/qwen3.8-27b:free"],
"tools": true } }Refusals are read, not guessed at. A dry free pool (402) or a rate limit
(429) moves to the next model in fallbacks and says so once; a model name
that doesn't exist (503) stops, because falling back would hide the typo. And
a provider that rejects Loom rather than your key says exactly that —
AgentRouter, for one, only accepts clients it recognises, which is a thing to
ask them about rather than a key to go and regenerate.
Threads, and who answers in them
A thread can name its own agent. It then answers there whatever the baton is doing elsewhere, which is what lets two threads talk to two agents at once — one planning on a free model, one editing with Claude Code, neither waiting for the other.
Pinning does not take the baton. The baton is the write lock for work that touches the repository; a conversation doesn't need it, and taking it would stop whatever is actually working. The main thread still follows the baton, because that's what makes it the main thread.
A thread may pin a model as well, for agents that can change model per turn
(the model kind). Pinning one to an agent whose model is baked into a spawned
process is refused at the point of pinning — a setting that silently does
nothing is worse than an error.
Asking several models at once
loom ask --free --limit 3 "is this migration reversible?"
loom ask --models "openrouter/qwen/qwen3.8-27b:free,ollama/llama3" "review this approach"In the app it's More → Ask several models… in the composer: tick the models (free ones are listed first), and what's in the box goes to each.
One prompt, one thread per model, all at the same time, each thread named after the model that answered in it. With free quota this costs what asking one model costs, and picking the good answer takes a person ten seconds.
The agents that run are transient — five asks don't leave five agents in the roster — and one model failing leaves its error in its own thread while the others carry on.
The brain, sharpened
Five retrieval behaviours worth knowing, all inspectable with explain:
- Recency decay — a unit's match decays toward a floor (7-day half-life, never below 0.55): an equal match, fresher, wins; a strong old constraint still beats a weak new fact. Age tips ties, it never erases history.
- A fuzzy channel — hashed character-trigram vectors catch typos and
morphology (
sqllite databsefindssqlite database;deployingfindsdeployment). Honestly not synonymy: that needs a model, which is the next one. - A semantic channel, opt-in (
brain.semantic, or Settings → Brain) — all-MiniLM-L6-v2 embeddings, so how does login work reaches a note about Supabase JWKS. Measured ontest/brain-recall.test.ts, recall@5 goes 0.50 → 1.00 on queries that share no word with the memory they want, 0.80 → 1.00 across word forms, and stays 1.00 on the literal ones — it costs nothing where the lexical channels already worked. It is not a dependency: the 23MB model needs a ~470MB ONNX runtime, so Loom asks you to install it (npm i -g @huggingface/transformers) and works exactly as before if you don't. Scored relative to the best hit in the query, like BM25, because this model's absolute similarities don't mean enough to threshold. - Contradiction flags —
loom brain:conflictsscans same-kind units for negation pairs (always/neveron one topic) and same-topic-divergent decisions. Every flag names the signal that tripped it, so you can judge the judge. Resolution stays human. - Portability —
loom brain:exportwrites what the project knows to a plain file;brain:importbrings one in, deduped by hash, so the same file twice reports known rather than minting duplicates.loom snapshot/restorewiden that to brain+board+config — restore merges the brain, because what the project learned since the snapshot should survive it.
How memory actually reaches a model
Worth being precise about, because this is the whole premise and it has a soft edge.
Loom reliably builds the shared brain (every ADE's imported memory + your decisions
- the thread) and reliably writes it to
.loom/memory/<agent>.mdon every handoff. That part is solid and tested.
Getting it into the model's context is a different problem, and it depends on the agent:
| Agent | How the brain arrives | Strength |
|---|---|---|
| Claude Code | briefing appended to Claude Code's system prompt — the model always sees a summary (recent decisions + messages) plus a pointer to .loom/memory/claude-code.md, which it can Read | strong — the summary is guaranteed; the full file is one tool-call away |
| Grok Code | the briefing rides in --rules, Grok's real system-prompt channel, so -p stays your clean prompt | strong — --rules is a genuine system channel, not text in the turn |
| Codex | no per-turn system channel in codex app-server, so the briefing rides in front of your prompt — framed as an unmissable LOOM SESSION MEMORY — authoritative, read first block | reliable — one prompt either way, but framed so it can't be mistaken for chatter |
| OpenCode | no per-prompt system field on /prompt, so the same framed block is prepended to your prompt | reliable — delivered as an authoritative block, not loose text |
| Antigravity, Kiro | nothing tells them the file exists — Loom types into their chat box, which is not a system prompt | none — a human has to open it |
So: the summary always lands; the full brain is an invitation. An agent that
ignores the pointer works from the summary alone. If you need something remembered for
certain, put it in a decision (loom decision) — decisions ride in the briefing itself.
There's an opt-in eval (LOOM_TEST_REAL=1) that checks a real model actually uses an
injected brief, and declines rather than invents when the brief is silent.
Memory also flows one way. Loom reads CLAUDE.md / AGENTS.md and never writes
them, so your ADE's own memory files stay yours. And the import is a merge, not a
parse: files are read, capped at 8000 chars, and concatenated under headers. Claude
Code's @path imports are not followed — a CLAUDE.md that is mostly @ pointers
imports the pointers, not what they point at.
The brain also learns on its own. After each turn a small Claude reads what changed and files what's worth keeping as typed memory units — a constraint, a decision, a convention, a fact, a failure — reconciled on write (add / update / forget, never a growing blob), the approach mem0 pioneered, adapted to Loom's event log. Every unit's evidence is verified against the turn before it's kept, so the brain doesn't remember things that were never said. Retrieval is hybrid too — exact entity matches (file paths, symbols, error codes) unioned with BM25 over the text and a trigram channel for typos and word forms, and a real embedding channel if you turn one on — with failures and constraints biased to the top of the brief, because getting burned twice is worse than missing a detail.
The brain is the project's, not each agent's: a fact one agent learns is scoped to the
chat, not walled off to whoever happened to learn it, so it reaches whichever agent takes
the baton next. (The brain-shared test makes this concrete —
five agents each learn one fact, and every other agent's brief then carries all five.) The
**B
