Loom keeps your agents on track.

CLI reference

This page documents every loom command: the six built-in commands and the 34 tool commands that mirror the Loom MCP tools your agent uses.

Check your installed version with loom --version.

How the command line works#

loom [--store NAME] <verb> [--params '<json object>' | --params-file PATH]
loom <built-in> [options]
  • Built-in commands (setup, login, drop_store, fleet, residual, --version) have their own options. They are listed first below.
  • Tool commands run the MCP tool of the same name. They take their parameters as a JSON object, either inline with --params or from a file with --params-file. The two options are mutually exclusive. With neither, the parameters are {}.
  • --store NAME fills in the tool's store parameter when the tool has one and store is not already in the JSON. For tools with no store parameter, --store is an error; put the store in the documented parameter instead (for example namespace in query).
  • Short names. The loom_ and kanban_ prefix is optional: loom list_stores runs loom_list_stores. whoami has no prefix and no alias. The short name also works after leading options, so loom --store kanban describe_store runs loom_describe_store. Headings below show the full tool name.
  • Built-in help. In v0.674, loom --help lists only the tool commands, and loom setup --help and loom hosted-setup --help print unknown argument '--help' (exit 2); loom fleet --help works. A later release lists the built-in commands in loom --help and answers loom setup --help.
  • Per-tool help. loom <tool> --help (v0.674 or later; also -h, and with the short name) prints that tool's own usage, its parameters (name, type, and = default or (required)) and its description, and exits 0. It needs no store and sends nothing.
  • Output. A tool's result is printed as indented JSON on stdout. The first command that needs embeddings downloads the model and prints progress lines on stderr before the JSON.
  • Stores load themselves. loom is a one-shot process. When you name a store other than your default, it connects that store first, so you never need load_store on the command line.
loom --store coding save --params '{"entity":"Decision(\"Use SQLite\")","content":"Simple to run."}'

Exit status#

CodeMeaning
0Success.
1setup or login was cancelled (for example, you declined the terms).
2Usage error, invalid parameters, or an error from a built-in command (including setup or login with no terminal to prompt on).
130setup or login was interrupted with Ctrl-C.
3A lifecycle result was not ok: a store operation still pending or failed, a partial setup or login, or a drop_store that did not destroy the store.

A domain answer is not an error. For example hard_delete reporting existed: false still exits 0.

Choosing a server#

setup and login pick the server in this order. --base applies only to setup, login, and drop_store:

Stepsetuplogin
1--base URL on the command.--base URL on the command.
2The server recorded in stores.toml, when it has exactly one store section and that section has a web_origin.The LOOM_WEB_BASE environment variable.
3The LOOM_WEB_BASE environment variable.https://loomcloud.ai.
4https://loomcloud.ai.

With zero sections, two or more sections, or a single section that has no web_origin, setup skips step 2. Once the server is chosen, setup signs in only when stores.toml holds no section with a store_key for it (a section counts for the server named in its web_origin, or https://loomcloud.ai when it has none); otherwise it prints Using your existing Loom stores for <server>; run loom login to sign in again. (loom login --base <server> for another server) and goes on to the harnesses. login always signs in. After a normal sign-in stores.toml holds several sections, so step 2 applies mainly to a computer whose file holds only one pasted section (see Join a Kanban board from another computer). A recorded web_origin that is not a valid server address stops setup before any prompt: loom setup: stores.toml section '<name>' has an invalid web_origin (exit status 2).

setup also installs the Claude Code and Codex plugins from the server it chose (<server>/loom-plugins.git) and runs opencode auth login <server> for OpenCode. See setup.

drop_store takes the server from the web_origin of the section it destroys (a section with none is https://loomcloud.ai). Its --base is optional and must match that origin, or it stops with --base does not match the server store '<section>' is connected to.

https is required, except for a private-range IP address (10/8, 172.16/12, 192.168/16, 100.64/10, or fc00::/7), where plain http is allowed. A bad --base or LOOM_WEB_BASE (including a recorded web_origin) fails before any request is sent.

Built-in commands#

Setup and sign-in#

setup#

loom setup [--harness codex|claude|opencode]... [--json] [--base URL]
loom setup --mode private --config /abs/path.json [--action ACTION] [--apply]

Signs you in with an email code, provisions your stores, writes ~/.loom/stores.toml, and installs and verifies Loom in each coding harness you select. Safe to re-run: when stores.toml already holds your credentials for the server, it skips the sign-in and only configures the harnesses. hosted-setup is an older spelling of the same command. The full walkthrough is in Set up and log in.

OptionMeaning
--harness HSelect a harness without the prompt. Repeat for several. Names: codex, claude, opencode.
--jsonPrint one JSON result on stdout. Prompts and progress go to stderr. A failure prints {"status": "error", "detail": "..."} (exit 2), or {"status": "cancelled", "detail": "..."} (exit 1), on stdout as well as the message on stderr.
--base URLThe server to use, and where the plugins come from (<server>/loom-plugins.git). See above.
--mode privateRun the private single-host server lifecycle instead of the client flow.
--config PATH(private mode) Absolute path to the server configuration JSON.
--action ACTION(private mode) One of init, plan, preflight, install, start, stop, status, restart, upgrade, recover-key. Default is plan.
--apply(private mode) Actually make the change; needs root.

Exit status: 0 ok, 1 cancelled, 2 error, 3 partial result, 130 interrupted with Ctrl-C.

Installing the plugins depends on the server:

  • Claude Code and Codex are installed with <server>/loom-plugins.git, and OpenCode with opencode auth login <server>. For the hosted service the server is https://loomcloud.ai.
  • If the server is an http:// address, the Claude Code and Codex plugin install is skipped, because plugins need an https server. Your stores are still set up. setup prints Plugins need an https server: the Loom plugin marketplace is not served over http, so the claude/codex plugin install was skipped. Your store is set up. and the harness row reads claude skipped (plugins need an https server). The result is partial, so the exit status is 3. With --json, the harness entry has "action": "skipped" and "ok": false.
  • If Claude Code or Codex already has a loom marketplace from a different server, setup changes nothing for that harness and its row reads FAILED -- claude: the existing Loom marketplace comes from <listed source> but this setup is for <server> (<server>/loom-plugins.git), so nothing was changed. To switch deliberately, run ..., followed by the removal commands for that harness and the loom setup command to re-add it (exit status 3). When the harness does not say where its marketplace came from and the server is not https://loomcloud.ai, setup continues and adds the note the existing Loom marketplace's origin is not exposed by this harness, so it was updated without checking it came from <server>.
  • Other harnesses are installed independently; one failure does not stop the others.

hosted-setup#

An alias for setup, accepted indefinitely. It takes the same options and has the same exit status.

login#

loom login [--json] [--base URL]

Authenticates and writes your stores to ~/.loom/stores.toml without touching any harness. Use it to connect a second computer, or to get back a store after logout.

login (and the sign-in half of setup) waits up to 120 seconds for your stores. A single slow status check inside that wait is retried instead of failing, and the first sign-in waits until all three starter stores (coding, loom_demo, kanban) are available. Messages you can see, all exit status 2:

MessageMeaning
provisioning is taking longer than expected; run login again with the same email to check status -- it is safe to retryThe 120 seconds ran out.
Trial capacity is currently full. Please try again later.Hosted only: no room for a new trial account right now.
provisioning failed; run login again to retry -- it is safeThe server reported a failure. A provisioning run that was killed before it finished is reported this way too (from v0.674), so a retry works instead of waiting forever.
too many sign-in code requests for this address; wait a few minutes and run it againThe server throttled the code request and sent no email (v0.674 or later).
stores.toml already binds [<name>] to a different store; refusing to replace it. Remove or rename that section first.An existing section would be rebound to another store. This also applies when the incoming section has no store line.

If your account lists two stores with the same section name, the first (oldest) one is kept. The other is not configured, is not listed in the configured in ... line, and a warning goes to stderr (exit status stays 0): Warning: store <handle> was not configured locally: its section name [<name>] is already used by another of your stores and it needs a distinct alias.

Exit status: 0 ok, 1 cancelled, 2 error, 3 partial result, 130 interrupted with Ctrl-C. With no terminal to prompt on (closed or non-interactive input, or Ctrl-D) it prints one line, loom login: no input available (stdin is closed or not a terminal); run it from an interactive terminal, and exits 2. Ctrl-C prints loom login: interrupted; anything already sent to the server may have completed. Re-running loom login is safe. One stderr line, never a traceback.

If your account's coding store was dropped, login still writes the stores the account has (v0.674 or later; earlier it kept polling until the 120 seconds ran out). An account with no stores at all is provisioned again.

drop_store#

loom drop_store SECTION --confirm SECTION [--json] [--base URL]

Destroys the store named SECTION on its server, then removes its section from stores.toml. Irreversible. --confirm must repeat the name exactly. See Manage stores.

Exit status: 0 ok, 2 error or missing confirmation, 3 the store was not destroyed or its destruction could not be confirmed (if the server closes the connection before answering, drop_store prints one line saying whether the drop happened is unknown and that your local section was kept, and exits 3; check the store in your Loom web app before retrying; from v0.672, dropping the store that authenticates the request ends with 3 and keeps the local section; with --json the explanation is the result's detail (status and server_status are credentials_rejected), and without --json nothing is printed, so check the exit status and run loom logout --params '{"section":"SECTION"}' after confirming with loom list_stores; see Manage stores). With --json, errors found before any request (unknown section, mismatched --base, wrong --confirm) are still one plain line on stderr with exit 2, not a JSON object.

Version#

--version#

loom --version

Prints loom <version>. It must be the first argument, and it works even when nothing else is configured. A build with no release stamp prints loom version unknown: this build carries no release stamp. The short form is -V.

Kanban fleet commands#

fleet#

loom fleet [--store STORE] [--stores-file PATH] <subcommand> [subcommand options]

Manages the desired state and credential vault for Kanban worker fleets. The store defaults to kanban. Subcommands: key-create, key-install, profile-put-claude, profile-put-git-ssh, profile-put-loom-store, profile-put-app-secrets, config-publish --file PATH, and show [--config-id ID]. Everything after fleet is passed to the fleet tool unchanged, so loom fleet --help shows its own help. See Fleet.

Put the global flags (--store, --stores-file) before the subcommand; loom fleet show --store kanban is rejected as an unrecognized argument. Options that belong to a subcommand, such as --config-id for show, --file for config-publish, and --replace for key-create, go after it. Output is compact JSON, not indented. key-create and key-install refuse to overwrite an existing vault key: they print <subcommand>: a key already exists at <path>; nothing was changed on stderr and exit 1 (no traceback); add --replace to replace it on purpose. key-create refuses at once; key-install first asks for the key at the hidden prompt Fleet vault key (base64; input hidden):, so with no terminal (closed stdin) it ends with a Python EOFError traceback (exit 1) instead of the refusal.

residual#

loom residual [--store STORE] [--config-id ID] [--stores-file PATH] <subcommand> [subcommand options]

Runs the per-computer fleet reconciler. Subcommands: preflight, install, remove, status, once, daemon --max-cycles N, and prune-uv-cache. Everything after residual is passed through unchanged. Without --store, it reconciles every store in stores.toml. See Fleet.

Put the global flags (--store, --stores-file, --config-id) before the subcommand. Only daemon takes a subcommand option (--max-cycles); the other subcommands take none. daemon also installs the per-computer schedule (a cron entry on Linux); undo it with loom residual remove. Output is compact JSON, except loom residual preflight, which prints nothing and exits 0.

Tool commands#

The remaining commands run MCP tools. Each section shows the parameter object. A parameter shown with a value is required; one marked "optional" can be omitted. Where a tool takes store, you can supply it with --store instead.

Store lifecycle#

These talk to a web server. unlock_store and set_store_domain use the web_origin recorded in the store's section (for an unknown section name they stop before any request with no store named '<name>' in <path>. Configured: <sections>). create_store uses the web_origin of the section it authenticates with (auth_store, default the first section in stores.toml); a section with no web_origin means https://loomcloud.ai. It does not read LOOM_WEB_BASE, so a private install needs no environment variable. logout only edits stores.toml on this computer and contacts no server. Failures exit with status 3 and print a JSON object with "status": "error" and a detail.

loom_create_store#

Create a new store and register it in stores.toml. Cannot create your first store; use loom login for that.

{"name": "notes", "usage": "Personal notes", "domain": "PersonalDomain", "auth_store": "coding"}

usage, domain, and auth_store are optional. Example: loom create_store --params '{"name":"notes"}'.

A hyphen in name becomes _ in the section name and handle. An invalid domain is currently accepted silently and the store gets CodingDomain, so use one of the four domains. If stores.toml already has a section with the name the server would give the store (the name lower-cased, with anything other than letters, digits and _ turned into _, at most 30 characters), create_store refuses before sending any request: stores.toml already has a [<section>] section; refusing to replace it. Remove or rename that section first, or choose another name. Nothing was created on the server. It exits 3 with "status": "error" and that text as detail, and no store slot is used. Servers and clients older than v0.674 sent the request anyway, created a second store (handle ending _2) and then failed locally with exit 3, which used up a store slot.

A server limits how many stores an account may have; at the limit the result is {"status": "store_limit", ...} with exit 3.

On success the result is {"status": "ok", "server_status": "ready", "section": ..., "handle": ..., "stores_toml": ..., ...}: the store exists and its section is already written to stores.toml. This works the same on the hosted service and on a private server. No credential is printed.

Any other result exits 3 with status, server_status, rid, name, and detail. Unless the status is store_limit, denied, not_found, capacity_unavailable, or default_forbidden, the detail ends with a recovery hint, because the server may have made the store even though this command could not register it:

Store 'notes' may already exist on the server; run `loom login --base <origin>` to register it locally instead of retrying the create.

The hint says loom login with no --base when the server is https://loomcloud.ai. Do not retry the create: a second attempt makes a second store and uses another slot of your limit. The same hint ends the detail when the call times out (status: "pending", after Still provisioning after 120s; the request may still complete.), and in the rare case where the server could not produce the new section: store became ready but no authenticated bundle was returned. Other errors, such as having no store to authenticate with yet, report "status": "error" with a detail and no rid.

loom_unlock_store#

Unlock a configured store and wait until it is ready.

{"section": "notes", "auth_store": "coding"}

auth_store is optional. Example: loom unlock_store --params '{"section":"notes"}'.

loom_set_store_domain#

Change a store's domain: CodingDomain, PersonalDomain, ReconciliationDomain, or LogDomain.

{"section": "notes", "domain": "CodingDomain", "auth_store": "coding"}

auth_store is optional. Example: loom set_store_domain --params '{"section":"notes","domain":"LogDomain"}'.

loom_logout#

Remove one store's credential from this computer. Local only; the credential stays valid anywhere else it was copied.

{"section": "notes"}

Example: loom logout --params '{"section":"notes"}'.

Connection and discovery#

loom_list_stores#

List the stores you can target. Takes no parameters.

{}

Example: loom list_stores.

loom_current_default_store#

Report the effective default store for this session and whether it is a session override. Takes no parameters.

{}

Example: loom current_default_store.

loom_describe_store#

Describe one store: its description, namespace scheme, fact counts by type, custom types, and the server version. The description holds the store's registry text when the server has none of its own (v0.674 or later).

{"store": "coding"}

Example: loom describe_store --params '{"store":"coding"}'.

loom_load_store#

Connect a named store for later calls. set_default routes "default" to it for the current session only.

{"name": "kanban", "set_default": false}

Both parameters are required. Example: loom load_store --params '{"name":"kanban","set_default":false}'.

loom_unload_store#

Disconnect an extra store. The primary store cannot be unloaded.

{"name": "kanban"}

Example: loom unload_store --params '{"name":"kanban"}'.

Writing facts#

loom_save#

Save one fact. Returns {"fact_id": "..."}.

{
  "store": "coding",
  "entity": "Directive(\"Never deploy on a Friday\")",
  "content": "Friday deploys caused two outages.",
  "known_from": "2026-09-30",
  "edges": [["Has()", "Importance(\"critical\")"]],
  "source_text": "optional verbatim source"
}

store, entity, and content are required. Optional: known_from, edges, source_text, source_session_index, source_turn_index, source_observed_at, source_run_id. Example: loom --store coding save --params '{"entity":"Decision(\"Use SQLite\")","content":"Simple."}'.

loom_save_many#

Save many facts in one call. Returns one {"index", "fact_id"} or {"index", "error"} per item for facts the server rejects (for example an unknown type). An item missing entity or content fails the whole call with item N missing required 'entity' (or 'content', when content is the missing field) and exit 2; nothing is saved.

{"store": "coding", "facts": [{"entity": "Decision(\"A\")", "content": "..."}, {"entity": "Decision(\"B\")", "content": "..."}]}

Example: loom --store coding save_many --params-file facts.json.

loom_forget#

Retract a fact. It stays in history but disappears from reads. Returns a string: ok: <entity> (for example ok: Decision({...})), noop_already_retracted: <entity>, or noop_no_such_fact.

{"store": "coding", "fact_id": "<id>"}

Example: loom --store coding forget --params '{"fact_id":"<id>"}'.

loom_hard_delete#

Permanently delete a fact and all its edges. Cannot be undone. Returns {"fact_id", "existed", "status"}; status is ok or noop_no_such_fact.

{"store": "coding", "fact_id": "<id>"}

Example: loom --store coding hard_delete --params '{"fact_id":"<id>"}'.

loom_add_edges#

Add [relation, target] edges to an existing fact without changing the fact. Returns one result per edge; a failed edge is an entry with an error code, and the command exits 3 (from v0.674) when any edge failed.

{"store": "coding", "fact_id": "<id>", "edges": [["Has()", "Importance(\"critical\")"]]}

Example: loom --store coding add_edges --params-file edges.json.

loom_remove_edges#

Remove edges from an existing fact. Returns the fact id for each removed edge and -1 for each edge that matched nothing.

{"store": "coding", "fact_id": "<id>", "edges": [["Has()", "Importance(\"low\")"]]}

Example: loom --store coding remove_edges --params-file edges.json.

Reading facts#

loom_query#

Run a named rule and return its rows. Takes no store; the store is part of namespace, written <store>_root for built-in rules or <store>_user for the store's own rules.

{
  "namespace": "coding_root",
  "rule_name": "facts_by_groups",
  "args": [[[["SubTypeOf()", "Directive"]]]],
  "when": "2026-09-30",
  "full": false,
  "include_history": false
}

namespace and rule_name are required. Optional: args (the rule's middle arguments only), when (a YYYY-MM-DD cutoff), full, include_history. include_history brings back superseded and disputed rows. On facts_by_groups it never returns retracted facts; to read a retracted fact call fact_history with "include_history":true (its rows carry current_status: "retracted" and retracted_at; without the flag fact_history also leaves retracted rows out). text_search honours when like facts_by_groups: only facts known by that date match (v0.674 or later). A rule called with the wrong number or shape of arguments fails with a usage error such as `rule 'text_search' was called with the wrong number or shape of args (expected text_search(needle, label_exprs, mode)); pass only the middle arguments, never dbpath or when instead of a raw MethodError (v0.674 or later). Example: loom query --params '{"namespace":"coding_root","rule_name":"list_types"}'`. See Save and load facts.

loom_nearest_types#

Find the existing entity types of the facts most similar to some text. Use it before inventing a new type.

{"store": "coding", "text": "release freeze policy", "k": 30}

k is optional (default 30). Example: loom --store coding nearest_types --params '{"text":"release freeze policy"}'.

loom_read_probe_bundle#

Meaning-based retrieval. Returns current_facts, stale_context and contentions.

{"store": "coding", "query_text": "deployment rules", "max_facts": 16}

Provide at least one of query_text, label_queries, label_exprs. label_exprs is [[rel,target],...] and label_queries is [[[rel,target],"inquiry text"],...]; a wrong shape (for example a bare [relation, target] pair in label_queries, or a non-pair in label_exprs) is rejected with an error that shows the expected shape (from v0.674), and a call with none of the three says read_probe_bundle_text needs at least one of label_queries, label_exprs or query_text. Optional: label_queries, label_exprs, mode (or by default, or and; anything else fails with exit 2), top_k_sources (default 5), max_facts (default 16), when, since, until. Example: loom --store coding read_probe_bundle --params '{"query_text":"deployment rules"}'.

Schema and rules#

loom_declare_types#

Declare new entity or relation types.

{"store": "coding", "type_specs": [{"name": "Runbook", "parent": "Entity", "kind": "concrete", "fields": ["title"], "why_new": "Operational runbooks"}]}

Each spec needs name and parent (a missing parent is rejected). A concrete type with no fields is rejected unless why_no_fields is given. kind (concrete or abstract, default concrete) and why_new are recommended but not enforced; why_not_consolidate is an optional note. A parent must be an existing abstract type, or one declared earlier in the same call. Valid abstract parents in a new store include Entity, Document, Record, Resource, Event, Person, and Label. A concrete parent, such as Decision, is rejected. A rejected spec does not fail the command: it appears in the rejected list with a reason. The command exits 0 when at least one type was declared and exits 3 (from v0.674) when specs were rejected and nothing was declared. Example: loom --store coding declare_types --params-file types.json.

loom_write_user_files#

Replace the store's user_rules.jl and/or user_atoms.jl, reload them, and commit the change. Both succeed or the previous content is restored.

{"store": "coding", "rules_content": "<file text>", "atoms_content": null}

Pass null (or omit a field) to leave that file unchanged. Returns {"atoms_path", "rules_path"}. Example: loom --store coding write_user_files --params-file files.json.

loom_read_user_rules#

Return the current user_rules.jl content as {"content": ...}. A path field (the file's location on disk) appears only when the files are local; for a store on a server it is omitted (earlier releases returned "path": null).

{"store": "coding"}

Example: loom --store coding read_user_rules.

loom_read_user_atoms#

Return the current user_atoms.jl content as {"content": ...}. A path field appears only when the files are local; for a store on a server it is omitted (earlier releases returned "path": null).

{"store": "coding"}

Example: loom --store coding read_user_atoms.

loom_schema_log#

Show the version history of the store's rule and type definitions, not of facts.

{"store": "coding", "max_entries": 20}

max_entries is optional (default 20). Example: loom --store coding schema_log.

loom_revert#

Roll back the last N commits of the rule and type history. A revert is itself a commit, so running revert twice undoes the first revert.

{"store": "coding", "n_commits": 1}

n_commits is optional (default 1). The revert is applied, schema_log shows it, and the command returns {"atoms_path", "rules_path", "reverted": <n>} (the two paths are file paths on the machine that holds the store, so for a server store they are server paths). Releases before v0.674 applied the revert but then exited 2 with loom julia_exception: UndefVarError: \d\ not defined; if you see that, check schema_log before running it again. Example: loom --store coding revert.

loom_reap_unused_types#

Remove user-declared types that have no facts, no child types, and no edge uses. With dry_run nothing is removed; the types that would be removed are listed in skipped, and reaped is empty.

{"store": "coding", "dry_run": true}

dry_run is optional (default false). Returns {"reaped", "skipped", "dry_run"}; a real run lists the removed types under reaped. A parent abstract type is reaped only by a second run, after its last child is gone. Example: loom --store coding reap_unused_types --params '{"dry_run":true}'.

loom_suggest_consolidations#

Suggest clusters of similar sibling types that might be merged. A recommendation only.

{"store": "coding"}

Example: loom --store coding suggest_consolidations.

loom_consolidate_types#

Merge several sibling types into one generic type, migrating their facts in place.

{"store": "coding", "plan": {"new": {"name": "LifeEvent", "parent": "Entity", "fields": ["kind", "date"]}, "migrations": [{"from": "Marriage", "set": {"kind": "marriage"}, "map": {"when": "date"}, "drop": []}]}}

Returns consolidated, dropped_types, migrated_fact_ids, rejected, and reembedded. Example: loom --store coding consolidate_types --params-file plan.json.

loom_reembed#

Recompute the search vector for specific facts. Rarely needed by hand.

{"store": "coding", "fact_ids": ["<id>"]}

Returns {"reembedded": n}. Example: loom --store coding reembed --params-file ids.json.

Kanban#

See Set up a Kanban board and Kanban daily use for how these fit together.

publish_task, claim_run, record_capability_assessment, and post_message check that the agent id is the live session of a coding harness, with two exceptions: record_capability_assessment with "failures": [] skips the live-session check (it applies only when failures is non-empty), so even an unknown agent id exits 0 with deduplicated true; and post_message rejects a non-positive ttl_seconds before the identity check. Run them from inside that harness session. With any other id they fail with "owns no session transcript on this machine" and exit 2. For a Codex agent run from a plain shell, the id must also name a real Codex session file on this machine (a regular file at ~/.codex/sessions/YYYY/MM/DD/rollout-<timestamp>-<thread id>.jsonl, or under CODEX_HOME); an invented or stale id fails with kanban identity: codex@<id> owns no Codex session file on this machine, so it cannot be the caller's live session. Run from the owning Codex session. (exit 2). claim_run, record_capability_assessment, and post_message also need the agent to be registered on the board, otherwise they fail with kanban_agent_status:agent_status_identity_mismatch:unregistered; a post_message destination must be a registered agent id.

whoami#

Return this session's agent identity, <harness>@<session-id>.

{"harness": "claude", "native_conversation_id": ""}

native_conversation_id is optional; when empty, the harness's session environment variable is used. Outside a harness session, without native_conversation_id, it exits 2 with native conversation id unavailable for <harness>; agents run inside a harness (Claude or Codex) and pass its native id explicitly; a script may publish ownerless tasks as script@<name>. script is reserved: whoami with harness script is refused (script@<name> may only publish ownerless tasks (no declared owner); claiming, messages and events need a live agent in a harness). Example: loom whoami --params '{"harness":"claude"}'.

loom_kanban_init#

Install the Kanban protocol in one store. Safe to repeat.

{"store": "kanban"}

Only this command defaults store to kanban; every other tool that takes store needs --store or a store field. Example: loom kanban_init.

kanban_publish_task#

Publish a task. Returns task_id, fact_id, and status.

{
  "store": "kanban",
  "submitter_agent_id": "claude@<session>",
  "title": "Check the docs build",
  "brief": "Run the docs build and report failures.",
  "required_capabilities": {"python": "python3 --version prints 3.10 or later"},
  "total_run_limit": 1,
  "idempotency_key": "docs-build-001",
  "lease_seconds": 600,
  "depends": [],
  "declared_owner_agent_id": null,
  "required_runs": null
}

Required: store, submitter_agent_id, title, brief, required_capabilities, total_run_limit, idempotency_key. Optional: lease_seconds (default 600), depends, declared_owner_agent_id, required_runs. A task with no declared owner must have total_run_limit of 1. A script or CI job with no harness session can publish as submitter_agent_id script@<name> (a simple name with no @ or spaces), with no live-session check, registration or heartbeat, but only ownerless tasks: with a declared_owner_agent_id it fails with kanban task: script@<name> (a simple name, no '@' or spaces) may only publish ownerless tasks, and claim_run, post_message and the other Kanban calls refuse script@ ids. This is a check in the loom client, not something the server enforces. Example: loom publish_task --params-file task.json.

kanban_claim_run#

Reserve one execution of a task.

{"store": "kanban", "task_id": "<task id>", "agent_id": "claude@<session>", "idempotency_key": "claim-001"}

Example: loom claim_run --params-file claim.json.

kanban_record_capability_assessment#

Record a worker's pre-claim capability check. Pass an empty list when every check passes.

{"store": "kanban", "task_id": "<task id>", "agent_id": "claude@<session>", "failures": [{"capability": "python", "reason": "python3 not found", "operation": "python3 --version", "outcome": "command not found"}]}

Each failure has exactly capability, reason, operation, and outcome, all non-empty strings. Example: loom record_capability_assessment --params-file assessment.json.

kanban_post_message#

Post a message to another agent or the operator.

{"store": "kanban", "from_agent_id": "claude@<session>", "destination": "<agent id>", "body": "Ready for review.", "ttl_seconds": 3600, "idempotency_key": "msg-001"}

ttl_seconds must be positive. Example: loom post_message --params-file message.json.