Drive a loomcycle agentic runtime
from inside Claude Code — spawn and cancel runs, list them, snapshot the
runtime, submit evaluations, inspect agent memory, and import .claude/ repos
— all through slash commands and skills, with no JSON pasting.
This plugin is the UX layer over loomcycle's loomcycle mcp stdio server.
loomcycle exposes its primitives as MCP meta-tools (spawn_run, cancel_run,
list_runs, snapshot ops, evaluation, agentdef, …); this plugin pre-wires
that server and wraps the common workflows in operator-friendly commands.
Why a plugin and not just
loomcycle mcp install? The MCP server is plumbing — you'd still have to discover each tool, compose the rightopdiscriminator, and remember each input schema. The plugin is the kitchen: installed once, it pre-wires the server and gives you/loomcycle:run,/loomcycle:runs, etc.
- loomcycle on PATH (or set
bin_pathat install). The plugin does not bundle or start the loomcycle binary — install it separately (Homebrew / Docker / release binary) and have an instance reachable. - A
loomcycle.yamltheloomcycle mcpserver will load. - Claude Code v2.1+ (plugin support).
Run these in Claude Code (the marketplace registers under the name loomcycle,
declared in marketplace.json):
/plugin marketplace add denn-gubsky/claude-code-plugin-loomcycle
/plugin install loomcycle@loomcycle
loomcycle@loomcycle reads as "the loomcycle plugin from the loomcycle
marketplace" — the @loomcycle suffix disambiguates if you have other
marketplaces installed (the bare /plugin install loomcycle also works when
it's unambiguous).
At install you'll be prompted for:
| userConfig | Purpose | Default |
|---|---|---|
bin_path |
Path to the loomcycle binary | loomcycle (PATH lookup) |
base_url |
Upstream runtime URL the plugin proxies to — a loomcycle instance must be running here | http://127.0.0.1:8787 |
auth_token |
Bearer sent to the upstream (as LOOMCYCLE_MCP_UPSTREAM_TOKEN); its principal governs what the plugin can do. OS keychain, never the repo |
— |
The bundled MCP server entry (.mcp.json) launches
loomcycle mcp --upstream <base_url> automatically when the plugin is enabled.
This is a thin client (loomcycle RFC R): it runs a stdio↔/v1/_mcp proxy to
the loomcycle runtime already running at base_url and boots no runtime of its
own — no providers, scheduler, sweepers, or port to bind.
Single-runtime invariant. Earlier versions launched
loomcycle mcp --config … --no-http, which booted a full second runtime (listener muted) next to your real one. Two runtimes sharing one state each have their own in-process event bus, so a resolved interruption or a cancel raised on one never wakes a run owned by the other — the source of the "session wedged / interruption never resumes" failures. The thin client fixes this structurally: there is exactly one runtime, and the plugin is its client. You must have a loomcycle instance running atbase_url(start it separately vialoomcycle/brew services/Docker); the plugin no longer starts one. Requires a loomcycle build that supportsloomcycle mcp --upstream.
The bundled .mcp.json registers the loomcycle server only for sessions where
this plugin is enabled. If a project doesn't load the plugin (or a subagent
that doesn't inherit it needs the tools), mcp__loomcycle__path /
mcp__loomcycle__document / … won't appear until that project has its own
loomcycle server entry. Add a .mcp.json at the project root pointing at the
same thin client:
{ "mcpServers": { "loomcycle": { "command": "/abs/path/to/loomcycle-mcp-upstream.sh" } } }where the wrapper sources your env (for the bearer) then
exec loomcycle mcp --upstream http://127.0.0.1:8787 — so the runtime is shared
(one store) and the token stays out of the file. (Inline form:
"command": "loomcycle", "args": ["mcp","--upstream","<base_url>"], "env": {"LOOMCYCLE_MCP_UPSTREAM_TOKEN": "…"} — but that puts the bearer in the file; prefer the wrapper.) Then restart.
The "auto-advertise — no .mcp.json edit" note in the version table below is
about an already-registered server picking up newly-shipped tools; it is
not a substitute for registering the server the first time.
The marketplace tracks main, so updating is two steps — refresh the
marketplace cache from git, then update the installed plugin:
/plugin marketplace update loomcycle
/plugin update loomcycle@loomcycle
- The first command re-pulls
marketplace.json+ the plugin from GitHub; the second installs the new version. Run them in this order —/plugin updatealone won't see a new release until the marketplace cache is refreshed (a stale cache is also why a freshaddcan appear to "do nothing" after a release). - Re-running
/plugin installon an already-installed plugin does not upgrade it — use/plugin update. - Prefer hands-off? Open
/plugin→ Marketplaces → selectloomcycle→ enable auto-update, and it pulls new releases at startup. - After updating, restart Claude Code so the bundled
.mcp.json(theloomcycle mcp --upstreamserver) is relaunched with any wiring changes.
To pin or roll back to an exact release, add the marketplace at a tag and reinstall:
/plugin marketplace add denn-gubsky/claude-code-plugin-loomcycle#v0.32.0
/plugin install loomcycle@loomcycle
All commands are namespaced under the plugin name: /loomcycle:<command>.
| Command | Wraps | Purpose |
|---|---|---|
/loomcycle:connect [--user=<id>] [--bearer=<tok>] [--base-url=<url>] [--persist] |
(session state) | Set the active loomcycle identity reused by later commands. The bearer is the per-run user_bearer, not the API token. |
/loomcycle:run <agent> [--user=<id>] [--compact] <prompt…> |
spawn_run |
Spawn a run; the result (final text + agent_id cancel handle + run_id) streams back. --compact turns on per-run context-compaction (≥ v0.32). |
/loomcycle:fanout <agent> [--count=N] [--user=<id>] <prompt…> |
spawn_runs |
Fan out up to 32 concurrent runs of one agent in a single call; renders the index-aligned results table (≥ v0.32). |
/loomcycle:compact <agent_id> [--reason=<text>] |
compact_run |
Summarize a parked run's history to free context, then continue (≥ v0.32). |
/loomcycle:runs [--user=<id>] [--status=<s>] [--limit=<n>] |
list_runs |
List recent runs as a table. user_id is required (set it once via connect). |
/loomcycle:cancel <agent_id> [--reason=<text>] |
cancel_run |
Cancel a running agent; cascades to sub-agents; idempotent. |
/loomcycle:snapshot <create|list|restore|delete> [id] |
the 4 snapshot tools | Runtime snapshot ops from the IDE. Restore/delete confirm first. Admin token only — runtime-global, withheld from a substrate:tenant session (RFC AG). |
/loomcycle:eval <run_id> <score> [--rationale=<text>] |
evaluation (op=submit) |
Record an evaluation against a completed run. loomcycle never auto-promotes on score. |
/loomcycle:memory <recall|search|add|get|set|list> [--scope=agent|user] [args…] |
memory |
Inspect/edit an agent's memory: semantic recall/search, add conversation facts, or plain key/value. add/recall need a memory-layer backend (v0.16). |
/loomcycle:operator-token <create|rotate|retire|get|list> [--name=<n>] [--tenant=<id>] [--subject=<s>] [--scopes=a,b] |
operatortokendef |
Mint/rotate/retire per-principal bearer tokens (RFC L multi-tenant auth, loomcycle ≥ v0.17). Operator-admin only; create/rotate show the plaintext once. |
Skills load automatically when their description matches what you're doing:
| Skill | What it does |
|---|---|
loomcycle-spawn-evaluator |
Spawn an independent evaluator agent to score the current transcript or a named run, then offer to record the score. |
loomcycle-replay-failed-run |
get_run → diagnose the failure → re-spawn with the smallest fix. Stops after a repeat failure and recommends an operator-side config fix. |
loomcycle-diff-agentdefs |
Diff two AgentDef versions by def_id (system_prompt / allowed_tools / max_tokens / lineage). Descriptive only. |
loomcycle-import-claude-code |
Guided wrapper over loomcycle import claude-code (RFC C2): dry-run → review the lossy report → --write → validate. |
loomcycle-configure |
Author/tune loomcycle.yaml + env: providers, model tiers, user_tiers, fallbacks, the env-var catalogue, six deployment profiles (brew/in-system · containerized · true sandbox · server · multi-tenant · cloud), and the runtime primitives — filesystem Volumes (RFC AH), the Bashbox in-process sandbox (RFC AJ), the Path VFS (RFC AL), and chunked-graph Documents (RFC AK). Never writes the env file — prints the lines. |
Both hooks ship disabled — they no-op unless you opt in with an env var.
| Hook | Enable with | Action |
|---|---|---|
| capture-run-telemetry | LOOMCYCLE_PLUGIN_TELEMETRY=1 |
Append {ts, run_id, agent_id} to ${CLAUDE_PLUGIN_DATA}/run-telemetry.jsonl after each spawn_run. |
| auto-snapshot-on-error | LOOMCYCLE_PLUGIN_AUTO_SNAPSHOT=1 |
On a state-mutating tool error, run loomcycle snapshot --description pre-error-<ts>. Needs LOOMCYCLE_AUTH_TOKEN (and optionally LOOMCYCLE_BASE_URL) exported in the shell that launches Claude Code — the hook reads the bearer from the environment, never from a substituted command string. |
loomcycle v0.17.0 (RFC L) adds per-principal bearer tokens (OperatorTokenDef,
lct_…), each bound to an authoritative {tenant_id, subject, scopes} resolved
from the token. How much of that the plugin enforces depends entirely on
which transport the bundled MCP server uses:
Since 0.21.0 the default stdio transport is a thin client that proxies to the
runtime's POST /v1/_mcp — the same principal-enforced path as the direct
HTTP transport. So the plugin's authority is governed by the token's principal
on the upstream, regardless of transport:
| Transport | What the plugin is | Isolation / scopes |
|---|---|---|
stdio thin client (default .mcp.json: loomcycle mcp --upstream) |
a proxy to the runtime's /v1/_mcp |
Principal-enforced by auth_token. An admin token (or an open-mode runtime) → full authority; a scoped lct_… token → confined (applyPrincipal overrides wider wire values; under-scoped calls get a scope refusal; reads tenant-filtered). |
HTTP (POST /v1/_mcp direct, see examples/mcp-http-tenant.json) |
same /v1/_mcp, direct transport (no local stdio process) |
Same principal enforcement. |
To confine the plugin to one tenant, just set auth_token to a scoped
lct_… bearer in the default config — no .mcp.json swap needed any more, since
the default already routes through the principal-enforced /v1/_mcp. (The
HTTP example remains a valid alternative
transport if you'd rather not run the stdio proxy process.)
A recent loomcycle line (RFC AG + AO, post-v1.4.0) makes the auth_token you
hand the plugin a first-class tenant identity, not just an admin key:
/v1/_mcpis now asubstrate:tenantroute (RFC AG). Earlier the loomcycle MCP transport ran as a global operator and the route requiredsubstrate:admin, so a tenantlct_…bearer was refused at the door — the plugin effectively needed an admin token. Now asubstrate:tenanttoken opens the session and everything inside is confined to its tenant. (On an older loomcycle build without the flip, a tenant token still 403s at the route — see the Compatibility note.)- A per-tool gate withholds the admin-only meta-tools. Inside a
substrate:tenantsession, the runtime-global / minting tools are hidden fromtools/listand refused ontools/call. Concretely, with a tenant token:- ✅ work, tenant-confined:
/loomcycle:run,/loomcycle:fanout,/loomcycle:runs,/loomcycle:cancel,/loomcycle:compact,/loomcycle:memory,/loomcycle:eval,/loomcycle:steer. - 🔒 need an admin token:
/loomcycle:operator-token(token minting) and/loomcycle:snapshot(runtime-global). A refusal here is expected, not a plugin bug — a confined tenant key is meant to be unable to mint or snapshot.
- ✅ work, tenant-confined:
- Declared principals (RFC AO) — one token for the UI and the plugin.
Instead of minting an
OperatorTokenDef, the operator can declare a stable login inloomcycle.yamland bind it to a secret in.env.local:Put that same token in the plugin'sprincipals: marketing: { tenant: acme, subject: marketing, scopes: [runs:create, runs:read, substrate:tenant], token_env: LOOMCYCLE_TOKEN_MARKETING }
auth_tokenand use it to log into the loomcycle Web UI (/ui/login). Both resolve to the same(tenant, subject), so anything the plugin's agents write in user scope (Memory, Documents, Paths) shows up under the identity the Web UI reads — no synthetic-operator mismatch. This is the simplest way to make a plugin-driven agent and the Web UI act as one identity.
/loomcycle:operator-token rotate mints a replacement valid alongside the old
one for the grace window. Two sharp edges to know:
- Creating the first admin
OperatorTokenDefdisables the legacyLOOMCYCLE_AUTH_TOKENfor inbound HTTP (loomcycle fails closed onto the substrate). Ifauth_tokenis still that legacy value, the HTTP-using auto-snapshot hook (and the HTTP transport above) will start returning 401 — updateauth_tokento a validlct_…admin bearer. - A running
loomcycle mcpcaptured its token at launch. After updating theauth_tokenuserConfig, restart Claude Code so the MCP server picks up the new bearer. Rotate within the grace window to avoid a gap.
The stdio command surface keeps working across a rotation regardless (stdio is operator-trust) — only the HTTP-authed paths (the snapshot hook, or the HTTP transport) depend on a currently-valid bearer.
git clone https://github.com/denn-gubsky/claude-code-plugin-loomcycle
claude --plugin-dir ./claude-code-plugin-loomcycle # load for one session
claude plugin validate ./claude-code-plugin-loomcycle # validate before publishing
# optional: shellcheck hooks/scripts/*.sh| This plugin | loomcycle | Claude Code |
|---|---|---|
| 1.5.0 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client; an instance running at base_url). Tracks loomcycle v1.5.0 — the RFC AG per-principal /v1/_mcp transport (PRs #549–#553) + RFC AO config-declared principals (#554–#555). The MCP tool contract is UNCHANGED — this is an auth/route change, not new meta-tools: /v1/_mcp moves substrate:admin → substrate:tenant (a tenant or config-declared-principal auth_token can now drive the plugin, confined to its tenant), and a per-tool gate withholds the admin-only meta-tools (/loomcycle:operator-token + /loomcycle:snapshot need an admin token). RFC AO lets the operator declare a (tenant, subject) login in loomcycle.yaml and use one token for both the Web UI and the plugin. /v1/_mcp; on an older build the route is still substrate:admin and a tenant token 403s (use an admin token, or upgrade loomcycle). |
≥ 2.1 |
| 1.4.0 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client; an instance running at base_url). Version-vector track through loomcycle's v1.2.0 → v1.4.0 line, grounded against source at the v1.4.0 tag. MCP contract is additive — adds the path (RFC AL) + document (RFC AK) meta-tools (the thin client auto-advertises them, so .mcp.json needs no edit). Skill grounding adds the three new primitives an operator configures: Bashbox (RFC AJ, v1.3.0 — LOOMCYCLE_BASHBOX_ENABLED + the host-command fallback), Path (RFC AL — allowed_tools:[Path]), and Documents (RFC AK — allowed_tools:[Document] + LOOMCYCLE_SQLMEM_ENABLED, the SQL Memory prerequisite, RFC AA v1.2.0). Thin-client --upstream wiring unchanged. |
≥ 2.1 |
| 1.1.1 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client; an instance running at base_url). Tracks loomcycle v1.1.1 — RFC AI interactive agentic sessions: a run with interactive: true parks at end_turn for operator steering (POST /v1/runs/{id}/input) and is re-attachable (GET /v1/runs/{id}/stream). Adds /loomcycle:steer (HTTP — there is no MCP steering tool) + reference/interactive.md; /loomcycle:run documents the interactive flag. MCP tool contract unchanged. (Documented retroactively in the 1.4.0 changelog backfill.) |
≥ 2.1 |
| 1.1.0 | Same runtime requirement as 0.21.0. Tracks loomcycle v1.1.0 (plugin jumped v0.32.0 → v1.1.0) — the RFC AH Volume primitive. LOOMCYCLE_READ_ROOT / WRITE_ROOT / BASH_CWD are fatal config-load errors, replaced by a volumes: block. Adds reference/volumes.md (migration, volumes: fields, VolumeDef + gates, ephemeral volumes, spawn narrowing) + the Volume section in loomcycle-configure; the volumedef meta-tool is now documented. (Documented retroactively in the 1.4.0 changelog backfill.) |
≥ 2.1 |
| 0.32.0 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client; an instance running at base_url). Re-grounded against loomcycle source at the v0.32.0 tag (loomcycle shipped v0.25.1 → v0.32.0). MCP contract grew 40 → 42 tools (both additive): spawn_runs (RFC Y external fan-out, ≤32 runs/call → /loomcycle:fanout) and compact_run (summarize a parked run → /loomcycle:compact); spawn_run gained an optional compaction field (→ /loomcycle:run --compact). Skill grounding adds the per-agent sampling: (v0.28.0) + compaction: (v0.32.0) blocks, LOOMCYCLE_RESUME_FANOUT (v0.31.0), and the corrected ${} deny-list (PG_DSN/LOOMCYCLE_PG_DSN/LOOMCYCLE_AUTH_TOKEN never interpolated, v0.32.0/#462). Thin-client --upstream wiring unchanged. |
≥ 2.1 |
| 0.25.1 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client). Docs-only, re-grounded against loomcycle source at the v0.25.1 tag (loomcycle shipped v0.24.0 → v0.25.0 → v0.25.1). MCP tool contract unchanged (the channel/context descriptions gained the new ops). Adds: LOOMCYCLE_MCP_SPAWN_RUN_TIMEOUT_MS now applies to the --upstream//v1/_mcp path the plugin uses (v0.24.0); the per-tenant webhook route POST /v1/_webhooks/{tenant}/{name} (v0.24.0, RFC N); the RFC S fan-in/fan-out primitives (Channel.await/broadcast, Context op=time, ScheduleDef max_fires — v0.25.0); and the F37 scheduler channel.publish declared-scope fix (v0.25.1). No plugin command/skill behavior change — pure version-grounding. |
≥ 2.1 |
| 0.23.5 | Same runtime requirement as 0.21.0 (loomcycle mcp --upstream thin client; an instance running at base_url). Docs-only, re-verified against loomcycle source at the v0.23.5 tag. Replaces the prior "post-v0.23.0 main" hedging with concrete tags now that they exist (loomcycle went v0.23.0 → v0.23.3 → v0.23.4 → v0.23.5, no v0.23.1/v0.23.2): robust anthropic-oauth-dev (status --probe F6/#392 + cross-process refresh lock F7/#391, both v0.23.3); the dynamic substrate (webhook→AgentDef-agent spawn F30/#403, gated dynamic-stdio MCP F31/#405, both v0.23.3); secret redaction at rest (F32, v0.23.4); and dynamic-MCP tools advertised at run start so single-tool notifier agents fire (F33, v0.23.5). Each post-v0.23.0 feature is still flagged with its v0.23.0-binary fallback, so the docs hold whether the operator runs v0.23.0 or v0.23.5. |
≥ 2.1 |
| 0.23.2 | Same runtime requirement as 0.21.0. Docs-only — loomcycle-configure reconciled against loomcycle main (then-unreleased post-v0.23.0): inbound webhooks, third-party MCP, the two-layer tool gate, and the .env.local/.env.insecure split. (Superseded by 0.23.5, which pins those fixes to the v0.23.3 tag.) |
≥ 2.1 |
| 0.21.0 | Requires a loomcycle build with loomcycle mcp --upstream (RFC R thin client). The plugin no longer boots a runtime — a loomcycle instance must be running at base_url. On a build without --upstream, the flag is unknown and the server errors at launch — pin 0.20.x or upgrade loomcycle. |
≥ 2.1 |
| 0.20.2 | as 0.20.1, plus the loomcycle mcp --no-http flag (verified on v0.22.0). On an older build that doesn't recognise --no-http, the server errors at launch — pin 0.20.1 or upgrade loomcycle. |
≥ 2.1 |
| 0.20.1 | ≥ v0.12.x (loomcycle mcp + meta-tools); memory add/recall need ≥ v0.16; operator-token needs ≥ v0.17 |
≥ 2.1 |
The plugin's version tracks loomcycle's version vector through the v1.x batch.
All tool contracts are re-verified against loomcycle's internal/api/mcp/tools.go
each release — the v0.12.x → v0.16.x meta-tool additions are back-compatible,
so the existing commands work unchanged against any loomcycle ≥ v0.12.x.
The plugin consumes loomcycle's MCP tools verbatim. If a capability is missing, it's a feature request on the loomcycle repo — the plugin ships no workarounds.
- MCP server won't connect — the plugin is a thin client, so check two
things: (1) the
loomcyclebinary is on PATH (orbin_pathis correct), and (2) a loomcycle runtime is actually running and reachable atbase_url— the plugin boots no runtime of its own, it proxies to one. If the upstream requires a bearer, confirmauth_tokenis set (sent asLOOMCYCLE_MCP_UPSTREAM_TOKEN). The upstream runtime owns its ownloomcycle.yaml+ env — the thin client loads none. - Wrong binary / "command not found" after moving machines or switching OS
(e.g. a Linux
bin_pathcarried onto macOS) — the committed.mcp.jsonis a template (${user_config.bin_path}/${user_config.base_url}); Claude Code resolves those to concrete values at install time and stores them in its own settings, not in this repo, so a path baked on one machine is stale on another. Fix it where it lives:/plugin→ manage loomcycle → Configure → setbin_pathtoloomcycle(PATH lookup — most portable) or the platform path (/opt/homebrew/bin/loomcycleon Apple Silicon,/usr/local/bin/loomcycleon Intel), confirmbase_url, then restart Claude Code. Don't hand-edit the resolved.mcp.jsonin your live config — that's the plugin's running MCP startup config and editing it is correctly blocked; reconfigure via/plugininstead (reinstalling re-prompts for these fields too). - An
envchange insettings.json/settings.local.jsondidn't take effect — the respawnedloomcycle mcpserver inherits the environment Claude Code itself started with, so a/reload-pluginsdoes not re-readsettings-supplied env (e.g.LOOMCYCLE_LISTEN_ADDR,LOOMCYCLE_MCP_ALLOW_PRIVILEGED_TOOLS). Only a full Claude Code restart picks up a changedsettingsenv. (Exporting the var in the shell that launches Claude Code, then restarting, is the reliable path.) list_runserrors — it requiresuser_id. Set one via/loomcycle:connect --user=<id>or pass--user=on the command.- Bearer / auth — the API bearer lives in your OS keychain via the
auth_tokenuserConfig, never in the repo. The per-run bearer set by/loomcycle:connectis a separate, session-scoped value. - Multi-replica loomcycle — the plugin talks to whatever instance its
loomcycle mcpserver is configured against; cluster-mode is transparent.
- Start, stop, or health-check loomcycle (lifecycle is the operator's job).
- Bundle the loomcycle binary.
- Push agent definitions into loomcycle (that's
loomcycle import claude-code— RFC C2 — the data-movement direction; this plugin moves runtime control).
Apache-2.0. See LICENSE.
