@loompad/cli 0.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +96 -11
- package/CHANGELOG.md +1042 -0
- package/README.md +1473 -44
- package/THIRD_PARTY_NOTICES.md +275 -0
- package/dist/adapters/antigravity-cli.d.ts +62 -0
- package/dist/adapters/antigravity-cli.js +258 -0
- package/dist/adapters/antigravity-cli.js.map +1 -0
- package/dist/adapters/base.d.ts +61 -9
- package/dist/adapters/base.js +266 -15
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/bridges/antigravity.d.ts +9 -12
- package/dist/adapters/bridges/antigravity.js +9 -12
- package/dist/adapters/bridges/antigravity.js.map +1 -1
- package/dist/adapters/echo.d.ts +1 -0
- package/dist/adapters/echo.js +59 -3
- package/dist/adapters/echo.js.map +1 -1
- package/dist/adapters/grok.d.ts +7 -0
- package/dist/adapters/grok.js +20 -10
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +53 -7
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/model.d.ts +137 -0
- package/dist/adapters/model.js +527 -0
- package/dist/adapters/model.js.map +1 -0
- package/dist/adapters/opencode.d.ts +101 -1
- package/dist/adapters/opencode.js +874 -98
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/cli/doctor.d.ts +32 -0
- package/dist/cli/doctor.js +89 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +2651 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tui-model.js +3 -1
- package/dist/cli/tui-model.js.map +1 -1
- package/dist/cli/tui.js +44 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/cli/ui.d.ts +2 -0
- package/dist/cli/ui.js +13 -0
- package/dist/cli/ui.js.map +1 -1
- package/dist/core/ades.d.ts +32 -16
- package/dist/core/ades.js +57 -20
- package/dist/core/ades.js.map +1 -1
- package/dist/core/agent-state.d.ts +14 -0
- package/dist/core/agent-state.js +32 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/approvals.d.ts +33 -0
- package/dist/core/approvals.js +29 -0
- package/dist/core/approvals.js.map +1 -0
- package/dist/core/backup.d.ts +19 -0
- package/dist/core/backup.js +86 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/baton.d.ts +2 -2
- package/dist/core/baton.js.map +1 -1
- package/dist/core/brain-extract.d.ts +17 -0
- package/dist/core/brain-extract.js +28 -2
- package/dist/core/brain-extract.js.map +1 -1
- package/dist/core/brain-index.d.ts +71 -0
- package/dist/core/brain-index.js +204 -5
- package/dist/core/brain-index.js.map +1 -1
- package/dist/core/brain.d.ts +58 -2
- package/dist/core/brain.js +79 -1
- package/dist/core/brain.js.map +1 -1
- package/dist/core/chat-import.d.ts +71 -0
- package/dist/core/chat-import.js +419 -0
- package/dist/core/chat-import.js.map +1 -0
- package/dist/core/checkpoint.d.ts +107 -0
- package/dist/core/checkpoint.js +259 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/claude-cli.js +1 -1
- package/dist/core/claude-cli.js.map +1 -1
- package/dist/core/continuity/artifacts.d.ts +14 -0
- package/dist/core/continuity/artifacts.js +118 -0
- package/dist/core/continuity/artifacts.js.map +1 -0
- package/dist/core/continuity/capabilities.d.ts +65 -0
- package/dist/core/continuity/capabilities.js +129 -0
- package/dist/core/continuity/capabilities.js.map +1 -0
- package/dist/core/continuity/contracts.d.ts +352 -0
- package/dist/core/continuity/contracts.js +117 -0
- package/dist/core/continuity/contracts.js.map +1 -0
- package/dist/core/continuity/engine.d.ts +55 -0
- package/dist/core/continuity/engine.js +504 -0
- package/dist/core/continuity/engine.js.map +1 -0
- package/dist/core/continuity/store.d.ts +73 -0
- package/dist/core/continuity/store.js +438 -0
- package/dist/core/continuity/store.js.map +1 -0
- package/dist/core/conversations.d.ts +19 -0
- package/dist/core/conversations.js +87 -0
- package/dist/core/conversations.js.map +1 -0
- package/dist/core/crew-coordination.d.ts +6 -0
- package/dist/core/crew-coordination.js +24 -0
- package/dist/core/crew-coordination.js.map +1 -0
- package/dist/core/crew-policy.d.ts +18 -0
- package/dist/core/crew-policy.js +40 -0
- package/dist/core/crew-policy.js.map +1 -0
- package/dist/core/crew-protocol.d.ts +64 -0
- package/dist/core/crew-protocol.js +146 -0
- package/dist/core/crew-protocol.js.map +1 -0
- package/dist/core/crew-requests.d.ts +14 -0
- package/dist/core/crew-requests.js +22 -0
- package/dist/core/crew-requests.js.map +1 -0
- package/dist/core/crew.d.ts +395 -0
- package/dist/core/crew.js +1857 -0
- package/dist/core/crew.js.map +1 -0
- package/dist/core/digest.d.ts +40 -0
- package/dist/core/digest.js +94 -0
- package/dist/core/digest.js.map +1 -0
- package/dist/core/distill.js +3 -5
- package/dist/core/distill.js.map +1 -1
- package/dist/core/eventlog.d.ts +27 -25
- package/dist/core/eventlog.js +97 -150
- package/dist/core/eventlog.js.map +1 -1
- package/dist/core/events/contracts.d.ts +51 -0
- package/dist/core/events/contracts.js +2 -0
- package/dist/core/events/contracts.js.map +1 -0
- package/dist/core/events/jsonl-store.d.ts +23 -0
- package/dist/core/events/jsonl-store.js +90 -0
- package/dist/core/events/jsonl-store.js.map +1 -0
- package/dist/core/events/sqlite-store.d.ts +37 -0
- package/dist/core/events/sqlite-store.js +215 -0
- package/dist/core/events/sqlite-store.js.map +1 -0
- package/dist/core/forecast.d.ts +44 -0
- package/dist/core/forecast.js +46 -0
- package/dist/core/forecast.js.map +1 -0
- package/dist/core/free-pool.d.ts +65 -0
- package/dist/core/free-pool.js +171 -0
- package/dist/core/free-pool.js.map +1 -0
- package/dist/core/git.d.ts +30 -0
- package/dist/core/git.js +50 -2
- package/dist/core/git.js.map +1 -1
- package/dist/core/github-events.d.ts +53 -0
- package/dist/core/github-events.js +227 -0
- package/dist/core/github-events.js.map +1 -0
- package/dist/core/goal-lanes.d.ts +53 -0
- package/dist/core/goal-lanes.js +99 -0
- package/dist/core/goal-lanes.js.map +1 -0
- package/dist/core/hosted.d.ts +27 -0
- package/dist/core/hosted.js +50 -0
- package/dist/core/hosted.js.map +1 -0
- package/dist/core/http-server.d.ts +5 -0
- package/dist/core/http-server.js +8 -0
- package/dist/core/http-server.js.map +1 -0
- package/dist/core/invite-link.d.ts +31 -0
- package/dist/core/invite-link.js +45 -0
- package/dist/core/invite-link.js.map +1 -0
- package/dist/core/loops.d.ts +37 -0
- package/dist/core/loops.js +81 -0
- package/dist/core/loops.js.map +1 -0
- package/dist/core/mcp-catalog.d.ts +182 -0
- package/dist/core/mcp-catalog.js +431 -0
- package/dist/core/mcp-catalog.js.map +1 -0
- package/dist/core/mcp.d.ts +87 -0
- package/dist/core/mcp.js +181 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/memory.d.ts +1 -0
- package/dist/core/memory.js +5 -2
- package/dist/core/memory.js.map +1 -1
- package/dist/core/model-tools.d.ts +97 -0
- package/dist/core/model-tools.js +388 -0
- package/dist/core/model-tools.js.map +1 -0
- package/dist/core/notify.js +19 -1
- package/dist/core/notify.js.map +1 -1
- package/dist/core/orchestra.d.ts +635 -0
- package/dist/core/orchestra.js +2120 -0
- package/dist/core/orchestra.js.map +1 -0
- package/dist/core/permissions.d.ts +56 -0
- package/dist/core/permissions.js +122 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/preview-proxy.d.ts +70 -0
- package/dist/core/preview-proxy.js +563 -0
- package/dist/core/preview-proxy.js.map +1 -0
- package/dist/core/preview-shot.d.ts +46 -0
- package/dist/core/preview-shot.js +109 -0
- package/dist/core/preview-shot.js.map +1 -0
- package/dist/core/process.d.ts +36 -0
- package/dist/core/process.js +168 -0
- package/dist/core/process.js.map +1 -0
- package/dist/core/prompt-queue.d.ts +136 -0
- package/dist/core/prompt-queue.js +269 -0
- package/dist/core/prompt-queue.js.map +1 -0
- package/dist/core/prompts.d.ts +53 -0
- package/dist/core/prompts.js +127 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/proof.d.ts +82 -0
- package/dist/core/proof.js +216 -0
- package/dist/core/proof.js.map +1 -0
- package/dist/core/provider-text.d.ts +23 -0
- package/dist/core/provider-text.js +66 -0
- package/dist/core/provider-text.js.map +1 -0
- package/dist/core/providers.d.ts +152 -0
- package/dist/core/providers.js +343 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quota.d.ts +33 -0
- package/dist/core/quota.js +74 -0
- package/dist/core/quota.js.map +1 -0
- package/dist/core/recipes.d.ts +50 -0
- package/dist/core/recipes.js +99 -0
- package/dist/core/recipes.js.map +1 -0
- package/dist/core/registry.d.ts +54 -0
- package/dist/core/registry.js +62 -5
- package/dist/core/registry.js.map +1 -1
- package/dist/core/relay-client.d.ts +67 -0
- package/dist/core/relay-client.js +184 -0
- package/dist/core/relay-client.js.map +1 -0
- package/dist/core/relay-protocol.d.ts +140 -0
- package/dist/core/relay-protocol.js +189 -0
- package/dist/core/relay-protocol.js.map +1 -0
- package/dist/core/replay.d.ts +18 -0
- package/dist/core/replay.js +191 -0
- package/dist/core/replay.js.map +1 -0
- package/dist/core/review-triage.d.ts +41 -0
- package/dist/core/review-triage.js +124 -0
- package/dist/core/review-triage.js.map +1 -0
- package/dist/core/router.js +2 -2
- package/dist/core/router.js.map +1 -1
- package/dist/core/routes.d.ts +39 -3
- package/dist/core/routes.js +137 -4
- package/dist/core/routes.js.map +1 -1
- package/dist/core/run-command.d.ts +14 -0
- package/dist/core/run-command.js +45 -0
- package/dist/core/run-command.js.map +1 -0
- package/dist/core/runner-setup.d.ts +36 -0
- package/dist/core/runner-setup.js +114 -0
- package/dist/core/runner-setup.js.map +1 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -1
- package/dist/core/semantic.d.ts +92 -0
- package/dist/core/semantic.js +227 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/servers.d.ts +115 -0
- package/dist/core/servers.js +305 -0
- package/dist/core/servers.js.map +1 -0
- package/dist/core/setup.js +64 -6
- package/dist/core/setup.js.map +1 -1
- package/dist/core/shield.d.ts +17 -0
- package/dist/core/shield.js +81 -0
- package/dist/core/shield.js.map +1 -0
- package/dist/core/shift.d.ts +69 -0
- package/dist/core/shift.js +120 -0
- package/dist/core/shift.js.map +1 -0
- package/dist/core/skill-install.d.ts +125 -0
- package/dist/core/skill-install.js +328 -0
- package/dist/core/skill-install.js.map +1 -0
- package/dist/core/skills.d.ts +116 -0
- package/dist/core/skills.js +223 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/step-conditions.d.ts +65 -0
- package/dist/core/step-conditions.js +102 -0
- package/dist/core/step-conditions.js.map +1 -0
- package/dist/core/subagents.d.ts +45 -0
- package/dist/core/subagents.js +90 -0
- package/dist/core/subagents.js.map +1 -0
- package/dist/core/team-canon.d.ts +42 -0
- package/dist/core/team-canon.js +121 -0
- package/dist/core/team-canon.js.map +1 -0
- package/dist/core/team-crypto.d.ts +97 -0
- package/dist/core/team-crypto.js +175 -0
- package/dist/core/team-crypto.js.map +1 -0
- package/dist/core/team-hub.d.ts +514 -0
- package/dist/core/team-hub.js +964 -0
- package/dist/core/team-hub.js.map +1 -0
- package/dist/core/team-landing.d.ts +239 -0
- package/dist/core/team-landing.js +504 -0
- package/dist/core/team-landing.js.map +1 -0
- package/dist/core/team-leases.d.ts +44 -0
- package/dist/core/team-leases.js +145 -0
- package/dist/core/team-leases.js.map +1 -0
- package/dist/core/team-memory.d.ts +42 -0
- package/dist/core/team-memory.js +84 -0
- package/dist/core/team-memory.js.map +1 -0
- package/dist/core/team-policy.d.ts +85 -0
- package/dist/core/team-policy.js +191 -0
- package/dist/core/team-policy.js.map +1 -0
- package/dist/core/thread-md.d.ts +6 -0
- package/dist/core/thread-md.js +42 -0
- package/dist/core/thread-md.js.map +1 -0
- package/dist/core/turn-stats.d.ts +61 -0
- package/dist/core/turn-stats.js +124 -0
- package/dist/core/turn-stats.js.map +1 -0
- package/dist/core/updater.d.ts +73 -0
- package/dist/core/updater.js +153 -0
- package/dist/core/updater.js.map +1 -0
- package/dist/core/usage.d.ts +75 -0
- package/dist/core/usage.js +163 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/windows-jobs.d.ts +15 -0
- package/dist/core/windows-jobs.js +170 -0
- package/dist/core/windows-jobs.js.map +1 -0
- package/dist/core/worktree-merge.d.ts +57 -0
- package/dist/core/worktree-merge.js +123 -0
- package/dist/core/worktree-merge.js.map +1 -0
- package/dist/core/worktree.d.ts +5 -0
- package/dist/core/worktree.js +45 -5
- package/dist/core/worktree.js.map +1 -1
- package/dist/core/worktrees.d.ts +6 -0
- package/dist/core/worktrees.js +21 -0
- package/dist/core/worktrees.js.map +1 -0
- package/dist/daemon/app-page.d.ts +0 -17
- package/dist/daemon/app-page.js +10 -6346
- package/dist/daemon/app-page.js.map +1 -1
- package/dist/daemon/auth.d.ts +24 -3
- package/dist/daemon/auth.js +66 -7
- package/dist/daemon/auth.js.map +1 -1
- package/dist/daemon/board.d.ts +7 -0
- package/dist/daemon/board.js +12 -1
- package/dist/daemon/board.js.map +1 -1
- package/dist/daemon/brand-icons.d.ts +6 -1
- package/dist/daemon/brand-icons.js +11 -3
- package/dist/daemon/brand-icons.js.map +1 -1
- package/dist/daemon/client.d.ts +665 -1
- package/dist/daemon/client.js +381 -3
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/delivery.d.ts +34 -0
- package/dist/daemon/delivery.js +63 -0
- package/dist/daemon/delivery.js.map +1 -0
- package/dist/daemon/deploys.d.ts +62 -0
- package/dist/daemon/deploys.js +159 -0
- package/dist/daemon/deploys.js.map +1 -0
- package/dist/daemon/landing.d.ts +192 -0
- package/dist/daemon/landing.js +992 -0
- package/dist/daemon/landing.js.map +1 -0
- package/dist/daemon/onboard.d.ts +150 -0
- package/dist/daemon/onboard.js +390 -0
- package/dist/daemon/onboard.js.map +1 -0
- package/dist/daemon/proof.d.ts +70 -0
- package/dist/daemon/proof.js +172 -0
- package/dist/daemon/proof.js.map +1 -0
- package/dist/daemon/push.d.ts +19 -1
- package/dist/daemon/push.js +90 -3
- package/dist/daemon/push.js.map +1 -1
- package/dist/daemon/relay.d.ts +82 -0
- package/dist/daemon/relay.js +351 -0
- package/dist/daemon/relay.js.map +1 -0
- package/dist/daemon/routes/agent-discovery.d.ts +4 -0
- package/dist/daemon/routes/agent-discovery.js +29 -0
- package/dist/daemon/routes/agent-discovery.js.map +1 -0
- package/dist/daemon/routes/agent-settings.d.ts +4 -0
- package/dist/daemon/routes/agent-settings.js +94 -0
- package/dist/daemon/routes/agent-settings.js.map +1 -0
- package/dist/daemon/routes/agents.d.ts +4 -0
- package/dist/daemon/routes/agents.js +105 -0
- package/dist/daemon/routes/agents.js.map +1 -0
- package/dist/daemon/routes/alerts.d.ts +4 -0
- package/dist/daemon/routes/alerts.js +137 -0
- package/dist/daemon/routes/alerts.js.map +1 -0
- package/dist/daemon/routes/approvals.d.ts +4 -0
- package/dist/daemon/routes/approvals.js +40 -0
- package/dist/daemon/routes/approvals.js.map +1 -0
- package/dist/daemon/routes/assets.d.ts +3 -0
- package/dist/daemon/routes/assets.js +64 -0
- package/dist/daemon/routes/assets.js.map +1 -0
- package/dist/daemon/routes/board.d.ts +4 -0
- package/dist/daemon/routes/board.js +147 -0
- package/dist/daemon/routes/board.js.map +1 -0
- package/dist/daemon/routes/brain.d.ts +4 -0
- package/dist/daemon/routes/brain.js +270 -0
- package/dist/daemon/routes/brain.js.map +1 -0
- package/dist/daemon/routes/chats.d.ts +4 -0
- package/dist/daemon/routes/chats.js +249 -0
- package/dist/daemon/routes/chats.js.map +1 -0
- package/dist/daemon/routes/clients.d.ts +4 -0
- package/dist/daemon/routes/clients.js +49 -0
- package/dist/daemon/routes/clients.js.map +1 -0
- package/dist/daemon/routes/cloud.d.ts +4 -0
- package/dist/daemon/routes/cloud.js +46 -0
- package/dist/daemon/routes/cloud.js.map +1 -0
- package/dist/daemon/routes/config.d.ts +4 -0
- package/dist/daemon/routes/config.js +35 -0
- package/dist/daemon/routes/config.js.map +1 -0
- package/dist/daemon/routes/context.d.ts +51 -0
- package/dist/daemon/routes/context.js +2 -0
- package/dist/daemon/routes/context.js.map +1 -0
- package/dist/daemon/routes/crews.d.ts +4 -0
- package/dist/daemon/routes/crews.js +149 -0
- package/dist/daemon/routes/crews.js.map +1 -0
- package/dist/daemon/routes/files.d.ts +3 -0
- package/dist/daemon/routes/files.js +253 -0
- package/dist/daemon/routes/files.js.map +1 -0
- package/dist/daemon/routes/git.d.ts +4 -0
- package/dist/daemon/routes/git.js +96 -0
- package/dist/daemon/routes/git.js.map +1 -0
- package/dist/daemon/routes/history.d.ts +4 -0
- package/dist/daemon/routes/history.js +101 -0
- package/dist/daemon/routes/history.js.map +1 -0
- package/dist/daemon/routes/integrations-project.d.ts +3 -0
- package/dist/daemon/routes/integrations-project.js +103 -0
- package/dist/daemon/routes/integrations-project.js.map +1 -0
- package/dist/daemon/routes/integrations.d.ts +3 -0
- package/dist/daemon/routes/integrations.js +111 -0
- package/dist/daemon/routes/integrations.js.map +1 -0
- package/dist/daemon/routes/logs.d.ts +3 -0
- package/dist/daemon/routes/logs.js +40 -0
- package/dist/daemon/routes/logs.js.map +1 -0
- package/dist/daemon/routes/messages.d.ts +4 -0
- package/dist/daemon/routes/messages.js +60 -0
- package/dist/daemon/routes/messages.js.map +1 -0
- package/dist/daemon/routes/observability.d.ts +4 -0
- package/dist/daemon/routes/observability.js +247 -0
- package/dist/daemon/routes/observability.js.map +1 -0
- package/dist/daemon/routes/onboard.d.ts +12 -0
- package/dist/daemon/routes/onboard.js +67 -0
- package/dist/daemon/routes/onboard.js.map +1 -0
- package/dist/daemon/routes/orchestra.d.ts +4 -0
- package/dist/daemon/routes/orchestra.js +110 -0
- package/dist/daemon/routes/orchestra.js.map +1 -0
- package/dist/daemon/routes/pairing.d.ts +4 -0
- package/dist/daemon/routes/pairing.js +155 -0
- package/dist/daemon/routes/pairing.js.map +1 -0
- package/dist/daemon/routes/preview.d.ts +24 -0
- package/dist/daemon/routes/preview.js +122 -0
- package/dist/daemon/routes/preview.js.map +1 -0
- package/dist/daemon/routes/project-actions.d.ts +4 -0
- package/dist/daemon/routes/project-actions.js +53 -0
- package/dist/daemon/routes/project-actions.js.map +1 -0
- package/dist/daemon/routes/project-team.d.ts +4 -0
- package/dist/daemon/routes/project-team.js +201 -0
- package/dist/daemon/routes/project-team.js.map +1 -0
- package/dist/daemon/routes/projects.d.ts +14 -0
- package/dist/daemon/routes/projects.js +138 -0
- package/dist/daemon/routes/projects.js.map +1 -0
- package/dist/daemon/routes/prompts.d.ts +4 -0
- package/dist/daemon/routes/prompts.js +44 -0
- package/dist/daemon/routes/prompts.js.map +1 -0
- package/dist/daemon/routes/proof.d.ts +4 -0
- package/dist/daemon/routes/proof.js +22 -0
- package/dist/daemon/routes/proof.js.map +1 -0
- package/dist/daemon/routes/providers.d.ts +3 -0
- package/dist/daemon/routes/providers.js +60 -0
- package/dist/daemon/routes/providers.js.map +1 -0
- package/dist/daemon/routes/public.d.ts +4 -0
- package/dist/daemon/routes/public.js +64 -0
- package/dist/daemon/routes/public.js.map +1 -0
- package/dist/daemon/routes/queue.d.ts +4 -0
- package/dist/daemon/routes/queue.js +140 -0
- package/dist/daemon/routes/queue.js.map +1 -0
- package/dist/daemon/routes/routing.d.ts +4 -0
- package/dist/daemon/routes/routing.js +61 -0
- package/dist/daemon/routes/routing.js.map +1 -0
- package/dist/daemon/routes/runtime-handler.d.ts +2 -0
- package/dist/daemon/routes/runtime-handler.js +63 -0
- package/dist/daemon/routes/runtime-handler.js.map +1 -0
- package/dist/daemon/routes/search.d.ts +4 -0
- package/dist/daemon/routes/search.js +17 -0
- package/dist/daemon/routes/search.js.map +1 -0
- package/dist/daemon/routes/servers.d.ts +4 -0
- package/dist/daemon/routes/servers.js +133 -0
- package/dist/daemon/routes/servers.js.map +1 -0
- package/dist/daemon/routes/setup.d.ts +3 -0
- package/dist/daemon/routes/setup.js +50 -0
- package/dist/daemon/routes/setup.js.map +1 -0
- package/dist/daemon/routes/shift.d.ts +4 -0
- package/dist/daemon/routes/shift.js +30 -0
- package/dist/daemon/routes/shift.js.map +1 -0
- package/dist/daemon/routes/specs.d.ts +4 -0
- package/dist/daemon/routes/specs.js +57 -0
- package/dist/daemon/routes/specs.js.map +1 -0
- package/dist/daemon/routes/task-delivery.d.ts +4 -0
- package/dist/daemon/routes/task-delivery.js +38 -0
- package/dist/daemon/routes/task-delivery.js.map +1 -0
- package/dist/daemon/routes/team.d.ts +4 -0
- package/dist/daemon/routes/team.js +193 -0
- package/dist/daemon/routes/team.js.map +1 -0
- package/dist/daemon/routes/terminals.d.ts +4 -0
- package/dist/daemon/routes/terminals.js +87 -0
- package/dist/daemon/routes/terminals.js.map +1 -0
- package/dist/daemon/routes/tools.d.ts +4 -0
- package/dist/daemon/routes/tools.js +180 -0
- package/dist/daemon/routes/tools.js.map +1 -0
- package/dist/daemon/routes/updates.d.ts +4 -0
- package/dist/daemon/routes/updates.js +89 -0
- package/dist/daemon/routes/updates.js.map +1 -0
- package/dist/daemon/routes/voice.d.ts +4 -0
- package/dist/daemon/routes/voice.js +53 -0
- package/dist/daemon/routes/voice.js.map +1 -0
- package/dist/daemon/runner.d.ts +186 -0
- package/dist/daemon/runner.js +612 -0
- package/dist/daemon/runner.js.map +1 -0
- package/dist/daemon/runtime/accounting.d.ts +130 -0
- package/dist/daemon/runtime/accounting.js +303 -0
- package/dist/daemon/runtime/accounting.js.map +1 -0
- package/dist/daemon/runtime/agents.d.ts +16 -0
- package/dist/daemon/runtime/agents.js +71 -0
- package/dist/daemon/runtime/agents.js.map +1 -0
- package/dist/daemon/runtime/briefings.d.ts +134 -0
- package/dist/daemon/runtime/briefings.js +329 -0
- package/dist/daemon/runtime/briefings.js.map +1 -0
- package/dist/daemon/runtime/native-usage.d.ts +21 -0
- package/dist/daemon/runtime/native-usage.js +71 -0
- package/dist/daemon/runtime/native-usage.js.map +1 -0
- package/dist/daemon/runtime/queue.d.ts +94 -0
- package/dist/daemon/runtime/queue.js +222 -0
- package/dist/daemon/runtime/queue.js.map +1 -0
- package/dist/daemon/runtime/relay.d.ts +93 -0
- package/dist/daemon/runtime/relay.js +240 -0
- package/dist/daemon/runtime/relay.js.map +1 -0
- package/dist/daemon/runtime/turns.d.ts +202 -0
- package/dist/daemon/runtime/turns.js +663 -0
- package/dist/daemon/runtime/turns.js.map +1 -0
- package/dist/daemon/runtime-support.d.ts +76 -0
- package/dist/daemon/runtime-support.js +134 -0
- package/dist/daemon/runtime-support.js.map +1 -0
- package/dist/daemon/runtime.d.ts +739 -70
- package/dist/daemon/runtime.js +2751 -470
- package/dist/daemon/runtime.js.map +1 -1
- package/dist/daemon/server.d.ts +82 -55
- package/dist/daemon/server.js +674 -1435
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shift.d.ts +70 -0
- package/dist/daemon/shift.js +170 -0
- package/dist/daemon/shift.js.map +1 -0
- package/dist/daemon/specs.d.ts +92 -0
- package/dist/daemon/specs.js +285 -0
- package/dist/daemon/specs.js.map +1 -0
- package/dist/daemon/system.d.ts +208 -0
- package/dist/daemon/system.js +557 -0
- package/dist/daemon/system.js.map +1 -0
- package/dist/daemon/tasks.js +1 -1
- package/dist/daemon/team-brain.d.ts +136 -0
- package/dist/daemon/team-brain.js +659 -0
- package/dist/daemon/team-brain.js.map +1 -0
- package/dist/daemon/team-coordinator.d.ts +78 -0
- package/dist/daemon/team-coordinator.js +408 -0
- package/dist/daemon/team-coordinator.js.map +1 -0
- package/dist/daemon/team.d.ts +339 -0
- package/dist/daemon/team.js +1433 -0
- package/dist/daemon/team.js.map +1 -0
- package/dist/daemon/terminals.d.ts +22 -3
- package/dist/daemon/terminals.js +236 -16
- package/dist/daemon/terminals.js.map +1 -1
- package/dist/hub/client.d.ts +120 -0
- package/dist/hub/client.js +228 -0
- package/dist/hub/client.js.map +1 -0
- package/dist/hub/server.d.ts +32 -0
- package/dist/hub/server.js +180 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/supabase-client.d.ts +220 -0
- package/dist/hub/supabase-client.js +843 -0
- package/dist/hub/supabase-client.js.map +1 -0
- package/dist/observability/ask.d.ts +91 -0
- package/dist/observability/ask.js +179 -0
- package/dist/observability/ask.js.map +1 -0
- package/dist/observability/clickhouse.d.ts +33 -0
- package/dist/observability/clickhouse.js +48 -0
- package/dist/observability/clickhouse.js.map +1 -0
- package/dist/observability/decisions.d.ts +176 -0
- package/dist/observability/decisions.js +377 -0
- package/dist/observability/decisions.js.map +1 -0
- package/dist/observability/index.d.ts +89 -0
- package/dist/observability/index.js +348 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/insights.d.ts +151 -0
- package/dist/observability/insights.js +319 -0
- package/dist/observability/insights.js.map +1 -0
- package/dist/observability/logs-query.d.ts +82 -0
- package/dist/observability/logs-query.js +102 -0
- package/dist/observability/logs-query.js.map +1 -0
- package/dist/observability/logs.d.ts +72 -0
- package/dist/observability/logs.js +295 -0
- package/dist/observability/logs.js.map +1 -0
- package/dist/observability/metrics.d.ts +86 -0
- package/dist/observability/metrics.js +303 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otlp.d.ts +127 -0
- package/dist/observability/otlp.js +242 -0
- package/dist/observability/otlp.js.map +1 -0
- package/dist/observability/snapshots.d.ts +43 -0
- package/dist/observability/snapshots.js +129 -0
- package/dist/observability/snapshots.js.map +1 -0
- package/dist/observability/triage.d.ts +49 -0
- package/dist/observability/triage.js +284 -0
- package/dist/observability/triage.js.map +1 -0
- package/dist/providers/adapter.d.ts +50 -0
- package/dist/providers/adapter.js +38 -0
- package/dist/providers/adapter.js.map +1 -0
- package/dist/providers/agent.d.ts +110 -0
- package/dist/providers/agent.js +487 -0
- package/dist/providers/agent.js.map +1 -0
- package/dist/providers/approvals.d.ts +24 -0
- package/dist/providers/approvals.js +78 -0
- package/dist/providers/approvals.js.map +1 -0
- package/dist/providers/attachments.d.ts +20 -0
- package/dist/providers/attachments.js +51 -0
- package/dist/providers/attachments.js.map +1 -0
- package/dist/providers/claude/adapter.d.ts +99 -0
- package/dist/providers/claude/adapter.js +790 -0
- package/dist/providers/claude/adapter.js.map +1 -0
- package/dist/providers/codex/adapter.d.ts +113 -0
- package/dist/providers/codex/adapter.js +881 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/instructions.d.ts +11 -0
- package/dist/providers/codex/instructions.js +154 -0
- package/dist/providers/codex/instructions.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +36 -0
- package/dist/providers/codex/rpc.js +104 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers/contracts.d.ts +339 -0
- package/dist/providers/contracts.js +19 -0
- package/dist/providers/contracts.js.map +1 -0
- package/dist/providers/directory.d.ts +70 -0
- package/dist/providers/directory.js +95 -0
- package/dist/providers/directory.js.map +1 -0
- package/dist/providers/errors.d.ts +47 -0
- package/dist/providers/errors.js +24 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +18 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ingestion.d.ts +80 -0
- package/dist/providers/ingestion.js +330 -0
- package/dist/providers/ingestion.js.map +1 -0
- package/dist/providers/live.d.ts +22 -0
- package/dist/providers/live.js +56 -0
- package/dist/providers/live.js.map +1 -0
- package/dist/providers/process.d.ts +29 -0
- package/dist/providers/process.js +71 -0
- package/dist/providers/process.js.map +1 -0
- package/dist/providers/reaper.d.ts +33 -0
- package/dist/providers/reaper.js +65 -0
- package/dist/providers/reaper.js.map +1 -0
- package/dist/providers/service.d.ts +87 -0
- package/dist/providers/service.js +256 -0
- package/dist/providers/service.js.map +1 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +385 -6
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +7 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/dist/web/app.css +4643 -0
- package/dist/web/app.js +22640 -0
- package/dist/web/app.js.map +7 -0
- package/dist/web/shell.html +39 -0
- package/docs/README.md +21 -0
- package/docs/SETUP.md +10 -0
- package/docs/adapters.md +21 -8
- package/docs/brain-continuity.md +186 -0
- package/docs/cloud.md +108 -0
- package/docs/crews.md +62 -0
- package/docs/design-system.md +31 -2
- package/docs/integration-notes.md +128 -20
- package/docs/loompad.md +105 -0
- package/docs/observability.md +220 -0
- package/docs/play-store.md +123 -0
- package/docs/proposals/agent-teams.md +249 -0
- package/docs/proposals/brain-continuity-implementation.md +468 -0
- package/docs/proposals/brain-session-continuity.md +623 -0
- package/docs/proposals/t3code-port.md +81 -0
- package/docs/proposals/t3code-reference.md +95 -0
- package/docs/refactoring/AGENT-TEAMS-PROGRESS.md +88 -0
- package/docs/refactoring/BOUNDARIES.md +120 -0
- package/docs/refactoring/BRAIN-AUDIT.md +176 -0
- package/docs/refactoring/BRAIN-NOTES.md +390 -0
- package/docs/refactoring/BRAIN-TODO.md +214 -0
- package/docs/refactoring/NOTES.md +129 -0
- package/docs/refactoring/T3-PORT-NOTES.md +165 -0
- package/docs/refactoring/T3-PORT-TODO.md +123 -0
- package/docs/refactoring/TODO.md +46 -0
- package/docs/refactoring/brain-benchmark-2026-09-28.json +48 -0
- package/docs/refactoring/brain-benchmark-audit-2026-09-28.json +48 -0
- package/docs/release-notes/v0.2.0.md +35 -0
- package/docs/release-notes/v0.2.1.md +59 -0
- package/docs/release-notes/v0.2.3.md +77 -0
- package/docs/release-notes/v0.2.4.md +128 -0
- package/docs/release-notes/v0.2.5.md +153 -0
- package/docs/release-notes/v0.2.6.md +62 -0
- package/docs/release-notes/v1.0.0.md +120 -0
- package/docs/release-notes/v1.0.1.md +77 -0
- package/docs/release-notes/v1.1.0.md +71 -0
- package/docs/release-notes/v1.1.1.md +38 -0
- package/docs/teams-architecture.md +697 -0
- package/docs/teams.md +171 -0
- package/docs/windows.md +85 -0
- package/package.json +22 -9
- package/dist/adapters/claude-code.d.ts +0 -26
- package/dist/adapters/claude-code.js +0 -209
- package/dist/adapters/claude-code.js.map +0 -1
- package/dist/adapters/codex.d.ts +0 -51
- package/dist/adapters/codex.js +0 -291
- package/dist/adapters/codex.js.map +0 -1
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Loom
|
|
2
2
|
|
|
3
3
|
[](https://github.com/nickthelegend/loom/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/@loompad/cli)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
7
|
**The shared-memory layer for AI dev environments.** Every coding agent — Claude Code,
|
|
@@ -9,13 +9,25 @@ OpenCode, Antigravity, Codex, … — keeps its own brain in its own files. Loom
|
|
|
9
9
|
**one brain**: connect your ADEs, and their memory, decisions, and context become a
|
|
10
10
|
single shared thread that flows from one agent to the next.
|
|
11
11
|
|
|
12
|
-
Today that means **Claude Code, Codex, OpenCode
|
|
13
|
-
verified against a real version, plus **
|
|
14
|
-
|
|
12
|
+
Today that means **Claude Code, Codex, OpenCode, Grok Code and Antigravity** as full
|
|
13
|
+
agents, each verified against a real version, plus **Kiro** driven through its own
|
|
14
|
+
window —
|
|
15
15
|
see [Supported agents](#supported-agents) for exactly how far each one goes, and
|
|
16
16
|
[How memory actually reaches a model](#how-memory-actually-reaches-a-model) for the
|
|
17
17
|
part most tools gloss over.
|
|
18
18
|
|
|
19
|
+
An agent doesn't have to be a CLI any more. A **[`model` agent](#agents-that-are-models)**
|
|
20
|
+
is an HTTP endpoint — any OpenAI-compatible provider, a model you pick from a list,
|
|
21
|
+
nothing to install — which means the cheap work can go somewhere cheap, and on a
|
|
22
|
+
provider's free tier it costs nothing at all. Pin one to a
|
|
23
|
+
**[thread](#threads-and-who-answers-in-them)** and two conversations run on two models
|
|
24
|
+
at once; **[ask several](#asking-several-models-at-once)** and one prompt fans out to
|
|
25
|
+
all of them.
|
|
26
|
+
|
|
27
|
+
And because a fleet you can't see is a fleet you can't trust, every turn, handoff and
|
|
28
|
+
route step is exported as **OpenTelemetry** — traces, metrics and logs — and read back
|
|
29
|
+
into an in-app **[Observatory](#observability)**.
|
|
30
|
+
|
|
19
31
|
Loom is **not** another IDE. It's the thin layer *between* your agents — the continuity
|
|
20
32
|
and memory they don't share on their own.
|
|
21
33
|
|
|
@@ -39,6 +51,244 @@ and memory they don't share on their own.
|
|
|
39
51
|
<em>One thread over every agent — projects and chats on the left, the shared conversation in the middle, the Explorer on the right, and a composer you switch agents from without leaving the box.</em>
|
|
40
52
|
</p>
|
|
41
53
|
|
|
54
|
+
## One link — a teammate, their agents, your repo
|
|
55
|
+
|
|
56
|
+
Click **Invite** in a project, or run `loom invite`. Loom makes sure you're on a
|
|
57
|
+
team that shares this repo (it creates the team and shares the repo if you
|
|
58
|
+
haven't) and gives you one https link to send.
|
|
59
|
+
|
|
60
|
+
Your teammate clicks it. If they already have Loom, it opens in their Loom and
|
|
61
|
+
one **Join** does the rest. If they don't, the page shows one command that
|
|
62
|
+
installs Loom and joins:
|
|
63
|
+
|
|
64
|
+
1. **Sign in** with GitHub.
|
|
65
|
+
2. **Join** the team, and keep the team key the link carries.
|
|
66
|
+
3. **Get the repo.** Loom uses a clone they already have, or clones it. For a
|
|
67
|
+
private repo, *your* Loom adds them as a collaborator the moment they join,
|
|
68
|
+
and *theirs* accepts GitHub's invitation.
|
|
69
|
+
4. **Open it with their agents** — whichever of Codex, OpenCode, Claude Code
|
|
70
|
+
and the rest are installed on their machine.
|
|
71
|
+
5. **Share** it with the team, so you see each other's live agents, goals and
|
|
72
|
+
files.
|
|
73
|
+
6. **Set up your crews**, filled from their own agents.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
loom invite # in a project: the link, a QR, and a message to paste
|
|
77
|
+
loom join '<link>' # the same thing from a terminal
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The invite is the part of the link after `#`. Browsers never send that part to
|
|
81
|
+
a server, including the join page's. A link works once and expires in a day.
|
|
82
|
+
It carries the team key, so send it the way you'd send a password.
|
|
83
|
+
|
|
84
|
+
## Quota Relay — a limit doesn't stop the work
|
|
85
|
+
|
|
86
|
+
Hit Claude's 5-hour window halfway through a task, and the work normally
|
|
87
|
+
stops: Claude Code parks the turn until the reset, and Codex ends it with
|
|
88
|
+
"try again at 3:04 PM". Loom recognises the limit and responds:
|
|
89
|
+
|
|
90
|
+
- **It hands the work on.** It stops the parked turn and passes the baton to
|
|
91
|
+
the next agent, with a briefing: "claude ran out of quota; its changes are in
|
|
92
|
+
the working tree, continue from them". It re-sends the same request in the
|
|
93
|
+
same chat.
|
|
94
|
+
- **It routes new prompts.** While an agent is out, anything sent to it goes
|
|
95
|
+
to the agent standing in.
|
|
96
|
+
- **It gives the chat back.** When the window resets, the baton returns.
|
|
97
|
+
- **It waits if it has to.** If no other agent is free, the request is queued
|
|
98
|
+
for the moment the limit lifts.
|
|
99
|
+
|
|
100
|
+
The thread records each step ("claude hit its usage limit — codex carries on;
|
|
101
|
+
claude is back around 19:00"), and your phone gets a push. By default only
|
|
102
|
+
your other CLI agents take over, never a metered API model. Set the order
|
|
103
|
+
with `relay.to`. `relay.auto: false` only reports the limit, and
|
|
104
|
+
`relay.handBack: false` keeps the stand-in. Limits are read from what Claude
|
|
105
|
+
and Codex report about their usage windows, and from the error text any CLI
|
|
106
|
+
gives ("usage limit", "rate limited", "quota exceeded", with "try again at…"
|
|
107
|
+
or "resets in…").
|
|
108
|
+
|
|
109
|
+
## Proof — is "done" true?
|
|
110
|
+
|
|
111
|
+
Agents say "all tests pass" when three of fourteen ran, or when none did. When
|
|
112
|
+
an agent's reply claims the tests, build, typecheck or lint pass, Loom checks
|
|
113
|
+
the claim three ways:
|
|
114
|
+
|
|
115
|
+
- **What it said.** The claims in its final message, including any numbers
|
|
116
|
+
("all 14 tests pass").
|
|
117
|
+
- **What it ran.** The commands from that turn and their exit codes. A claim
|
|
118
|
+
with no test run behind it, or with a failed one, doesn't hold.
|
|
119
|
+
- **A clean re-run.** Loom runs your tests again in a fresh worktree containing
|
|
120
|
+
exactly the agent's uncommitted changes and new files. Dependency folders
|
|
121
|
+
are linked, not reinstalled. Your working tree is never touched.
|
|
122
|
+
|
|
123
|
+
The verdict goes into the thread as a card: **Verified**, **Didn't hold** (with
|
|
124
|
+
the reason, such as "said 14 pass; 11 did"), or **Unverified**. If a "done"
|
|
125
|
+
doesn't hold, your phone gets a push. Every turn card also has **Check it**,
|
|
126
|
+
to verify any turn on demand.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
loom proof # check the newest turn (exits 1 if it didn't hold)
|
|
130
|
+
loom proof --test "npm run test:unit"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The test command is detected from the project (`npm test`, `cargo test`,
|
|
134
|
+
`go test ./...`, `pytest`, `make test`). Override it with `proof.testCommand`
|
|
135
|
+
in `.loom/config.json`; `proof.auto: "off"` turns off automatic checks.
|
|
136
|
+
Pass/fail counts are read from vitest, jest, node:test, mocha, pytest, cargo,
|
|
137
|
+
go test, rspec and phpunit output.
|
|
138
|
+
|
|
139
|
+
## Crews — agents with roles, on one goal
|
|
140
|
+
|
|
141
|
+
A crew is a few of your agents with jobs: a **lead** who plans, **builders**, a
|
|
142
|
+
**reviewer**, a **tester**. You give the crew a goal. The Lead turns it into
|
|
143
|
+
cards, and you approve the plan. Each card is then built, reviewed (changes go
|
|
144
|
+
back to the same builder), and tested, in the goal's own worktree and branch.
|
|
145
|
+
When it's done, you apply it.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
loom crew create ship # templates: ship · fix · research · solo
|
|
149
|
+
loom crew goal "Add rate limiting to the public API"
|
|
150
|
+
loom crew approve # or: loom crew say "smaller cards, please"
|
|
151
|
+
loom crew # cards by stage; questions a teammate asked you
|
|
152
|
+
loom crew apply # merge the goal's branch into yours
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The **Crew** tab shows the same thing. Every commit carries `Loom-Crew`,
|
|
156
|
+
`Loom-Goal`, `Loom-Card` and `Loom-Teammate` trailers. Phase 1 works one card
|
|
157
|
+
at a time; [docs/proposals/agent-teams.md](docs/proposals/agent-teams.md) has
|
|
158
|
+
the rest of the plan.
|
|
159
|
+
|
|
160
|
+
## Orchestra — one conductor, many agents, in parallel
|
|
161
|
+
|
|
162
|
+
Give Loom a goal and let one agent run the others. The **orchestrator** can be any
|
|
163
|
+
agent: Claude Code, Codex (ChatGPT), Antigravity, Grok or OpenCode.
|
|
164
|
+
|
|
165
|
+
1. It reads the repository and splits the goal into a **task graph**.
|
|
166
|
+
2. Loom runs every ready task **at the same time** on the **worker** agents the
|
|
167
|
+
plan names. Each task gets its own thread and its own git worktree.
|
|
168
|
+
3. Loom merges each finished task into one integration branch.
|
|
169
|
+
4. The orchestrator reviews the results. It can accept them, send follow-ups,
|
|
170
|
+
spawn more tasks, or finish.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
loom orchestrate "add OAuth login with tests" -o claude-code -w codex,antigravity,claude-code -p 6
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
┌──────────── orchestrator (any agent) ────────────┐
|
|
178
|
+
goal ──▶ plan │ t1 → codex t2 → antigravity t3 → codex │ ◀── review, follow up, done
|
|
179
|
+
└──────┬──────────────────┬────────────────┬──────┘
|
|
180
|
+
▼ ▼ ▼ (after t1)
|
|
181
|
+
worktree t1 worktree t2 worktree t3
|
|
182
|
+
└──────── merged into loom/orchestra/<run>/main ──▶ apply to your branch
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
- **Any agent can orchestrate.** The orchestrator never runs shell commands. It
|
|
186
|
+
answers in a small JSON block that Loom parses, so an agent in a sandbox or with
|
|
187
|
+
no tools at all can still lead.
|
|
188
|
+
- **Many workers, mixed or the same kind.** Three Codex tasks and two Antigravity
|
|
189
|
+
tasks run side by side as separate CLI sessions, capped by `--parallel` (1–12).
|
|
190
|
+
- **Real dependencies.** A task with `dependsOn` starts only after those tasks are
|
|
191
|
+
merged, and it branches from a tip that already contains their work.
|
|
192
|
+
- **Nothing touches your branch until you say so.** All work lands on
|
|
193
|
+
`loom/orchestra/<run>/main`. You merge it with `loom orchestra:apply <run>`, or
|
|
194
|
+
with the **Apply** button in the app. Merge conflicts go back to the
|
|
195
|
+
orchestrator, which can ask the worker to resolve them.
|
|
196
|
+
- **You're in the loop.** When the orchestrator needs a decision it asks, and the
|
|
197
|
+
run waits for your reply (`loom orchestra:reply <run> "…"`, or the app on your
|
|
198
|
+
phone). You can steer a run mid-flight the same way.
|
|
199
|
+
- **Everything is visible.** Every step is an `orchestra` event. The web app,
|
|
200
|
+
desktop app, phone and CLI all watch the same run live, and each task's stream
|
|
201
|
+
sits in its own thread. The project's budgets, quarantine and shared brain apply
|
|
202
|
+
to every worker.
|
|
203
|
+
|
|
204
|
+
- **Race instead of plan.** `--race` (or the Race switch in Orchestrate) gives
|
|
205
|
+
every worker the whole goal in its own worktree, with no orchestrator. The
|
|
206
|
+
entrants finish side by side, each with its diff, and you keep one:
|
|
207
|
+
**Pick this one** in the app, or `loom orchestra:apply <run> --task race-<agent>`.
|
|
208
|
+
- **A restart doesn't lose the run.** If Loom stops mid-run, the run says it
|
|
209
|
+
was interrupted. **Resume** (or `loom orchestra:resume <run>`) picks up the
|
|
210
|
+
tasks that were running and briefs the orchestrator again.
|
|
211
|
+
|
|
212
|
+
Under the hood: [`src/core/orchestra.ts`](src/core/orchestra.ts).
|
|
213
|
+
`node scripts/verify-orchestra.mjs` runs a real orchestra against your installed
|
|
214
|
+
CLIs and checks the result on disk.
|
|
215
|
+
|
|
216
|
+
## Codex & GPT‑5.6
|
|
217
|
+
|
|
218
|
+
Loom is built around orchestrating **OpenAI Codex** as a first‑class agent — and in this
|
|
219
|
+
build every Codex turn runs **GPT‑5.6**: Codex's model, pinned in Loom's per‑project agent
|
|
220
|
+
config (`.loom/config.json` → `codex → model: "gpt-5.6-terra"`) at high reasoning effort.
|
|
221
|
+
|
|
222
|
+
> The slug is `gpt-5.6-terra`, not a bare `gpt-5.6` — that is the id Codex actually
|
|
223
|
+
> accepts, and asking for `gpt-5.6` is a 400 (`not supported when using Codex with a
|
|
224
|
+
> ChatGPT account`). Loom's model picker asks the CLI what it supports
|
|
225
|
+
> (`codex debug models`) rather than shipping a list that goes stale, which is exactly
|
|
226
|
+
> how this got caught.
|
|
227
|
+
|
|
228
|
+
- **Codex holds the baton like any other agent.** The adapter
|
|
229
|
+
([`src/providers/codex/adapter.ts`](src/providers/codex/adapter.ts)) keeps a warm
|
|
230
|
+
`codex app-server` (JSON-RPC) per chat: it opens a thread, streams Codex's items
|
|
231
|
+
(`agentMessage` / `commandExecution` / `fileChange`), its context usage and compaction,
|
|
232
|
+
and **keeps the same thread across turns** (resuming it after a restart) so Codex keeps
|
|
233
|
+
its own context between handoffs.
|
|
234
|
+
- **Codex reads and writes the shared brain.** Before a Codex turn, Loom projects the
|
|
235
|
+
unified memory (imported ADE memory + decisions + the thread) into its briefing; its
|
|
236
|
+
replies and memory writes land back in the one shared store. So a handoff
|
|
237
|
+
*Claude Code → Codex* carries the full context, and the next agent inherits what Codex
|
|
238
|
+
learned. *(Verified end‑to‑end: Codex recalled a value another agent set one turn
|
|
239
|
+
earlier, and its turns emit `run_complete` + `memory_add`.)*
|
|
240
|
+
- **Voice, on real hardware.** The **LoomPad** is a physical ESP32‑S3 macropad, and the
|
|
241
|
+
loop is: press the **Codex** key to hand Codex the baton, hold the mic and speak, hear
|
|
242
|
+
the reply spoken back through the pad. It lives in this repo under
|
|
243
|
+
[`hardware/orchestrator-pad/`](hardware/orchestrator-pad/README.md) — parametric CAD,
|
|
244
|
+
printable STLs, the wiring diagram, the Arduino firmware and the voice backend — with
|
|
245
|
+
the daemon-side proxy endpoints (`/api/loompad/health`, `/api/loompad/connect`) and the
|
|
246
|
+
status pill that reads them. See [Hardware](#hardware) to print and wire one.
|
|
247
|
+
- **Codex as a dev agent, too.** Because Codex is a full agent, you can hand it real work
|
|
248
|
+
inside Loom — `loom route ship "…"` routes *plan → Codex executes → review*, the brain
|
|
249
|
+
flowing hop to hop.
|
|
250
|
+
|
|
251
|
+
In short: **GPT‑5.6, via Codex, is one of the interchangeable minds Loom keeps in sync** —
|
|
252
|
+
start a thread in Claude Code, hand it to Codex mid‑task, and it picks up with the whole
|
|
253
|
+
shared context intact.
|
|
254
|
+
|
|
255
|
+
## Observability
|
|
256
|
+
|
|
257
|
+
Loom exports its agent activity as **all three OpenTelemetry signals**, and then reads
|
|
258
|
+
them back into an in-app **Observatory** so you never have to leave to answer "what is
|
|
259
|
+
the fleet doing, and what did it cost".
|
|
260
|
+
|
|
261
|
+
**Backend-agnostic.** Loom speaks OTLP/HTTP and nothing else — no vendor SDK, no
|
|
262
|
+
vendor-specific code path. Point `OTEL_EXPORTER_OTLP_ENDPOINT` at a Collector, Grafana,
|
|
263
|
+
Jaeger, SigNoz, Honeycomb, anything with a `/v1/traces` endpoint. An ingestion key
|
|
264
|
+
rides in through the standard `OTEL_EXPORTER_OTLP_HEADERS`, so a new destination is a
|
|
265
|
+
config change and never a code change.
|
|
266
|
+
|
|
267
|
+
| Signal | What Loom puts in it |
|
|
268
|
+
|---|---|
|
|
269
|
+
| **Traces** | every turn as `gen_ai.agent.turn` (model, tokens, cost, duration), tool calls, baton handoffs, route steps, memory folds — one trace per turn |
|
|
270
|
+
| **Metrics** | tokens, turn duration, turns, cost, active agents, handoffs — GenAI semantic conventions |
|
|
271
|
+
| **Logs** | every message, tool call, edit, decision and error, correlated to the span it happened inside |
|
|
272
|
+
|
|
273
|
+
The Observatory tab renders it back: a live fleet canvas, per-agent **burn rate** and
|
|
274
|
+
budgets, **Span Replay** with a trace waterfall, a **Decisions** explorer, a **Logs**
|
|
275
|
+
view, **Time-Travel Replay** that refolds the whole run from the event log, and
|
|
276
|
+
**Ask** — a question about your fleet, answered from its own telemetry.
|
|
277
|
+
|
|
278
|
+
**Self-heal** closes the loop: point your monitoring's webhook at
|
|
279
|
+
`POST /api/webhooks/alerts` (the Alertmanager shape SigNoz, Prometheus and Grafana all
|
|
280
|
+
send) and a firing alert takes the named agent out of rotation until it resolves. That
|
|
281
|
+
is the part a dashboard can't show you — it knows the alert fired, only Loom knows the
|
|
282
|
+
fleet reacted.
|
|
283
|
+
|
|
284
|
+
Two rules run through all of it: **nothing is estimated**, and **absent beats invented**.
|
|
285
|
+
Tokens and cost are what each agent's own CLI reported; an adapter that reports tokens
|
|
286
|
+
but no dollar figure (codex) produces no cost datapoint rather than a zero, because
|
|
287
|
+
zeros are indistinguishable from real cheap turns once summed.
|
|
288
|
+
|
|
289
|
+
Off with `DO_NOT_TRACK=1`. Full details, every variable, and how to verify it's flowing:
|
|
290
|
+
**[docs/observability.md](docs/observability.md)**.
|
|
291
|
+
|
|
42
292
|
## Why
|
|
43
293
|
|
|
44
294
|
Every coding agent keeps its own brain. Claude Code's memory can't be read by
|
|
@@ -52,7 +302,7 @@ agents' **memory together** so work *continues* across them instead of forking.
|
|
|
52
302
|
- **One brain across every ADE** — Loom imports each agent's native memory
|
|
53
303
|
(`CLAUDE.md`, `AGENTS.md`, …) into a unified store, merges it with your decisions and
|
|
54
304
|
the shared thread, and hands the whole thing to whoever picks up next. `loom memory`.
|
|
55
|
-
- **The baton** — exactly one agent
|
|
305
|
+
- **The baton** — exactly one agent may *write* at a time; passing it *carries the context*
|
|
56
306
|
(interrupt-safe, memory projected, briefing armed). Not isolation — continuation.
|
|
57
307
|
- **Routes** — let Loom drive the chain: `loom route ship "add dark mode"` runs
|
|
58
308
|
plan → execute → review as one command, the brain flowing hop to hop; or `loom route
|
|
@@ -66,8 +316,34 @@ agents' **memory together** so work *continues* across them instead of forking.
|
|
|
66
316
|
|
|
67
317
|
Requires **Node ≥ 22.5** (Loom's event log uses the built-in `node:sqlite`).
|
|
68
318
|
|
|
319
|
+
On **Windows**, install Node.js and Git for Windows, then use PowerShell or
|
|
320
|
+
Command Prompt. Bash and WSL are not required. From a source checkout:
|
|
321
|
+
|
|
322
|
+
```powershell
|
|
323
|
+
git clone https://github.com/nickthelegend/loom.git
|
|
324
|
+
cd loom
|
|
325
|
+
npm.cmd ci
|
|
326
|
+
npm.cmd link
|
|
327
|
+
loom.cmd doctor
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
For a managed source installation, run `scripts/install.ps1` in PowerShell
|
|
331
|
+
(it clones into `%USERPROFILE%\.loom-src`, builds, and links the CLI). Use
|
|
332
|
+
`loom.cmd` when PowerShell's execution policy blocks npm's `.ps1` shim.
|
|
333
|
+
Install agents separately: `npm.cmd i -g @openai/codex opencode-ai`, then
|
|
334
|
+
sign in to the agents you use. Claude Code's native Windows installer is also
|
|
335
|
+
detected under `%USERPROFILE%\.local\bin`.
|
|
336
|
+
|
|
337
|
+
Normal chat, shared memory, routes, Orchestra worktrees and crews use the
|
|
338
|
+
standard runtime on Windows. The optional **native continuity** mode
|
|
339
|
+
(`brain.continuity: true`) needs verified process containment. On Windows,
|
|
340
|
+
every harness runs inside a Job Object, and Loom checks the containment
|
|
341
|
+
works on this machine before it offers the mode; if the check fails, the
|
|
342
|
+
agent says why and stays on the standard runtime. Leave it off for Orchestra
|
|
343
|
+
and crews, which also run outside native continuity on other OSes.
|
|
344
|
+
|
|
69
345
|
```bash
|
|
70
|
-
npm install -g
|
|
346
|
+
npm install -g @loompad/cli # → `loom` on your PATH
|
|
71
347
|
```
|
|
72
348
|
|
|
73
349
|
Other paths:
|
|
@@ -92,13 +368,118 @@ loom doctor # checks node, agents, tailscale, daemon, and your project
|
|
|
92
368
|
|
|
93
369
|
Surfaces, all talking to the same daemon:
|
|
94
370
|
- **TUI / CLI** — `loom` (default), `loom chat`, `loom send`, …
|
|
95
|
-
- **Desktop app
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
371
|
+
- **Desktop app (Loom Desktop)** — prebuilt for
|
|
372
|
+
[**macOS**, **Linux**, and **Windows**](https://loompad.tech/#download)
|
|
373
|
+
(`.dmg` · `.AppImage` · `.exe`; the macOS dmg is ad-hoc signed, so right-click → **Open**
|
|
374
|
+
the first time), or build from [`desktop/`](desktop/README.md): `cd desktop && npm
|
|
375
|
+
install && npm start`. Either way it opens a native window that starts the daemon and
|
|
376
|
+
pairs itself.
|
|
377
|
+
- **Phone app (LoomPad)** — install the newest
|
|
378
|
+
[Android APK](https://loompad.tech/download/android) (allow unknown sources; Google
|
|
379
|
+
Play is coming), open **Loom**, and **Scan QR code** from the desktop's *Connect a phone*.
|
|
380
|
+
Voice input, per-prompt diffs, push. Or build from source
|
|
381
|
+
([`app/`](app/README.md)): `cd app && npx expo install && npx expo start`.
|
|
100
382
|
- **Web app** — no install; `loom pair` → open the link. Same workspace in the browser.
|
|
101
383
|
|
|
384
|
+
## Fan out, retry, reap
|
|
385
|
+
|
|
386
|
+
Three moves the fleet learned for when one turn isn't the right shape:
|
|
387
|
+
|
|
388
|
+
- **Sub-agents** — a turn can fan a subtask out to a child that runs alongside
|
|
389
|
+
it and **never holds the baton**: `loom spawn "audit the tests" --agent codex`.
|
|
390
|
+
The child gets a narrowed briefing (its task, the project's rules, retrieval
|
|
391
|
+
scoped to its own work — not the parent's thread), reports back as
|
|
392
|
+
`subtask_done`, and everything it learns lands in the one brain. Capped at
|
|
393
|
+
four per project, because each child is a real CLI process.
|
|
394
|
+
- **Retry on another agent** — when a turn fails, `loom retry <agentId>`
|
|
395
|
+
re-runs the failed prompt on the agent you choose, with the failure attached
|
|
396
|
+
as a briefing instead of pasted into the message: the thread shows a clean
|
|
397
|
+
prompt twice, and the second agent is told what was tried and what it died of.
|
|
398
|
+
- **Reap a hung session** — a CLI that hangs holds `busy` forever and blocks
|
|
399
|
+
every dispatch. `loom stale` names what's hung (busy past ten minutes);
|
|
400
|
+
`loom reap <agentId>` interrupts, stops, respawns from config, and releases
|
|
401
|
+
the baton if the corpse held it.
|
|
402
|
+
|
|
403
|
+
## Native context continuity (opt-in)
|
|
404
|
+
|
|
405
|
+
The new deterministic Brain path supports sequential switching between checked
|
|
406
|
+
Codex/Claude CLI profiles with scoped native sessions, protected user context,
|
|
407
|
+
SQLite search, source-backed checkpoints and delivery diagnostics. It requires no
|
|
408
|
+
embedding model or inference package. Enable **Native context continuity** in
|
|
409
|
+
Settings → Preferences; the legacy workflow remains the default.
|
|
410
|
+
|
|
411
|
+
Unknown CLI versions, OpenCode/bridges, attachments and parallel execution are
|
|
412
|
+
gated in this mode. Overflow saves a reviewable request instead of silently dropping
|
|
413
|
+
user intent. See [native continuity](docs/brain-continuity.md),
|
|
414
|
+
[current architecture](ARCHITECTURE.md) and
|
|
415
|
+
[remaining verification gates](docs/refactoring/BRAIN-TODO.md).
|
|
416
|
+
|
|
417
|
+
## Several sessions, one brain
|
|
418
|
+
|
|
419
|
+
`loom agents:add claude-code` a second time doesn't error — it adds
|
|
420
|
+
`claude-code-2`: its own session, its own context window, **the same project
|
|
421
|
+
brain**. Name them for their jobs (`--as reviewer --role reviewer`) and pick
|
|
422
|
+
per message. Memory import dedupes by file and units are project-scoped, so
|
|
423
|
+
what one session learns the others retrieve. One baton still rules them all:
|
|
424
|
+
two sessions of a kind never means two writers at once.
|
|
425
|
+
|
|
426
|
+
## The Browser tab
|
|
427
|
+
|
|
428
|
+
Agents write Playwright specs constantly and could never watch them run. The
|
|
429
|
+
**Browser** tab (next to Console in the dock) lists the project's specs
|
|
430
|
+
(`*.spec.*`, `*.e2e.*` — never inside `node_modules`), runs one on click with
|
|
431
|
+
the reporter streaming line by line, records pass/fail in the Console, and on
|
|
432
|
+
failure stages the reporter's own words in the composer to hand back to
|
|
433
|
+
whichever agent should fix it. Playwright stays the project's dependency (`npx
|
|
434
|
+
--no-install`) — a project without it is told so plainly.
|
|
435
|
+
|
|
436
|
+
**Your dev servers live there too.** Declare them under `servers` in
|
|
437
|
+
`.loom/config.json` (Loom suggests them from `package.json` and waits for you
|
|
438
|
+
to accept) and the rail shows each one's real state: **running** means a port
|
|
439
|
+
answered, **starting** means the process is up but nothing is listening yet,
|
|
440
|
+
and **crashed** carries the exit code it died with. Start, stop and restart
|
|
441
|
+
from the row; click the row to point the preview at it; open its output under
|
|
442
|
+
the page, which is where the reason for a blank frame usually is. A server
|
|
443
|
+
that dies while an agent works against it says so in the thread, and every
|
|
444
|
+
server Loom started is stopped when the daemon goes.
|
|
445
|
+
|
|
446
|
+
From the terminal: `loom servers`, `loom servers start|stop|restart <name>`,
|
|
447
|
+
`loom servers logs <name> -n 100`.
|
|
448
|
+
|
|
449
|
+
**The page can talk back.** Click a server and Loom previews it through its
|
|
450
|
+
own proxy, which forwards everything (including the socket hot reload rides on)
|
|
451
|
+
and injects one small script. The **Page** pane then shows what that page
|
|
452
|
+
logged and what it fetched, live — and a click puts any line into the composer
|
|
453
|
+
as context, which is the whole point: the loop used to be *see the error,
|
|
454
|
+
select it, retype it*. **Pick** turns the cursor into an element picker: click
|
|
455
|
+
the thing that's wrong and its selector, text, size, markup and computed
|
|
456
|
+
styles (fonts, colours, spacing, layout) land in the composer, along with a
|
|
457
|
+
picture of just that element, so any agent can see what you mean. The picture
|
|
458
|
+
is taken at your width and colour scheme with the project's own Playwright.
|
|
459
|
+
Nothing about the page is rewritten; the script only reports.
|
|
460
|
+
|
|
461
|
+
**The preview itself** emulates a width (Fit / 375 / 768 / 1280, remembered per
|
|
462
|
+
project, scaled down when the dock is narrower), reloads itself when an agent
|
|
463
|
+
changes files — a toggle, on by default — and has a camera button that puts a
|
|
464
|
+
picture of what you're looking at into the composer. The page is another
|
|
465
|
+
origin, so the browser won't let it photograph itself: the daemon takes the
|
|
466
|
+
shot with the project's own Playwright, and a project without Playwright is
|
|
467
|
+
told exactly that rather than handed an empty file.
|
|
468
|
+
|
|
469
|
+
**Dark mode, on the page being previewed** — Auto / ☀ / ☾, independent of
|
|
470
|
+
Loom's own theme and remembered per project. No API lets one document set
|
|
471
|
+
another's `prefers-color-scheme`; Loom isn't outside the page, though — the
|
|
472
|
+
proxy served it *and* its stylesheets — so the bridge re-points the page's own
|
|
473
|
+
`@media (prefers-color-scheme: …)` rules, sets the UA colour-scheme, and makes
|
|
474
|
+
`matchMedia` answer the way you asked (firing `change`, so an app that themes
|
|
475
|
+
itself in JavaScript re-renders). Every rewritten rule remembers the media text
|
|
476
|
+
it came with, so **Auto is the page exactly as it shipped**; stylesheets Loom
|
|
477
|
+
doesn't serve can't be read and aren't claimed. A page you typed into the
|
|
478
|
+
address bar rather than previewing through Loom says so instead of offering a
|
|
479
|
+
switch that does nothing — and the screenshot drives a real browser, where the
|
|
480
|
+
scheme is honoured either way. Both conditions ride along with what you send:
|
|
481
|
+
*Screenshot taken at 375×812, dark mode*, and an element pick names them too.
|
|
482
|
+
|
|
102
483
|
## The workspace
|
|
103
484
|
|
|
104
485
|
On a wide screen the web app (and the desktop shell around it) is a full workspace for
|
|
@@ -198,7 +579,7 @@ form — the same bet the agent adapters make by shelling out to the CLIs you al
|
|
|
198
579
|
|
|
199
580
|
```bash
|
|
200
581
|
cd your-project
|
|
201
|
-
loom init # detects
|
|
582
|
+
loom init # detects the agents installed on this machine, assigns roles
|
|
202
583
|
loom # opens the TUI — a tabbed workspace (Thread · Board · Brain · Diff)
|
|
203
584
|
```
|
|
204
585
|
|
|
@@ -267,7 +648,11 @@ What happens per hop: interrupt-safe **handoff** → shared-memory **projection*
|
|
|
267
648
|
- the agent asks a question → the route **pauses** (`waiting_human`), you get a
|
|
268
649
|
notification, `loom route --status` and the board show the question; you answer in
|
|
269
650
|
the shared thread (`loom send "…"`) and the route **resumes by itself**;
|
|
270
|
-
- an agent errors or a step times out (45 min default) → the route fails loudly
|
|
651
|
+
- an agent errors or a step times out (45 min default) → the route fails loudly —
|
|
652
|
+
unless the step carries an [`onFail`](#routes-that-loop-and-steps-that-skip), which
|
|
653
|
+
sends it back to an earlier step instead;
|
|
654
|
+
- a step carrying a [condition](#routes-that-loop-and-steps-that-skip) the previous turn
|
|
655
|
+
didn't meet (`reviewer?lines>200`) is **skipped**, with the numbers that decided it;
|
|
271
656
|
- **you always outrank the route**: any manual `handoff`/`interrupt` cancels it, and
|
|
272
657
|
`loom route --abort` stops it and interrupts the in-flight turn.
|
|
273
658
|
|
|
@@ -284,13 +669,15 @@ step can carry its own focus:
|
|
|
284
669
|
"api-only": [
|
|
285
670
|
{ "step": "planner", "instruction": "design the endpoint contract only" },
|
|
286
671
|
{ "step": "executor", "instruction": "only touch src/api — no schema changes" },
|
|
287
|
-
"reviewer"
|
|
672
|
+
{ "step": "reviewer", "when": "lines>200", "onFail": "executor" }
|
|
288
673
|
]
|
|
289
674
|
}
|
|
290
675
|
```
|
|
291
676
|
|
|
292
677
|
Per-step instructions are appended to the role guidance for exactly that step — the
|
|
293
|
-
next hop never sees them. `
|
|
678
|
+
next hop never sees them. `when` runs a step only if the previous turn met a condition
|
|
679
|
+
Loom measured, and `onFail` sends a failing step back to an earlier one; both are
|
|
680
|
+
[explained here](#routes-that-loop-and-steps-that-skip). `loom init` seeds a `ship` route automatically when it
|
|
294
681
|
detects at least two roles.
|
|
295
682
|
|
|
296
683
|
## Commands
|
|
@@ -305,29 +692,94 @@ detects at least two roles.
|
|
|
305
692
|
| `loom route <spec> "<task>"` | Run a pipeline (name, or `a,b,c` ids/roles); `--status` / `--abort` / `--detach` |
|
|
306
693
|
| `loom routes` | List named pipelines defined for this project |
|
|
307
694
|
| `loom interrupt` | Stop the current holder's turn (cancels an active route) |
|
|
695
|
+
| `loom update [--check] [--yes]` | Is there a newer Loom — and install it, in the commands it prints first |
|
|
696
|
+
| `loom servers [start\|stop\|restart\|logs] [name]` | The project's dev servers: real state, start/stop, and their output |
|
|
697
|
+
| `loom queue` | What's lined up behind the running turn — position, who takes it, why it's waiting |
|
|
698
|
+
| `loom queue add "<text>" [--to <agent\|orchestrate\|auto>] [--plan]` | Line a prompt up; it goes when nothing is in its way |
|
|
699
|
+
| `loom queue edit / to / move / rm / clear` | Rewrite one, send it to someone else, reorder it, drop it, empty the queue |
|
|
700
|
+
| `loom queue pause` / `loom queue resume` | Hold the queue where it is · let it run again |
|
|
701
|
+
| `loom queue at <when> "<text>"` / `after landed:<run>\|green:<run>` | Queue a prompt for a time, or for a goal landing / going green |
|
|
702
|
+
| `loom queue save <name>` / `run <name>` / `recipes` | Keep a queue as a recipe and replay it on any project |
|
|
703
|
+
| `loom digest [--since <hours>]` | What happened in this project while you were away |
|
|
308
704
|
| `loom decision <text>` | Record a decision into shared memory |
|
|
309
705
|
| `loom memory [import]` | The unified brain — one memory across every connected ADE |
|
|
310
706
|
| `loom log [-f]` | Show (or follow) the project event log |
|
|
311
707
|
| `loom costs` | Project spend: total + per-agent turns, $ and agent time |
|
|
312
|
-
| `loom agents` / `loom projects` / `loom status` |
|
|
708
|
+
| `loom agents` / `loom projects` / `loom status` | Agent roster, project board, daemon health |
|
|
709
|
+
| `loom providers` | Where model agents send their turns, and whether each has a key |
|
|
710
|
+
| `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 |
|
|
711
|
+
| `loom providers:rm <id>` | Forget a provider's key and settings |
|
|
712
|
+
| `loom models [agentId] [--provider --free --refresh]` | With an agent: the models it reports. Without: what every configured provider has right now |
|
|
713
|
+
| `loom ask "<text>" [--models a,b,c \| --free --limit n]` | One prompt to several models at once, a thread each, side by side |
|
|
313
714
|
| `loom up [--tailnet] [--restart]` / `loom down` / `loom daemon` | Daemon lifecycle (`--tailnet` binds to your Tailscale IP) |
|
|
314
715
|
| `loom pair` | QR deep link that pairs a phone (single-use token) |
|
|
315
716
|
| `loom clients [--revoke <id>] [--ping]` | Paired devices: list, revoke, or send a test push |
|
|
316
|
-
| `loom doctor` | Diagnose env, daemon, binding, and project config —
|
|
717
|
+
| `loom doctor [--json] [--fix]` | Diagnose env, daemon, binding, and project config — `--fix` repairs what has exactly one safe repair |
|
|
718
|
+
| `loom orchestrate "<goal>"` | One orchestrator plans, many workers build in parallel worktrees (`-o`, `-w`, `-p`, `--no-watch`) |
|
|
719
|
+
| `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 |
|
|
720
|
+
| `loom orchestra [run] [--watch]` | Orchestra runs in this project, or one run's task graph live |
|
|
721
|
+
| `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 |
|
|
722
|
+
| `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 |
|
|
723
|
+
| `loom orchestra:resume <run>` | Carry on a run Loom stopped by restarting |
|
|
724
|
+
| `loom stats` | Today across every project: turns, how many failed, the time and the spend |
|
|
725
|
+
| `loom export [--chat <id\|title>] [--out file]` | A chat as Markdown: prompts, replies, tool summaries and turn footers |
|
|
726
|
+
| `loom backup [--out file]` | Everything Loom knows in one archive: `~/.loom` and every project's `.loom` (logs copied as consistent snapshots) |
|
|
727
|
+
| `loom completion zsh\|bash` | Shell completion: `loom completion zsh >> ~/.zshrc` |
|
|
728
|
+
| `loom hub [--host --port --secret]` | Run a self-hosted Team Hub for your team |
|
|
729
|
+
| `loom invite` / `loom join <link>` | One link: a teammate joins the team and gets this repo, their agents and your crews |
|
|
730
|
+
| `loom crew [status\|create\|goal\|say\|approve\|stop\|resume\|apply\|diff\|remove]` | Crews: agents with roles (lead, builders, reviewer, tester) working one goal |
|
|
731
|
+
| `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 |
|
|
732
|
+
| `loom land [runId]` | Land a goal's PR: fresh main in, fast tests, push, merge when GitHub's rules pass |
|
|
733
|
+
| `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 |
|
|
734
|
+
| `loom cloud [status\|enable\|disable\|rotate]` | Loom Cloud relay: reach this daemon from any network, end-to-end encrypted |
|
|
735
|
+
| `loom spawn "<task>"` | Fan a subtask out to a child agent — the parent keeps the baton |
|
|
736
|
+
| `loom subtasks` | Subtasks running right now |
|
|
737
|
+
| `loom agents:add <kind> [--as name] [--role r]` | Add an agent session — repeat for a second session of the same kind, same brain |
|
|
738
|
+
| `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 |
|
|
739
|
+
| `loom agents:rm <id>` / `loom agents:available` | Remove a session · what this machine can drive and how many are here |
|
|
740
|
+
| `loom watch [--all] [--json]` | Tail a project's events live — turns, handoffs, memory, subtasks |
|
|
741
|
+
| `loom retry <agentId>` | Re-run the last failed turn on a different agent, failure attached |
|
|
742
|
+
| `loom stale` / `loom reap <agentId>` | Sessions that look hung · respawn one fresh (baton released) |
|
|
743
|
+
| `loom budgets` / `loom budget <id> [usd]` | Daily USD caps: measured spend vs cap · set or clear one |
|
|
744
|
+
| `loom brain:search "<query>" [--explain]` | Retrieval exactly as a briefing sees it — the score, and the arithmetic behind it |
|
|
745
|
+
| `loom brain:forget <memoryId>` | Drop one unit from the project's memory |
|
|
746
|
+
| `loom brain:export [file]` / `brain:import <file>` | The project's memory as a portable file — import dedupes |
|
|
747
|
+
| `loom brain:conflicts` | Units that likely contradict each other, with the signal that tripped each |
|
|
748
|
+
| `loom snapshot [file]` / `loom restore <file>` | Checkpoint brain+board+config · bring one back (brain merges) |
|
|
749
|
+
| `loom rewind [checkpoint]` | Points the working tree can go back to · put the files back to one |
|
|
750
|
+
| `loom routes:save <name> <steps...>` / `routes:rm` | Define a named pipeline, validated against the roster · remove one |
|
|
751
|
+
| `loom task "<title>"` / `loom tasks` | Put a card on the board (`--agent`, `--blocked-by`) · list yours |
|
|
752
|
+
| `loom specs` / `loom specs:run <file>` | The project's Playwright specs · run one through the daemon |
|
|
753
|
+
| `loom mcp:health` | Each MCP server's live state from the background poll |
|
|
754
|
+
| `loom costs --series [days]` | The spend ledger day by day, per agent |
|
|
755
|
+
| `loom open` / `loom version` | Open this project in the web app · what's installed |
|
|
756
|
+
| `loom rename <name>` / `loom find <query>` | Rename the project (id never moves) · search the thread |
|
|
317
757
|
|
|
318
758
|
## Supported agents
|
|
319
759
|
|
|
320
760
|
| Agent | Tier | Transport | Status |
|
|
321
761
|
|---|---|---|---|
|
|
322
|
-
| Claude Code | adapter (full-duplex) |
|
|
323
|
-
| Codex | adapter (full-duplex) | `codex
|
|
324
|
-
| OpenCode | adapter (full-duplex) | `opencode serve` HTTP + SSE (`/prompt`, `/interrupt`, `/event`) | ✅ verified against 1.17.20 |
|
|
762
|
+
| 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) |
|
|
763
|
+
| 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) |
|
|
764
|
+
| OpenCode | adapter (full-duplex) | `opencode serve` HTTP + SSE (`/prompt`, `/interrupt`, `/event`) | ✅ verified against 1.17.20; re-verified 1.18.31 (2026-09-18) |
|
|
325
765
|
| 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) |
|
|
766
|
+
| 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) |
|
|
767
|
+
| Cursor | — | `cursor-agent --print` | 🔜 coming soon |
|
|
768
|
+
| **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](#agents-that-are-models)) |
|
|
326
769
|
| Echo | adapter (demo/tests) | in-process | ✅ |
|
|
327
|
-
|
|
|
328
|
-
|
|
770
|
+
| 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) |
|
|
771
|
+
|
|
772
|
+
**Context and limits.** For Claude Code and Codex, the composer shows the chosen
|
|
773
|
+
agent's context in use against its model's window, shows **compacting…** while the
|
|
774
|
+
harness compacts (with a "compacted its context · 180k → 12k tokens" line in the
|
|
775
|
+
thread when it's done), and calls out a usage-limit window once it passes 80%. Hover
|
|
776
|
+
for every window and when it resets.
|
|
329
777
|
|
|
330
|
-
|
|
778
|
+
> The Claude Agent SDK package carries a platform build of Claude Code as an
|
|
779
|
+
> optional dependency (about 215 MB installed on macOS arm64). Loom never runs it: it always points
|
|
780
|
+
> the SDK at your own `claude`. Installing with `--omit=optional` skips it.
|
|
781
|
+
|
|
782
|
+
Four of those need their asterisks spelled out, because the table row is
|
|
331
783
|
shorter than the truth:
|
|
332
784
|
|
|
333
785
|
**Codex reports tokens, never money.** Its `turn.completed` carries
|
|
@@ -344,15 +796,24 @@ the tree would put guesses in the event log dressed as facts. Its permission
|
|
|
344
796
|
mode also defaults to `bypassPermissions`, because headless with no TTY to ask,
|
|
345
797
|
every other mode ends the turn `Cancelled` having written nothing.
|
|
346
798
|
|
|
347
|
-
**Antigravity
|
|
799
|
+
**Antigravity used to be a bridge, and isn't any more.** Loom drove the IDE's
|
|
800
|
+
chat panel over the DevTools protocol: it needed the app open with a debugging
|
|
801
|
+
port, it could only watch, and it could never hold the baton. `agy` does the
|
|
802
|
+
whole turn headless, so Antigravity is a full agent and the GUI stopped being a
|
|
803
|
+
dependency. It reports no dollar cost — the CLI hands us none — so its turns
|
|
804
|
+
show a model and a duration and honestly nothing else. The CDP bridge is still
|
|
805
|
+
in the tree and still builds, so projects that name it still open; it just isn't
|
|
806
|
+
offered any more.
|
|
807
|
+
|
|
808
|
+
**Kiro is driven, not routed.** It's an Electron app with no
|
|
348
809
|
API; Loom connects to the debugging port, finds the chat box, types through the
|
|
349
810
|
input pipeline and reads back what the panel gained — the approach
|
|
350
811
|
[antigravity_phone_chat](https://github.com/krishnakanthb13/antigravity_phone_chat)
|
|
351
812
|
takes, and for the same reason: never touch the provider APIs, drive the app
|
|
352
|
-
that's already signed in. Launch
|
|
813
|
+
that's already signed in. Launch it with
|
|
353
814
|
`--remote-debugging-port=9222` first.
|
|
354
815
|
|
|
355
|
-
The driver refuses more than it accepts, on purpose.
|
|
816
|
+
The driver refuses more than it accepts, on purpose. Kiro is VS Code
|
|
356
817
|
family and Monaco — the editor holding your source file — is a
|
|
357
818
|
`contenteditable`. Anything under `.monaco-editor` is never a candidate, a
|
|
358
819
|
candidate must be labelled like a chat box, and zero-or-several matches is a
|
|
@@ -360,18 +821,159 @@ refusal that names the fix (`options.selectors.composer`). Typing a prompt into
|
|
|
360
821
|
your code and pressing Enter is not a mistake an error message repairs.
|
|
361
822
|
|
|
362
823
|
What's verified is the mechanism, against a real Chromium. What is **not**
|
|
363
|
-
verified is
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
824
|
+
verified is Kiro's actual chat DOM: it shows no chat panel until you open one,
|
|
825
|
+
so there was no composer to read the selectors from. Reachable and driveable are
|
|
826
|
+
separate questions, and Loom answers both — an app with no chat panel open
|
|
827
|
+
replies to CDP cheerfully and reports
|
|
367
828
|
`driveable: false — no chat box on screen`.
|
|
368
829
|
|
|
369
|
-
**Adapters**
|
|
370
|
-
diff) and may hold the baton. **Bridges** only observe and receive shared-memory
|
|
830
|
+
**Adapters** run a turn to completion headless and may hold the baton. **Bridges** only observe and receive shared-memory
|
|
371
831
|
projections — they never hold the write lock. That's a design decision, not a gap: GUI
|
|
372
832
|
agents without a stable API can't be trusted with interrupt-safe writes. See
|
|
373
833
|
[docs/integration-notes.md](docs/integration-notes.md) for the verified surfaces.
|
|
374
834
|
|
|
835
|
+
## Agents that are models
|
|
836
|
+
|
|
837
|
+
Every other agent in Loom wraps a CLI. A `model` agent is an HTTP endpoint:
|
|
838
|
+
|
|
839
|
+
```sh
|
|
840
|
+
loom providers:set openrouter --key sk-… # or $OPENROUTER_API_KEY
|
|
841
|
+
loom models --free # what costs nothing today
|
|
842
|
+
loom agents:add model --as cheap --role reviewer \
|
|
843
|
+
--model "google/gemma-4-31b-it:free" --tools
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
It streams into the thread like any other agent, shows reasoning as reasoning,
|
|
847
|
+
reports its token counts, and holds the baton. Out of the box it is a thinker
|
|
848
|
+
rather than an editor — planning, reviewing, summarising, answering, routing,
|
|
849
|
+
which is most of what a fleet does between edits and what free quota is very
|
|
850
|
+
happy to pay for — and it becomes an editor only when you say so, below.
|
|
851
|
+
|
|
852
|
+
**It can read the project, if you let it.** `--tools` adds `read_file`,
|
|
853
|
+
`list_files` and `search` — read-only, inside the project, with `.git` and
|
|
854
|
+
`.loom` off limits and every path proven contained before anything opens it.
|
|
855
|
+
Each call shows in the thread, and the loop is bounded at eight hops.
|
|
856
|
+
|
|
857
|
+
**And write, if you say so.** With `--tools` already on, `--write` adds
|
|
858
|
+
`write_file` and `--run "npm test"` adds `run` with that allow-list. (Either
|
|
859
|
+
without `--tools` is refused — they're the writing half of it, not a
|
|
860
|
+
replacement for it.) Every write and every command is a card you
|
|
861
|
+
allow or deny first:
|
|
862
|
+
|
|
863
|
+
> **write_file** — write src/app.ts: 40 lines (replacing 12) — fix the port
|
|
864
|
+
> [Allow] [Deny]
|
|
865
|
+
|
|
866
|
+
It asks in `auto` as well as `ask`, which is stricter than the CLI mapping and
|
|
867
|
+
on purpose: a CLI in your roster is one you installed and signed into, and a
|
|
868
|
+
model agent is a name you picked off a provider's list an hour ago. `bypass`
|
|
869
|
+
is the only mode that doesn't ask. With nobody to ask, the answer is no.
|
|
870
|
+
|
|
871
|
+
The allow-list is a prefix match on whole commands and there is no shell — so
|
|
872
|
+
`npm test` permits `npm test --watch` and refuses `npm testify`,
|
|
873
|
+
`npm test; rm -rf ~`, backticks, pipes and redirection — and a tool is only
|
|
874
|
+
offered to the model when it can actually be used.
|
|
875
|
+
|
|
876
|
+
**Providers Loom knows by name** — and anything else that speaks
|
|
877
|
+
`/v1/chat/completions` works with a base URL:
|
|
878
|
+
|
|
879
|
+
| provider | base URL | free tier |
|
|
880
|
+
|---|---|---|
|
|
881
|
+
| `openrouter` | `https://openrouter.ai/api` | models whose id ends `:free` |
|
|
882
|
+
| `agentrouter` | `https://agentrouter.org` | all of it — but see the note below |
|
|
883
|
+
| `groq` | `https://api.groq.com/openai` | — |
|
|
884
|
+
| `openai` | `https://api.openai.com` | — |
|
|
885
|
+
| `ollama` | `http://127.0.0.1:11434` | local, no key |
|
|
886
|
+
| *anything* | `loom providers:set mine --base-url …` | whatever you tell it |
|
|
887
|
+
|
|
888
|
+
**Keys are never project config.** `.loom/config.json` names a provider;
|
|
889
|
+
the key lives in the environment or in `~/.loom/providers.json` (mode 0600),
|
|
890
|
+
and nothing — no route, no log, no listing — hands it back. You get the last
|
|
891
|
+
four characters, which is enough to tell two keys apart.
|
|
892
|
+
|
|
893
|
+
**A fallback chain, for when the free pool is dry:**
|
|
894
|
+
|
|
895
|
+
```json
|
|
896
|
+
{ "id": "cheap", "kind": "model", "role": "reviewer",
|
|
897
|
+
"options": { "provider": "openrouter",
|
|
898
|
+
"model": "google/gemma-4-31b-it:free",
|
|
899
|
+
"fallbacks": ["qwen/qwen3.8-27b:free"],
|
|
900
|
+
"tools": true } }
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
**Refusals are read, not guessed at.** A dry free pool (402) or a rate limit
|
|
904
|
+
(429) moves to the next model in `fallbacks` and says so once; a model name
|
|
905
|
+
that doesn't exist (503) stops, because falling back would hide the typo. And
|
|
906
|
+
a provider that rejects *Loom* rather than your key says exactly that —
|
|
907
|
+
AgentRouter, for one, only accepts clients it recognises, which is a thing to
|
|
908
|
+
ask them about rather than a key to go and regenerate.
|
|
909
|
+
|
|
910
|
+
## Threads, and who answers in them
|
|
911
|
+
|
|
912
|
+
A thread can name its own agent. It then answers there whatever the baton is
|
|
913
|
+
doing elsewhere, which is what lets two threads talk to two agents at once —
|
|
914
|
+
one planning on a free model, one editing with Claude Code, neither waiting for
|
|
915
|
+
the other.
|
|
916
|
+
|
|
917
|
+
Pinning does **not** take the baton. The baton is the write lock for work that
|
|
918
|
+
touches the repository; a conversation doesn't need it, and taking it would
|
|
919
|
+
stop whatever is actually working. The main thread still follows the baton,
|
|
920
|
+
because that's what makes it the main thread.
|
|
921
|
+
|
|
922
|
+
A thread may pin a **model** as well, for agents that can change model per turn
|
|
923
|
+
(the `model` kind). Pinning one to an agent whose model is baked into a spawned
|
|
924
|
+
process is refused at the point of pinning — a setting that silently does
|
|
925
|
+
nothing is worse than an error.
|
|
926
|
+
|
|
927
|
+
## Asking several models at once
|
|
928
|
+
|
|
929
|
+
```sh
|
|
930
|
+
loom ask --free --limit 3 "is this migration reversible?"
|
|
931
|
+
loom ask --models "openrouter/qwen/qwen3.8-27b:free,ollama/llama3" "review this approach"
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
In the app it's **More → Ask several models…** in the composer: tick the
|
|
935
|
+
models (free ones are listed first), and what's in the box goes to each.
|
|
936
|
+
|
|
937
|
+
One prompt, one thread per model, all at the same time, each thread named
|
|
938
|
+
after the model that answered in it. With free quota this costs what asking
|
|
939
|
+
one model costs, and picking the good answer takes a person ten seconds.
|
|
940
|
+
|
|
941
|
+
The agents that run are transient — five asks don't leave five agents in the
|
|
942
|
+
roster — and one model failing leaves its error in its own thread while the
|
|
943
|
+
others carry on.
|
|
944
|
+
|
|
945
|
+
## The brain, sharpened
|
|
946
|
+
|
|
947
|
+
Five retrieval behaviours worth knowing, all inspectable with `explain`:
|
|
948
|
+
|
|
949
|
+
- **Recency decay** — a unit's match decays toward a floor (7-day half-life,
|
|
950
|
+
never below 0.55): an equal match, fresher, wins; a strong old constraint
|
|
951
|
+
still beats a weak new fact. Age tips ties, it never erases history.
|
|
952
|
+
- **A fuzzy channel** — hashed character-trigram vectors catch typos and
|
|
953
|
+
morphology (`sqllite databse` finds `sqlite database`; `deploying` finds
|
|
954
|
+
`deployment`). Honestly **not** synonymy: that needs a model, which is the
|
|
955
|
+
next one.
|
|
956
|
+
- **A semantic channel, opt-in** (`brain.semantic`, or Settings → Brain) —
|
|
957
|
+
all-MiniLM-L6-v2 embeddings, so *how does login work* reaches a note about
|
|
958
|
+
Supabase JWKS. Measured on `test/brain-recall.test.ts`, recall@5 goes
|
|
959
|
+
**0.50 → 1.00** on queries that share no word with the memory they want,
|
|
960
|
+
**0.80 → 1.00** across word forms, and stays **1.00** on the literal ones —
|
|
961
|
+
it costs nothing where the lexical channels already worked. It is **not** a
|
|
962
|
+
dependency: the 23MB model needs a ~470MB ONNX runtime, so Loom asks you to
|
|
963
|
+
install it (`npm i -g @huggingface/transformers`) and works exactly as
|
|
964
|
+
before if you don't. Scored relative to the best hit in the query, like
|
|
965
|
+
BM25, because this model's absolute similarities don't mean enough to
|
|
966
|
+
threshold.
|
|
967
|
+
- **Contradiction flags** — `loom brain:conflicts` scans same-kind units for
|
|
968
|
+
negation pairs (`always`/`never` on one topic) and same-topic-divergent
|
|
969
|
+
decisions. Every flag names the signal that tripped it, so you can judge the
|
|
970
|
+
judge. Resolution stays human.
|
|
971
|
+
- **Portability** — `loom brain:export` writes what the project knows to a
|
|
972
|
+
plain file; `brain:import` brings one in, deduped by hash, so the same file
|
|
973
|
+
twice reports *known* rather than minting duplicates. `loom snapshot` /
|
|
974
|
+
`restore` widen that to brain+board+config — restore **merges** the brain,
|
|
975
|
+
because what the project learned since the snapshot should survive it.
|
|
976
|
+
|
|
375
977
|
## How memory actually reaches a model
|
|
376
978
|
|
|
377
979
|
Worth being precise about, because this is the whole premise and it has a soft edge.
|
|
@@ -384,15 +986,15 @@ Getting it into the model's context is a different problem, and it depends on th
|
|
|
384
986
|
|
|
385
987
|
| Agent | How the brain arrives | Strength |
|
|
386
988
|
|---|---|---|
|
|
387
|
-
| Claude Code | briefing
|
|
989
|
+
| 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 |
|
|
388
990
|
| 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 |
|
|
389
|
-
| Codex | no
|
|
991
|
+
| 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 |
|
|
390
992
|
| 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 |
|
|
391
993
|
| 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 |
|
|
392
994
|
|
|
393
995
|
So: the **summary always lands**; the **full brain is an invitation**. An agent that
|
|
394
996
|
ignores the pointer works from the summary alone. If you need something remembered for
|
|
395
|
-
certain, put it in a decision (`loom
|
|
997
|
+
certain, put it in a decision (`loom decision`) — decisions ride in the briefing itself.
|
|
396
998
|
There's an opt-in eval (`LOOM_TEST_REAL=1`) that checks a real model actually *uses* an
|
|
397
999
|
injected brief, and declines rather than invents when the brief is silent.
|
|
398
1000
|
|
|
@@ -408,9 +1010,10 @@ convention, a fact, a failure — reconciled on write (add / update / forget, ne
|
|
|
408
1010
|
growing blob), the approach [mem0](https://github.com/mem0ai/mem0) pioneered, adapted to
|
|
409
1011
|
Loom's event log. Every unit's evidence is verified against the turn before it's kept, so
|
|
410
1012
|
the brain doesn't remember things that were never said. Retrieval is hybrid too — exact
|
|
411
|
-
entity matches (file paths, symbols, error codes) unioned with BM25 over the text
|
|
412
|
-
|
|
413
|
-
|
|
1013
|
+
entity matches (file paths, symbols, error codes) unioned with BM25 over the text and a
|
|
1014
|
+
trigram channel for typos and word forms, and [a real embedding
|
|
1015
|
+
channel](#the-brain-sharpened) if you turn one on — with failures and constraints biased
|
|
1016
|
+
to the top of the brief, because getting burned twice is worse than missing a detail.
|
|
414
1017
|
|
|
415
1018
|
The brain is the **project's**, not each agent's: a fact one agent learns is scoped to the
|
|
416
1019
|
chat, not walled off to whoever happened to learn it, so it reaches whichever agent takes
|
|
@@ -447,7 +1050,7 @@ the extractor off per project in Settings.
|
|
|
447
1050
|
- **Unified memory ("multiple memory in one")** — each connected ADE keeps its own
|
|
448
1051
|
native memory (`CLAUDE.md`, `AGENTS.md`, …). Loom imports them all into one brain
|
|
449
1052
|
(`memory_import` events, content-hash deduped), merges them with the project's
|
|
450
|
-
decisions and shared thread, and projects the union into whoever
|
|
1053
|
+
decisions and shared thread, and projects the union into whoever is taking the turn.
|
|
451
1054
|
Connect a new agent → its knowledge joins the brain, and everything the others learned
|
|
452
1055
|
flows into it. `loom memory` shows the merged brain; it refreshes on open and on every
|
|
453
1056
|
handoff. This is the seam an isolation-first tool (separate worktrees) can't own.
|
|
@@ -502,8 +1105,762 @@ phone buzzes when an agent **needs input**, when a **route completes or fails**,
|
|
|
502
1105
|
when a solo turn finishes — route hops are deliberately silent (a 5-step pipeline
|
|
503
1106
|
buzzes once, not five times). Verify with `loom clients --ping`.
|
|
504
1107
|
|
|
1108
|
+
## Permissions: bypass, auto, or always ask
|
|
1109
|
+
|
|
1110
|
+
Every agent CLI has its own idea of "may I?". Loom gives you one dropdown with
|
|
1111
|
+
three words and maps them onto each CLI. Every cell below was checked against the
|
|
1112
|
+
real CLI with `node scripts/verify-permissions.mjs`, not read off `--help`.
|
|
1113
|
+
|
|
1114
|
+
| | Bypass | Auto | Always ask |
|
|
1115
|
+
|---|---|---|---|
|
|
1116
|
+
| Claude Code | `bypassPermissions` | `acceptEdits` *(default)* | **real approvals**: each tool call waits for your Allow / Deny in Loom, on desktop or phone |
|
|
1117
|
+
| Codex | no sandbox | `workspace-write` *(default)* | **real approvals**: a `read-only` sandbox, and each command or edit waits for your Allow / Deny in Loom |
|
|
1118
|
+
| Antigravity | `--dangerously-skip-permissions` *(default)* | ✗ unavailable: agy 1.2.6 headless writes into its own scratch folder, not your project | `--mode plan` |
|
|
1119
|
+
| Grok | `bypassPermissions` *(default)* | `auto` | `plan` |
|
|
1120
|
+
| OpenCode | allow-all | its defaults *(default)* | ✗ unavailable: opencode 1.18.31 ignores read-only settings on its headless API |
|
|
1121
|
+
|
|
1122
|
+
- "Unavailable" cells are shown disabled with the reason, and the API refuses them.
|
|
1123
|
+
- **Always ask** is answered inside the daemon: Claude Code's permission prompts
|
|
1124
|
+
arrive through the Agent SDK's `canUseTool`, and Codex's through app-server
|
|
1125
|
+
approval requests. The request appears in the thread as an approval card; nothing
|
|
1126
|
+
happens until you answer, and a card closes by itself if the turn ends first.
|
|
1127
|
+
- `node scripts/verify-approvals.mjs` proves it against the real CLI: the file does
|
|
1128
|
+
not exist until you press Allow.
|
|
1129
|
+
|
|
1130
|
+
## Plan mode
|
|
1131
|
+
|
|
1132
|
+
Turn on **Plan** in the composer and agents write plans, not code.
|
|
1133
|
+
|
|
1134
|
+
- **In a chat:** the agent investigates, then writes
|
|
1135
|
+
`plans/<date>-<slug>.md` (Goal, Context, Approach, self-contained Tasks, Risks,
|
|
1136
|
+
Verification) and stops.
|
|
1137
|
+
- **In an orchestra** (`loom orchestrate --plan "<goal>"`):
|
|
1138
|
+
- the orchestrator's plan becomes `plans/<run>/PLAN.md` plus one spec file per
|
|
1139
|
+
task, committed to the run's branch *before* any worker starts;
|
|
1140
|
+
- each worker is pointed at its own spec;
|
|
1141
|
+
- when the run ends, statuses and results are written back.
|
|
1142
|
+
|
|
1143
|
+
Every file has front matter (`id`, `agent`, `depends_on`, `status`) and is written
|
|
1144
|
+
so another agent, or a teammate, can pick up any task with no other context.
|
|
1145
|
+
|
|
1146
|
+
## Git delivery: commit, push, or open a PR
|
|
1147
|
+
|
|
1148
|
+
One per-project setting decides what happens to finished work. It's the toggle
|
|
1149
|
+
next to GitHub in the status bar, or `git.delivery` in `.loom/config.json`.
|
|
1150
|
+
|
|
1151
|
+
| | Chat turns | A finished orchestra |
|
|
1152
|
+
|---|---|---|
|
|
1153
|
+
| **No commit** *(default)* | left uncommitted | waits on its branch until you Apply |
|
|
1154
|
+
| **Commit only** | committed, agent as co-author | merged into your branch |
|
|
1155
|
+
| **Commit & push** | committed and pushed | merged and pushed |
|
|
1156
|
+
| **Commit & open PR** | committed | its branch pushed, and a PR opened with `gh`: summary, task table, PLAN.md |
|
|
1157
|
+
|
|
1158
|
+
A failed push never un-finishes a run. The error is shown, and **Retry delivery**
|
|
1159
|
+
is one click.
|
|
1160
|
+
|
|
1161
|
+
## Rewind: put the work back the way it was
|
|
1162
|
+
|
|
1163
|
+
An agent turn can touch forty files, and until now the honest answer to *undo
|
|
1164
|
+
that* was "read the diff and retype it". So **Loom writes down what the files
|
|
1165
|
+
were before every turn**, and the turn's Update card offers **Rewind**.
|
|
1166
|
+
|
|
1167
|
+
It is a working tree, not a history. Rewinding restores what changed, removes
|
|
1168
|
+
what was created, brings back what was deleted — and does not move HEAD, does
|
|
1169
|
+
not touch your commits, and does not edit the thread. What happened still
|
|
1170
|
+
happened; the files just don't have to live with it.
|
|
1171
|
+
|
|
1172
|
+
**Three things it will never touch.** Checkpoints are taken with `git add -A`,
|
|
1173
|
+
so `.gitignore` applies at capture: your `node_modules`, your build output and
|
|
1174
|
+
your `.env` are never read, never stored and never restored. `.loom/` is
|
|
1175
|
+
ignored too, which is why a rewind cannot eat the event log.
|
|
1176
|
+
|
|
1177
|
+
**It is itself rewindable.** A rewind saves what it is about to replace before
|
|
1178
|
+
it replaces it, and hands you that checkpoint back — in the thread as *Undo the
|
|
1179
|
+
rewind*, and on the command line as the id to pass next. A one-way undo is a
|
|
1180
|
+
trap, and the one time it matters is the time someone rewinds past work they
|
|
1181
|
+
meant to keep.
|
|
1182
|
+
|
|
1183
|
+
**It refuses while an agent is mid-turn**, naming who. Replacing the tree under
|
|
1184
|
+
a running agent gives it a working directory that contradicts everything it has
|
|
1185
|
+
read, and the damage lands in whatever it writes next.
|
|
1186
|
+
|
|
1187
|
+
```sh
|
|
1188
|
+
loom rewind # the points you can go back to
|
|
1189
|
+
loom rewind cm8x2k1p # go back to one (asks first)
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
Checkpoints live on hidden refs under `refs/loom/checkpoints/`, so no branch
|
|
1193
|
+
listing, no stash and no `git log` ever shows them. The newest 60 are kept.
|
|
1194
|
+
|
|
1195
|
+
## Review Triage: what to read first
|
|
1196
|
+
|
|
1197
|
+
An agent hands back twelve changed files and you have five minutes. Most are
|
|
1198
|
+
fine to skim; one deleted a test, one touched the auth middleware, one added a
|
|
1199
|
+
dependency. Every turn card now ranks its files by what a reviewer would want
|
|
1200
|
+
to see first and puts the top ones up front, each with its reason:
|
|
1201
|
+
|
|
1202
|
+
> **Review first** `session.ts` touches auth or security code · `ci.yml` changes CI, build or deploy
|
|
1203
|
+
|
|
1204
|
+
A chip opens the diff on that file, and its header carries the reason. What it
|
|
1205
|
+
flags, most serious first:
|
|
1206
|
+
|
|
1207
|
+
- an added secret key or hard-coded credential
|
|
1208
|
+
- a deleted test file, a skipped or focused test, or test assertions removed
|
|
1209
|
+
- a database migration or schema change
|
|
1210
|
+
- auth, session, token, permission or other security code
|
|
1211
|
+
- CI, build and deploy files; new or changed dependencies; project config
|
|
1212
|
+
- added risky calls: `eval`, shell commands, `rm -rf` or `git push --force`,
|
|
1213
|
+
raw HTML injection, TLS checks turned off, a silenced type or lint check
|
|
1214
|
+
- a large source change in a turn that touched no test
|
|
1215
|
+
|
|
1216
|
+
Lockfiles and generated files sink to the bottom. Triage reads only what the
|
|
1217
|
+
diff shows and never guesses why: it says "a test was removed", not "the agent
|
|
1218
|
+
cheated". The phone's turn card lists the same files.
|
|
1219
|
+
|
|
1220
|
+
## Opening a PR from a card
|
|
1221
|
+
|
|
1222
|
+
With `git.branchPerTask` on, dragging a card to **Working** cuts its branch.
|
|
1223
|
+
When it reaches **Review**, the card offers **Open PR** — which first shows
|
|
1224
|
+
what would be pushed: the branch, the commits `main` doesn't have, the files
|
|
1225
|
+
they touch, and the exact `git push` and `gh pr create` it will run. Asking
|
|
1226
|
+
pushes nothing. Publishing happens on the button and nowhere else.
|
|
1227
|
+
|
|
1228
|
+
## While you were away
|
|
1229
|
+
|
|
1230
|
+
Come back to a project you haven't looked at in a while and Loom says what
|
|
1231
|
+
happened: goals started and finished, what landed, what failed, what a server
|
|
1232
|
+
did, and what is waiting on you — newest first, each line clicking through to
|
|
1233
|
+
the moment it came from. It says what happened and never guesses at why; a
|
|
1234
|
+
line without an event behind it doesn't get written.
|
|
1235
|
+
|
|
1236
|
+
The mark is per device, because "when did you last look" is a fact about this
|
|
1237
|
+
window. From the terminal: `loom digest --since 8`.
|
|
1238
|
+
|
|
1239
|
+
## Night Shift: run the list while you sleep
|
|
1240
|
+
|
|
1241
|
+
Line up work before bed and wake up to a report you can trust. Each task runs
|
|
1242
|
+
in its own chat, one after another, through the queue. While the shift runs:
|
|
1243
|
+
|
|
1244
|
+
- **Proof** checks every "done" against what ran and a clean re-run of the tests.
|
|
1245
|
+
- **Loop watch** nudges an agent going in circles (on by default during a shift).
|
|
1246
|
+
- **Quota Relay** hands the work on if an agent runs out.
|
|
1247
|
+
- **A question doesn't hold up the line.** It stays open in its chat, and the
|
|
1248
|
+
next task goes.
|
|
1249
|
+
|
|
1250
|
+
When the last task settles, one report card lands in the thread and on your
|
|
1251
|
+
phone: each task marked **Verified**, **Done**, **Didn't hold**, **Failed**,
|
|
1252
|
+
**Needs you** or **Not started**, with what decided it, the files it changed,
|
|
1253
|
+
and what it cost. Click a task to open its chat.
|
|
1254
|
+
|
|
1255
|
+
```bash
|
|
1256
|
+
loom shift "fix the flaky date test" "add a --json flag to loom costs" --to codex --until 7:00 --max 5
|
|
1257
|
+
loom shift # how it's going, or the last report
|
|
1258
|
+
loom shift --stop # the running task finishes; nothing new starts
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
In the desktop app, type the tasks in the composer, one per line, and choose
|
|
1262
|
+
**⋯ → Night shift…** to pick the agent, a stop-by time and a budget. On the
|
|
1263
|
+
phone, type them and tap 🌙. `--until` and `--max` stop *new* tasks from
|
|
1264
|
+
starting; the one running always finishes. A shift survives a daemon restart.
|
|
1265
|
+
A task the restart cut off is reported as failed, and the rest carry on.
|
|
1266
|
+
|
|
1267
|
+
## Scorecards: which agent can you trust with this?
|
|
1268
|
+
|
|
1269
|
+
"Finished without an error" isn't the same as "did what it said". Proof, the
|
|
1270
|
+
loop watch and the shield record, per agent, whether its work held up, and
|
|
1271
|
+
the Observatory's leaderboard (and the phone's) has a **Held up** column:
|
|
1272
|
+
|
|
1273
|
+
- how many of its checked "done"s were verified, out of all it claimed (for
|
|
1274
|
+
example `9/10`, green above 90%)
|
|
1275
|
+
- the times the loop watch caught it going in circles (↻) and the shield
|
|
1276
|
+
caught a command that can't be taken back (⛔)
|
|
1277
|
+
- **cost per verified result**: its spend over the "done"s that held, which
|
|
1278
|
+
is what a result you can trust costs from that agent
|
|
1279
|
+
|
|
1280
|
+
```bash
|
|
1281
|
+
loom scorecard # every agent's record in this project
|
|
1282
|
+
loom scorecard --days 7
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
The same numbers are on every agent in `/api/projects/:id/insights/turns` as
|
|
1286
|
+
`trust`.
|
|
1287
|
+
|
|
1288
|
+
## Replay: a run you can send anyone
|
|
1289
|
+
|
|
1290
|
+
Export any chat as one self-contained HTML page:
|
|
1291
|
+
- what you asked and what the agents said;
|
|
1292
|
+
- every command they ran, with its output folded away;
|
|
1293
|
+
- every diff, with Review Triage's "read first";
|
|
1294
|
+
- what Loom checked along the way: Proof's verdicts, the loop watch, the
|
|
1295
|
+
shield, a Night Shift's report.
|
|
1296
|
+
|
|
1297
|
+
Across the top is a one-line summary: who worked, how many turns, how long,
|
|
1298
|
+
files changed, checks verified, cost. Send it to a teammate, attach it to a
|
|
1299
|
+
PR, or post it. It opens in any browser, offline, with nothing to install.
|
|
1300
|
+
|
|
1301
|
+
```bash
|
|
1302
|
+
loom replay # the main chat → <project>-main.replay.html
|
|
1303
|
+
loom replay --chat "night" --open
|
|
1304
|
+
```
|
|
1305
|
+
|
|
1306
|
+
In the desktop app it's **⋯ → Export as replay**, beside Export as
|
|
1307
|
+
Markdown. The page runs no scripts and loads nothing: every value is
|
|
1308
|
+
escaped, and a Content-Security-Policy forbids the rest. It does contain the
|
|
1309
|
+
code and output from the chat, so share it the way you'd share the repo.
|
|
1310
|
+
|
|
1311
|
+
## Blast-Radius Shield: what can't be taken back
|
|
1312
|
+
|
|
1313
|
+
Rewind puts the files back. It can't un-push a force-push, restore a dropped
|
|
1314
|
+
table, or un-publish a package. The shield watches every command an agent
|
|
1315
|
+
runs, and when one can't be taken back it says so at once, with a red card
|
|
1316
|
+
in its chat and a push to your phone, naming the command and why it matters:
|
|
1317
|
+
|
|
1318
|
+
- **git:** force-push (not `--force-with-lease`), `reset --hard`,
|
|
1319
|
+
`clean -fd`, discarding every change, `branch -D`, history rewrites
|
|
1320
|
+
- **files:** a recursive delete outside the project (`/`, your home, `..`,
|
|
1321
|
+
an absolute path elsewhere). Inside the project is fine.
|
|
1322
|
+
- **data:** `DROP TABLE`, `TRUNCATE`, a `DELETE` with no `WHERE`, database resets
|
|
1323
|
+
- **infrastructure and publishing:** `terraform destroy`, `kubectl delete`,
|
|
1324
|
+
`npm publish`, `docker system prune -a`
|
|
1325
|
+
- **the machine:** `sudo`, piping a download into a shell, `chmod 777`,
|
|
1326
|
+
writing to a disk device
|
|
1327
|
+
|
|
1328
|
+
Most CLIs report a command after it has run, so the shield can't veto that
|
|
1329
|
+
first command. What it can do is stop the turn before it does more:
|
|
1330
|
+
|
|
1331
|
+
```json
|
|
1332
|
+
{ "shield": { "auto": "stop" } }
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
`"notify"` (the default) tells you and offers **Stop the turn**. `"stop"`
|
|
1336
|
+
stops it immediately, and is the default during a Night Shift, when nobody
|
|
1337
|
+
is watching. `"off"` turns the shield off.
|
|
1338
|
+
|
|
1339
|
+
## Loop watch: an agent going in circles
|
|
1340
|
+
|
|
1341
|
+
The expensive failure isn't the error. It's the agent that hits the same red
|
|
1342
|
+
test eight times while you're away, burning tokens on fixes that don't work.
|
|
1343
|
+
Loom counts what each agent runs within a turn, and when the same command has
|
|
1344
|
+
failed three times (or the exact same call has been made five times) it says
|
|
1345
|
+
so: a card in the thread with the command, its last output, and three ways
|
|
1346
|
+
out, plus a push to your phone under Questions.
|
|
1347
|
+
|
|
1348
|
+
- **Stop**: end the turn.
|
|
1349
|
+
- **Nudge it**: stop the turn and tell the agent to say what's actually
|
|
1350
|
+
failing and try a different approach.
|
|
1351
|
+
- **Hand it off**: give the original request to another agent, told what the
|
|
1352
|
+
first one kept getting wrong. The changes stay in the working tree.
|
|
1353
|
+
|
|
1354
|
+
```json
|
|
1355
|
+
{ "watchdog": { "auto": "nudge" } }
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
`"notify"` (the default) only tells you. `"nudge"` also nudges on its own,
|
|
1359
|
+
once; if the agent is still going in circles after that, it's yours to decide.
|
|
1360
|
+
`"off"` turns the watch off. A person speaking in the chat starts every count
|
|
1361
|
+
over.
|
|
1362
|
+
|
|
1363
|
+
## Fleet: what every agent is doing
|
|
1364
|
+
|
|
1365
|
+
The **Fleet** tab (and the phone's Fleet screen) shows every agent in every open
|
|
1366
|
+
project, updated live:
|
|
1367
|
+
- its thread;
|
|
1368
|
+
- whether it's mid-turn, and for how long;
|
|
1369
|
+
- its permission mode;
|
|
1370
|
+
- the last thing it did: the command it ran, the file it edited, or what it said.
|
|
1371
|
+
|
|
1372
|
+
Orchestra tasks are listed too, each with a link to its thread.
|
|
1373
|
+
`GET /api/activity` serves the same data.
|
|
1374
|
+
|
|
1375
|
+
## Updating
|
|
1376
|
+
|
|
1377
|
+
Loom tells you when a newer version is published — a quiet pill in the status
|
|
1378
|
+
bar, never a banner — and **Settings → Updates** has the button.
|
|
1379
|
+
|
|
1380
|
+
It says what it will do before it does it, because that depends on how you
|
|
1381
|
+
installed it: a **git checkout** pulls, installs and rebuilds; a **global npm
|
|
1382
|
+
install** reinstalls itself; anything else is pointed at the release rather
|
|
1383
|
+
than having a command guessed at and run over it. A checkout with uncommitted
|
|
1384
|
+
changes is refused — that tree is yours. The commands are shown in full, their
|
|
1385
|
+
output streams while they run, and the daemon restarts itself on the new build
|
|
1386
|
+
with the page reconnecting to it.
|
|
1387
|
+
|
|
1388
|
+
From the terminal: `loom update --check` to look, `loom update` to do it.
|
|
1389
|
+
|
|
1390
|
+
**The desktop app** has its own, under **Help → Check for Updates…**, because
|
|
1391
|
+
an installed application is not a checkout:
|
|
1392
|
+
|
|
1393
|
+
- **Windows** and the **Linux AppImage** update themselves — it asks before
|
|
1394
|
+
downloading, installs on quit, and restarts only when you press Restart.
|
|
1395
|
+
- **macOS** finds the build for your architecture, downloads it, and
|
|
1396
|
+
**verifies its SHA-256** against the checksums the release publishes (a file
|
|
1397
|
+
that doesn't match is deleted, not opened), then opens the disk image for
|
|
1398
|
+
you to drag across. The drag stays yours: macOS only lets an app signed by a
|
|
1399
|
+
certificate it already trusts replace itself, and this build is ad-hoc
|
|
1400
|
+
signed. Automating the *checking* is the part worth having — the checksums
|
|
1401
|
+
have shipped since 0.2.0 and nobody compares them by hand.
|
|
1402
|
+
- A **`.deb`** is left to the package manager that owns it — and so is a
|
|
1403
|
+
**Homebrew** install: `brew install --cask` means `brew upgrade --cask`, and
|
|
1404
|
+
Loom says so rather than downloading over a copy brew is tracking. It also
|
|
1405
|
+
offers to **run that command in Loom's own terminal**, the one in the dock,
|
|
1406
|
+
so you watch the upgrade happen instead of it happening inside the app:
|
|
1407
|
+
|
|
1408
|
+
> Loom Desktop 0.2.4 was installed with Homebrew
|
|
1409
|
+
> `brew upgrade --cask loom-desktop`
|
|
1410
|
+
> [Run it in Loom] [Copy command] [Close]
|
|
1411
|
+
|
|
1412
|
+
It replaces the app in place, so restart Loom when it finishes. An upgrade
|
|
1413
|
+
that happens invisibly inside an app is an upgrade nobody can check.
|
|
1414
|
+
|
|
1415
|
+
**On a Mac, Homebrew is the easy way in:**
|
|
1416
|
+
|
|
1417
|
+
```sh
|
|
1418
|
+
brew tap nickthelegend/loom https://github.com/nickthelegend/loom
|
|
1419
|
+
brew install --cask loom-desktop
|
|
1420
|
+
brew upgrade --cask loom-desktop
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
The cask lives in this repo because the official tap requires every cask to be
|
|
1424
|
+
signed and notarized by Apple, and Loom's macOS build is ad-hoc signed. **There
|
|
1425
|
+
is no free Developer ID for open-source projects** — Apple's fee waiver covers
|
|
1426
|
+
nonprofits, schools and government entities, and a free Apple ID cannot sign
|
|
1427
|
+
for distribution at all — so the honest options are to buy one, to be one of
|
|
1428
|
+
those entities, or to ship unsigned and say so.
|
|
1429
|
+
|
|
1430
|
+
Installing with Homebrew does **not** get past Gatekeeper: brew applies the
|
|
1431
|
+
quarantine attribute to cask downloads deliberately, and removed the flag that
|
|
1432
|
+
used to skip it. The first launch asks, exactly as it does for a dmg you
|
|
1433
|
+
downloaded yourself — right-click, Open, Open, once. Some taps strip the
|
|
1434
|
+
attribute in a postflight; this one doesn't, because quarantine is the
|
|
1435
|
+
protection that exists *because* the app is unsigned. What the cask buys is a
|
|
1436
|
+
one-line install and a real update path owned by a package manager you already
|
|
1437
|
+
trust.
|
|
1438
|
+
|
|
1439
|
+
## More than one goal, when they can't collide
|
|
1440
|
+
|
|
1441
|
+
One orchestra goal at a time is the default, and for good reason: two goals
|
|
1442
|
+
editing the same files is a merge nobody can explain. Set
|
|
1443
|
+
`maxConcurrentGoals` in `.loom/config.json` and Loom will start a second one —
|
|
1444
|
+
but only when the paths can't collide. A goal's scope is the `touches` its
|
|
1445
|
+
plan declares; a goal that hasn't said what it touches counts as touching
|
|
1446
|
+
everything, and waits. Anything overlapping queues as it always did, with the
|
|
1447
|
+
overlap named: *waiting for "rework billing" — both touch src/billing/\*\* and
|
|
1448
|
+
src/billing/tax.ts*.
|
|
1449
|
+
|
|
1450
|
+
## What a goal may spend
|
|
1451
|
+
|
|
1452
|
+
`loom orchestrate --max-usd 2 "…"` stops that goal at $2: running work
|
|
1453
|
+
finishes its turn, nothing new starts, and the goal goes to **needs you** with
|
|
1454
|
+
what it spent. Replying allows one more budget's worth. A project default
|
|
1455
|
+
lives in `.loom/config.json` as `budgets.perGoalUsd`, and there's a word in the
|
|
1456
|
+
thread at 80% — hearing about a budget for the first time when the goal halts
|
|
1457
|
+
is a bad way to learn it exists.
|
|
1458
|
+
|
|
1459
|
+
## Before you send: the forecast
|
|
1460
|
+
|
|
1461
|
+
The composer shows what a turn on the chosen agent usually costs and takes:
|
|
1462
|
+
`~$0.12 · 1m 40s`. The numbers come from that agent's own recent turns in
|
|
1463
|
+
this project, not from a price list. "Typical" is the median of its last 30
|
|
1464
|
+
turns, and the tooltip shows the 80th percentile, so one marathon turn doesn't
|
|
1465
|
+
skew it. An agent on a subscription reports no spend, so it gets a time-only
|
|
1466
|
+
forecast. If the agent has a daily budget, the chip turns amber when one more
|
|
1467
|
+
long turn could cross it. The same numbers are on every agent in
|
|
1468
|
+
`/api/projects/:id` as `forecast`.
|
|
1469
|
+
|
|
1470
|
+
## The prompt queue — line up the next ones
|
|
1471
|
+
|
|
1472
|
+
Type while an agent is mid-turn, or while a goal is still running, and the
|
|
1473
|
+
prompt waits above the composer instead of being refused or lost. It stays
|
|
1474
|
+
yours until it's sent:
|
|
1475
|
+
|
|
1476
|
+
- **Queue as many as you like.** They run strictly one at a time, in order.
|
|
1477
|
+
- **Send each one where you want it:** one agent by name, the **orchestrator**
|
|
1478
|
+
(a whole new goal, started when the running one finishes), or **Auto** to let
|
|
1479
|
+
the router pick. Change a waiting prompt's target from the list on the item.
|
|
1480
|
+
- **Edit, reorder, drop.** Click a queued prompt to edit it (⌘⏎ saves, Esc
|
|
1481
|
+
cancels), move it up or down, or remove it — all before it runs.
|
|
1482
|
+
- **Pause and resume** the whole queue. **Stop** pauses it too: stopping a turn
|
|
1483
|
+
never silently discards what you lined up behind it.
|
|
1484
|
+
- **A question holds the queue.** If the agent stops to ask you something, the
|
|
1485
|
+
next queued prompt would answer a question you never read — so the queue
|
|
1486
|
+
waits and says who asked. Your reply goes out at once and lifts the hold; a
|
|
1487
|
+
pause you set yourself stays until you resume it.
|
|
1488
|
+
- A prompt enters the conversation when it is actually sent, not when it's
|
|
1489
|
+
queued, so the thread stays an honest record of what the agent was asked.
|
|
1490
|
+
- **Reorder by dragging** (the arrows stay, for anyone who can't drag).
|
|
1491
|
+
- **Queue for later.** A prompt can wait for a time, for a goal to *land*
|
|
1492
|
+
(not merely finish), for that goal's checks to go green, or for the project
|
|
1493
|
+
to be quiet for a while: `loom queue at 03:00 "…"`,
|
|
1494
|
+
`loom queue after landed:<runId> "…"`, `loom queue after green:<runId> "…"`.
|
|
1495
|
+
The row says what it's waiting for, and one click releases it.
|
|
1496
|
+
- **Save a queue as a recipe** and run it anywhere: `loom queue save ship`,
|
|
1497
|
+
`loom queue run ship`, `loom queue recipes`. Steps remember who they go to by
|
|
1498
|
+
**role**, so a recipe travels between projects; anything a project can't
|
|
1499
|
+
resolve becomes an Auto prompt rather than a refusal.
|
|
1500
|
+
|
|
1501
|
+
It survives a daemon restart (reloaded paused, so an hour-old queue doesn't
|
|
1502
|
+
start itself), and lives in the project's `.loom/queue.json`. Over the API:
|
|
1503
|
+
`GET/POST /api/projects/:id/queue`, `PATCH|DELETE .../queue/:itemId` (text,
|
|
1504
|
+
target, or `to` for its position), `POST .../queue/pause`, `DELETE .../queue`.
|
|
1505
|
+
|
|
1506
|
+
## Prompt manager
|
|
1507
|
+
|
|
1508
|
+
A clipboard manager for prompts, built into the chat bar (⌘⇧V):
|
|
1509
|
+
- **Saved** prompts: pinned first, then most used.
|
|
1510
|
+
- **Recent:** every prompt you actually sent (chats, plans, orchestra goals),
|
|
1511
|
+
de-duplicated and searchable.
|
|
1512
|
+
|
|
1513
|
+
Everything is stored in `~/.loom/prompts.json` and never leaves the machine.
|
|
1514
|
+
|
|
1515
|
+
## Teams — see each other's agents
|
|
1516
|
+
|
|
1517
|
+
Five people, each with their own agents, on one repo. **Phases 1 to 6 are built.** Phase 1:
|
|
1518
|
+
teammates see each other's live agents and goals, and a team feed collects goals,
|
|
1519
|
+
plans, PRs and CI in one place. Content is end-to-end encrypted to the team, so
|
|
1520
|
+
the hub can't read goal titles.
|
|
1521
|
+
|
|
1522
|
+
```bash
|
|
1523
|
+
loom hub --host 0.0.0.0 --secret <join-secret> # one teammate runs a hub (or use hosted Loom Teams)
|
|
1524
|
+
loom team signin http://<hub>:7430 --secret <join-secret>
|
|
1525
|
+
loom team create "Acme"
|
|
1526
|
+
loom team invite # one-time link; it carries the team key, so send it like a password
|
|
1527
|
+
loom team join '<link>' # a teammate
|
|
1528
|
+
loom team share # in a project: its repo now shows up for the team
|
|
1529
|
+
loom team # who's doing what, right now
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
- **What teammates see:** agents, state, branch, the files each task declared
|
|
1533
|
+
it will touch, and goal and task titles. They never see your prompts or your
|
|
1534
|
+
agents' transcripts.
|
|
1535
|
+
- **Sharing is opt-in per project.** A clone whose `origin` matches a team repo
|
|
1536
|
+
is shared automatically, and `loom team unshare` opts it out.
|
|
1537
|
+
- **Removing a member rotates the team key.** The new key is sealed to every
|
|
1538
|
+
remaining device, so the person who left can't read anything new.
|
|
1539
|
+
- **Worker commits carry `Loom-Goal`, `Loom-Task`, `Loom-Agent` and `Loom-Member`
|
|
1540
|
+
trailers.** `git log` alone tells you which goal, task and agent wrote a line.
|
|
1541
|
+
|
|
1542
|
+
**Phase 2 (stop colliding) is built too:**
|
|
1543
|
+
- **Declaring touches.** On a team project, every orchestra task declares the files
|
|
1544
|
+
it will `touch`, and Loom takes a lease on them.
|
|
1545
|
+
- **Overlap needs a decision.** If a teammate's goal already holds some of those
|
|
1546
|
+
files, the task waits until the orchestrator decides: wait for their PR, narrow
|
|
1547
|
+
its scope, or proceed with a reason the team can see.
|
|
1548
|
+
- **Hard zones.** Areas listed in `loom.team.json` (migrations, lockfiles) take one
|
|
1549
|
+
goal at a time. Other tasks queue and start on their own when the area frees up.
|
|
1550
|
+
- **Conflict prediction.** Work in progress goes to hidden `refs/loom/wip/*` refs,
|
|
1551
|
+
and `git merge-tree` predicts merge conflicts before anyone opens a PR. Both
|
|
1552
|
+
people involved hear about it.
|
|
1553
|
+
- **Team policy.** `loom.team.json` on your default branch sets a permission
|
|
1554
|
+
ceiling, the allowed agents, protected branches and concurrency caps. Changing it
|
|
1555
|
+
takes a reviewed PR.
|
|
1556
|
+
|
|
1557
|
+
```jsonc
|
|
1558
|
+
// loom.team.json
|
|
1559
|
+
{ "hardZones": ["db/migrations/**", "package-lock.json"],
|
|
1560
|
+
"permissions": { "ceiling": "auto", "bypassRequiresPlan": true },
|
|
1561
|
+
"delivery": { "protected": ["main"] },
|
|
1562
|
+
"orchestra": { "maxParallelPerMember": 6, "teamMaxConcurrentAgents": 20 } }
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
**Phase 3 (one brain) is built too:**
|
|
1566
|
+
- **What one agent learns, the team's agents know.** Durable memories
|
|
1567
|
+
(constraints, failures, decisions, conventions, facts) from a shared project
|
|
1568
|
+
reach teammates, encrypted to the team. `task` notes, memories you mark
|
|
1569
|
+
private, and anything learned while reading outside content (web pages, issue
|
|
1570
|
+
and PR text) stay on your machine.
|
|
1571
|
+
- **Every briefed line says how sure to be.** Briefings rank team canon first,
|
|
1572
|
+
then memories confirmed by two or more teammates, then your own, then a single
|
|
1573
|
+
teammate's proposal, and label each one. When two people's agents learn the
|
|
1574
|
+
same thing, it becomes one memory with two confirmations.
|
|
1575
|
+
- **Canon lives in `AGENTS.md`, reviewed like code.** "Propose as canon" adds a
|
|
1576
|
+
memory to a Loom-managed section of `AGENTS.md` on one rolling `loom/canon` PR,
|
|
1577
|
+
and adds `@AGENTS.md` to `CLAUDE.md`. Once merged, every agent reads it, Loom or
|
|
1578
|
+
not. Edit or delete a line in any PR to change canon.
|
|
1579
|
+
- **An inbox for what needs a human:** contradictions between teammates' memories,
|
|
1580
|
+
near-duplicates, corrections waiting on a decision, untrusted memories, and
|
|
1581
|
+
memories ready for canon. Resolving keeps the loser, linked to the winner.
|
|
1582
|
+
- **Live team context.** Worker briefings also say which teammates hold leases on
|
|
1583
|
+
the same paths, which open PRs change them, which checks are failing, and where
|
|
1584
|
+
a conflict is predicted.
|
|
1585
|
+
|
|
1586
|
+
```bash
|
|
1587
|
+
loom team brain # the team's memories and your inbox
|
|
1588
|
+
loom team brain promote <id> # propose as canon (opens or updates the loom/canon PR)
|
|
1589
|
+
loom team brain resolve <keep> <drop> "why"
|
|
1590
|
+
loom team brain trust <id> # an untrusted memory is fine: share it
|
|
1591
|
+
```
|
|
1592
|
+
|
|
1593
|
+
**Phase 4 (land safely) is built too:**
|
|
1594
|
+
- **CI comes back to the owner.** Your daemon watches your goal PRs. A failing
|
|
1595
|
+
required check is rerun once; if it passes, it's labelled flaky and no agent
|
|
1596
|
+
touches it. A real failure's log goes to the goal's orchestrator, which fixes
|
|
1597
|
+
it and pushes to the same PR — at most 2 attempts, then your phone buzzes.
|
|
1598
|
+
- **A second opinion from another vendor.** An agent from a different vendor
|
|
1599
|
+
than the goal's authors reviews each goal PR (on open and after each fix) and
|
|
1600
|
+
posts a `loom/review` check that fails only on high-severity findings. It
|
|
1601
|
+
comments; it never approves.
|
|
1602
|
+
- **Land.** One click (or `loom land`) merges fresh main into the goal, runs the
|
|
1603
|
+
team's fast tests, pushes, and asks GitHub to merge when approvals and checks
|
|
1604
|
+
pass. A conflict gets one agent attempt; a conflict in a hard zone goes to you.
|
|
1605
|
+
- **Stacks, Adopt, budgets.** Big goals can land as a stack of PRs
|
|
1606
|
+
(`"delivery": {"stack": "auto"}`). When your goal is stuck and you're offline,
|
|
1607
|
+
a teammate can adopt it, get it green on your branch, and hand it back. Goals
|
|
1608
|
+
and members can have budgets, and the team view rolls up cost per member, per
|
|
1609
|
+
goal and per landed PR.
|
|
1610
|
+
- **Doctor.** `loom team doctor` checks your repo for merge-queue traps
|
|
1611
|
+
(workflows that don't run on `merge_group`, no required checks) and offers a
|
|
1612
|
+
fix PR. It never changes repo settings.
|
|
1613
|
+
|
|
1614
|
+
```bash
|
|
1615
|
+
loom team landing # your goal PRs, and teammates' goals that need someone
|
|
1616
|
+
loom land # land the goal PR
|
|
1617
|
+
loom team adopt 123 # take a stuck teammate goal and make it green
|
|
1618
|
+
loom team doctor # merge-queue setup check (add "fix" to open the PR)
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
```jsonc
|
|
1622
|
+
// loom.team.json — Phase 4 additions
|
|
1623
|
+
{ "landing": { "fastTest": "npm test -- --changed", "timeoutMin": 10, "autoFixAttempts": 2 },
|
|
1624
|
+
"review": { "enabled": true, "maxRuns": 3 },
|
|
1625
|
+
"delivery": { "protected": ["main"], "stack": "auto" },
|
|
1626
|
+
"budgets": { "perGoalUsd": 15, "perMemberDailyUsd": 60 } }
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
**Hosted hub.** `loom team signin` with no URL signs in to hosted Loom Teams with
|
|
1630
|
+
your GitHub account; `loom team signin http://host:7430` still uses a
|
|
1631
|
+
self-hosted `loom hub`.
|
|
1632
|
+
|
|
1633
|
+
**Phase 5 (runners) is built too:**
|
|
1634
|
+
- **Your goals keep going when your laptop sleeps.** A runner is your own
|
|
1635
|
+
always-on Loom (a VPS, a home server, a container) paired to your account. Start
|
|
1636
|
+
a goal on it, or move a running goal there — from the desktop or your phone —
|
|
1637
|
+
and bring it back whenever you like. Its code never touches Loom's servers.
|
|
1638
|
+
- **Its own trust tier.** Each goal gets a fresh clone (a fresh container when
|
|
1639
|
+
Docker runs), agents get a scrubbed environment with only their logins and a
|
|
1640
|
+
fine-grained GitHub token, and `loom.team.json` sets how far they may go.
|
|
1641
|
+
- **CI fixes while you're away.** If your goal needs a fix and you've been offline
|
|
1642
|
+
15 minutes, your runner takes it.
|
|
1643
|
+
- **Shared runners** can take teammates' goals when the repo's policy allows it.
|
|
1644
|
+
- **Deploys and release notes.** Deploy results show in the team feed, a failed
|
|
1645
|
+
deploy that contains your goal buzzes your phone, and
|
|
1646
|
+
`loom team release-notes --since v1.2.0` writes the notes.
|
|
1647
|
+
|
|
1648
|
+
```bash
|
|
1649
|
+
loom runner pair # on your laptop: a one-time link
|
|
1650
|
+
loom runner join '<link>' # on the always-on box (then: loom runner install)
|
|
1651
|
+
loom runner token github_pat_… # a fine-grained token: contents + pull requests
|
|
1652
|
+
loom runner goal "Add rate limiting" # start a goal there
|
|
1653
|
+
loom runner move <runId> # move a running goal; `loom runner back <runId>` to return it
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
Or with Docker: `docker build -f Dockerfile.runner -t loom-runner .`
|
|
1657
|
+
|
|
1658
|
+
**Phase 6 (land in turn, hear it now) is built too:**
|
|
1659
|
+
- **A landing train when there's no merge queue.** Land queues the goal in its
|
|
1660
|
+
lanes and merges it on its turn: fresh main in, fast tests, push, green on that
|
|
1661
|
+
commit, then `gh pr merge --squash`. The next goal in the lane lands on top of
|
|
1662
|
+
it. A goal waiting shows **queued**; a red check on its turn hands the lane to
|
|
1663
|
+
the next goal, and it rejoins when green. Repos with a merge queue keep
|
|
1664
|
+
Phase 4's path.
|
|
1665
|
+
- **Lanes.** Path scopes in `loom.team.json`: goals in different lanes land side
|
|
1666
|
+
by side.
|
|
1667
|
+
- **GitHub webhooks, no App needed.** `loom team webhook --install` points a repo
|
|
1668
|
+
webhook at your hub (self-hosted, or the hosted hub's Edge Function). Checks,
|
|
1669
|
+
merges, reviews and deploys reach the team feed as they happen, signed and
|
|
1670
|
+
verified, and your daemon acts on your PR's CI result at once. Polling keeps
|
|
1671
|
+
running; nothing is posted twice.
|
|
1672
|
+
|
|
1673
|
+
```jsonc
|
|
1674
|
+
// loom.team.json — Phase 6 addition
|
|
1675
|
+
{ "landing": { "lanes": { "web": ["web/**"], "api": ["api/**", "db/**"] } } }
|
|
1676
|
+
```
|
|
1677
|
+
|
|
1678
|
+
```bash
|
|
1679
|
+
loom team webhook --install # a repo webhook to the team hub (prints the URL and secret)
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
**How to use it, step by step: [docs/teams.md](docs/teams.md).** The researched
|
|
1683
|
+
architecture (84 decisions, six phases, all built) is in
|
|
1684
|
+
**[docs/teams-architecture.md](docs/teams-architecture.md)**. Every doc is indexed
|
|
1685
|
+
in [docs/README.md](docs/README.md); the code map is [src/README.md](src/README.md).
|
|
1686
|
+
|
|
1687
|
+
## Loom Cloud — your agents from any network
|
|
1688
|
+
|
|
1689
|
+
Tailscale is great when your phone is on your tailnet. **Loom Cloud** reaches your
|
|
1690
|
+
computer from anywhere (mobile data, a café, another country) with nothing to
|
|
1691
|
+
install on the computer's network.
|
|
1692
|
+
|
|
1693
|
+
```bash
|
|
1694
|
+
loom cloud enable --url https://<ref>.supabase.co --key <anon key>
|
|
1695
|
+
loom pair # the QR now works from any network
|
|
1696
|
+
```
|
|
1697
|
+
|
|
1698
|
+
- **End-to-end encrypted.** The daemon and the phone meet on a Supabase Realtime
|
|
1699
|
+
channel. Every message is XChaCha20-Poly1305 with a key that exists only in the
|
|
1700
|
+
pairing QR. Supabase relays ciphertext it cannot read or forge. `loom cloud rotate`
|
|
1701
|
+
mints a new key and locks out every phone that paired with the old one.
|
|
1702
|
+
- **Same auth as the LAN.** Each relayed request becomes a real call against the
|
|
1703
|
+
daemon, carrying the phone's own paired token. Scopes, admin-only routes and
|
|
1704
|
+
revocation behave identically. The local admin bootstrap is never reachable
|
|
1705
|
+
through the relay.
|
|
1706
|
+
- **Direct when it can, cloud when it must.** The phone tries the LAN or tailnet
|
|
1707
|
+
address first and falls back to the relay. It reconnects on its own when you
|
|
1708
|
+
come back to the app.
|
|
1709
|
+
- **Bring your own Supabase project.** Free tier is plenty. Loom never runs a
|
|
1710
|
+
server of its own.
|
|
1711
|
+
|
|
1712
|
+
Setup, including Google sign-in and the country-only usage stats, takes about 10 minutes:
|
|
1713
|
+
[docs/cloud.md](docs/cloud.md). Protocol: [`src/core/relay-protocol.ts`](src/core/relay-protocol.ts).
|
|
1714
|
+
Daemon side: [`src/daemon/relay.ts`](src/daemon/relay.ts).
|
|
1715
|
+
|
|
1716
|
+
## Hardware
|
|
1717
|
+
|
|
1718
|
+
<div align="center">
|
|
1719
|
+
<img src="hardware/orchestrator-pad/docs/images/hero.png" alt="The LoomPad — a 3D-printed macropad with one key per agent and a voice bar" width="720">
|
|
1720
|
+
</div>
|
|
1721
|
+
|
|
1722
|
+
The **LoomPad** is the pad on your desk: one key per agent, a bar you hold to
|
|
1723
|
+
talk, and the answer spoken back through its own speaker. Press a key and it
|
|
1724
|
+
locks that agent in Loom — a real baton handoff, visible in the thread like any
|
|
1725
|
+
other. It talks to the daemon over Wi‑Fi, with USB‑HID as a fallback.
|
|
1726
|
+
|
|
1727
|
+
It is all here, MIT, in [`hardware/orchestrator-pad/`](hardware/orchestrator-pad/README.md):
|
|
1728
|
+
parametric CAD, printable STLs, the wiring diagram, the Arduino firmware and the
|
|
1729
|
+
voice backend. Nothing to buy a licence for and nothing to reverse‑engineer.
|
|
1730
|
+
|
|
1731
|
+
### Print it
|
|
1732
|
+
|
|
1733
|
+
Six parts, all watertight, none needing supports. 0.4 mm nozzle, 0.2 mm layers,
|
|
1734
|
+
PETG or PLA — it prints on a bed‑slinger in an evening.
|
|
1735
|
+
|
|
1736
|
+
| Part | Download | Orientation |
|
|
1737
|
+
|---|---|---|
|
|
1738
|
+
| Tray | [`tray.stl`](hardware/orchestrator-pad/exports/tray.stl) | flat on the bed |
|
|
1739
|
+
| Switch deck | [`switch-deck.stl`](hardware/orchestrator-pad/exports/switch-deck.stl) | flat, sits between tray and plate |
|
|
1740
|
+
| Plate | [`plate.stl`](hardware/orchestrator-pad/exports/plate.stl) | top face down |
|
|
1741
|
+
| Keycaps (all 14) | [`caps-all.stl`](hardware/orchestrator-pad/exports/caps-all.stl) | as oriented — slow the outer walls for crisp glyphs |
|
|
1742
|
+
| Legends | [`legends-all.stl`](hardware/orchestrator-pad/exports/legends-all.stl) | print in the contrast colour |
|
|
1743
|
+
| Knob | [`knob.stl`](hardware/orchestrator-pad/exports/knob.stl) | upright |
|
|
1744
|
+
|
|
1745
|
+
Look before you print: [`orchestrator-pad-assembled.glb`](hardware/orchestrator-pad/exports/orchestrator-pad-assembled.glb)
|
|
1746
|
+
and [`orchestrator-pad-exploded.glb`](hardware/orchestrator-pad/exports/orchestrator-pad-exploded.glb)
|
|
1747
|
+
open in any GLB viewer. Sizes, triangle counts and watertightness are recorded in
|
|
1748
|
+
[`MANIFEST.json`](hardware/orchestrator-pad/exports/MANIFEST.json).
|
|
1749
|
+
|
|
1750
|
+
The enclosure is code, not a binary — `numpy` and `shapely`, no CSG kernel — so
|
|
1751
|
+
you can change the layout and regenerate every STL:
|
|
1752
|
+
|
|
1753
|
+
```bash
|
|
1754
|
+
cd hardware/orchestrator-pad
|
|
1755
|
+
python -m venv .venv && .venv/bin/pip install numpy shapely
|
|
1756
|
+
cd cad && ../.venv/bin/python assembly.py
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
The agents printed on the keycaps are just a table in `cad/partlib.py`. Add your
|
|
1760
|
+
own and reprint the caps.
|
|
1761
|
+
|
|
1762
|
+
### Wire it
|
|
1763
|
+
|
|
1764
|
+
<div align="center">
|
|
1765
|
+
<img src="hardware/orchestrator-pad/docs/images/circuit.png" alt="Wiring diagram — 4x4 key matrix, INMP441 microphone and MAX98357A amplifier on an ESP32-S3" width="920">
|
|
1766
|
+
</div>
|
|
1767
|
+
|
|
1768
|
+
An **ESP32‑S3‑DevKitC‑1** (N16R8), a 4×4 key matrix on pull‑ups with no diodes,
|
|
1769
|
+
an **INMP441** microphone and a **MAX98357A** amplifier on I2S, and the onboard
|
|
1770
|
+
WS2812 as a status light in the locked agent's colour. Every pin is in
|
|
1771
|
+
[`config.h`](hardware/orchestrator-pad/firmware/orchestrator_pad/config.h), and
|
|
1772
|
+
the full table is in the
|
|
1773
|
+
[hardware README](hardware/orchestrator-pad/README.md#wire-it).
|
|
1774
|
+
|
|
1775
|
+
### Flash it
|
|
1776
|
+
|
|
1777
|
+
The sketch is
|
|
1778
|
+
[`orchestrator_pad.ino`](hardware/orchestrator-pad/firmware/orchestrator_pad/orchestrator_pad.ino).
|
|
1779
|
+
You never reflash to change networks — on first boot the pad raises its own
|
|
1780
|
+
access point, **`LoomPad-Setup`**, and a captive page asks for your Wi‑Fi, the
|
|
1781
|
+
backend URL and a pad token, then keeps them in NVS. It checks the daemon's
|
|
1782
|
+
`/health` and speaks "connected" through the amp so you know it is up without
|
|
1783
|
+
opening a terminal.
|
|
1784
|
+
|
|
1785
|
+
```
|
|
1786
|
+
hold 🎤 ──► the pad streams mic audio ──► the daemon does speech-to-text
|
|
1787
|
+
release ──► { agent: "claude-code", prompt: "..." }
|
|
1788
|
+
press ➤ ──► the daemon routes the job to that agent's session
|
|
1789
|
+
✓ / ✕ ──► answer the agent's next approval prompt from the pad
|
|
1790
|
+
```
|
|
1791
|
+
|
|
1792
|
+
The pad stays deliberately dumb: a small JSON protocol over WebSocket, and the
|
|
1793
|
+
daemon owns speech‑to‑text, session routing and CLI orchestration. Point it at
|
|
1794
|
+
your own stack by implementing one message handler.
|
|
1795
|
+
|
|
1796
|
+
## Git policies (all opt-in)
|
|
1797
|
+
|
|
1798
|
+
Four flags in `.loom/config.json → git`, off by default because committing is
|
|
1799
|
+
a policy, not a mechanic (a fifth key, `delivery`, is
|
|
1800
|
+
[its own setting](#git-delivery-commit-push-or-open-a-pr)):
|
|
1801
|
+
|
|
1802
|
+
- **`commitPerTurn`** — each turn's changes become one commit as they land:
|
|
1803
|
+
subject from the prompt, `Co-Authored-By: <agent>` so blame answers "who
|
|
1804
|
+
wrote this" with the agent, staged by exactly the files that turn touched —
|
|
1805
|
+
a bystander's uncommitted mess is never swallowed.
|
|
1806
|
+
- **`worktreePerAgent`** — each adapter works in a sibling checkout on branch
|
|
1807
|
+
`agent/<id>`; parallel edits can't collide in the filesystem. Pair with
|
|
1808
|
+
`commitPerTurn` so the branches actually carry the work; `git merge
|
|
1809
|
+
agent/<id>` is always available.
|
|
1810
|
+
- **`mergeOnHandoff`** — with worktrees on, the baton carries the work: handing
|
|
1811
|
+
from A to B merges `agent/A` into B's checkout, so B starts from what A
|
|
1812
|
+
finished instead of from before A began. It refuses more than it does, and
|
|
1813
|
+
says which: uncommitted work on either side isn't merged (A's isn't on the
|
|
1814
|
+
branch yet; merging onto B's is a state nobody can unpick), and a merge
|
|
1815
|
+
already in progress is never stacked on. A conflict is left in the tree —
|
|
1816
|
+
those files are the thing to resolve — and is named in the incoming
|
|
1817
|
+
briefing, in the handoff event, and stops a route rather than prompting an
|
|
1818
|
+
agent on top of conflict markers.
|
|
1819
|
+
- **`branchPerTask`** — dragging a board card to Working checks out
|
|
1820
|
+
`task/<id>-<slug>` (id first, so retitles don't orphan it; idempotent on
|
|
1821
|
+
re-drag). Reaching Review logs the exact `gh pr create` command instead of
|
|
1822
|
+
running it — pushing publishes, and that stays a human act.
|
|
1823
|
+
|
|
1824
|
+
## Routes that loop, and steps that skip
|
|
1825
|
+
|
|
1826
|
+
A step can carry `onFail` naming an **earlier** step: when it errors, the route
|
|
1827
|
+
re-enters there instead of failing — "review fails → back to execute", as data.
|
|
1828
|
+
Backward-only (a forward jump would skip work), budgeted at three re-entries
|
|
1829
|
+
(a pipeline that never converges should say so, not orbit), and visible in the
|
|
1830
|
+
thread as `route_step {loopedFrom, loop}`. Define and keep pipelines with
|
|
1831
|
+
`loom routes:save <name> planner executor reviewer` — validated against the
|
|
1832
|
+
roster at save, stored in the project config so they travel with the repo.
|
|
1833
|
+
|
|
1834
|
+
A step can also carry a **condition on the previous turn**, and runs only when
|
|
1835
|
+
it holds:
|
|
1836
|
+
|
|
1837
|
+
```bash
|
|
1838
|
+
loom route 'planner,executor,reviewer?lines>200' "tighten the tax rules"
|
|
1839
|
+
```
|
|
1840
|
+
|
|
1841
|
+
Conditions read the turn's own diff and nothing else — `changed>10`,
|
|
1842
|
+
`changed<3`, `lines>200`, `lines<20`, `touched:src/db/**`, `!touched:docs/**` —
|
|
1843
|
+
so a route can send a big change to the reviewer and let a small one go
|
|
1844
|
+
straight on. That bar is deliberate: a condition must be something the daemon
|
|
1845
|
+
*measured*, never a judgement about whether the work was any good; judgement
|
|
1846
|
+
stays with the LLM router. Anything else is refused when the route is defined,
|
|
1847
|
+
not silently never matched at run time, and the first step can't be
|
|
1848
|
+
conditional because there's no turn before it. A skipped step says so in the
|
|
1849
|
+
thread with the numbers that decided it — *step 3/3 → reviewer skipped — needs
|
|
1850
|
+
more than 200 lines changed, the turn changed 1 file, 4 lines* — and skipping
|
|
1851
|
+
is not failing: the route completes. In `.loom/config.json` the same thing is
|
|
1852
|
+
`{ "step": "reviewer", "when": "lines>200" }`.
|
|
1853
|
+
|
|
505
1854
|
## Security model
|
|
506
1855
|
|
|
1856
|
+
**Per-project tokens.** `POST /api/pair/new {"projects": ["<id>"]}` mints a
|
|
1857
|
+
pairing token whose claimed client can touch exactly those projects: everything
|
|
1858
|
+
else 403s (by id or name — the wall resolves names first), the project list
|
|
1859
|
+
filters to its world, and the event/terminal/log streams skip out-of-scope
|
|
1860
|
+
frames. The admin who mints chooses the scope; the device that claims cannot
|
|
1861
|
+
widen it. Clients paired before scoping existed keep the run of the place.
|
|
1862
|
+
|
|
1863
|
+
|
|
507
1864
|
- The daemon binds to `127.0.0.1` by default, or your **Tailscale interface** with
|
|
508
1865
|
`--tailnet` — never `0.0.0.0` on its own. **Connect-a-phone** can *add* a listener on a
|
|
509
1866
|
specific LAN/tailnet IP (never `0.0.0.0`, never an arbitrary host — the target is
|
|
@@ -540,7 +1897,7 @@ buzzes once, not five times). Verify with `loom clients --ping`.
|
|
|
540
1897
|
Add an agent in ~40 lines — implement the contract, register the kind:
|
|
541
1898
|
|
|
542
1899
|
```ts
|
|
543
|
-
import { AdapterBase, registerAgentKind, type SendInput } from "
|
|
1900
|
+
import { AdapterBase, registerAgentKind, type SendInput } from "@loompad/cli/sdk";
|
|
544
1901
|
|
|
545
1902
|
class MyAgentAdapter extends AdapterBase {
|
|
546
1903
|
async available() { return true; }
|
|
@@ -582,15 +1939,61 @@ with its why: [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
|
582
1939
|
}
|
|
583
1940
|
```
|
|
584
1941
|
|
|
585
|
-
Roles
|
|
586
|
-
|
|
1942
|
+
Roles are free text — `planner` · `executor` · `reviewer` · `general` are the ones
|
|
1943
|
+
Loom suggests and routes understand, and your project may invent others. Claude Code
|
|
1944
|
+
options: `permissionMode` (default `acceptEdits`), `model`. OpenCode options:
|
|
587
1945
|
`model` (`"providerID/modelID"`, e.g. `"opencode/minimax-m2.5"` — **set this**: headless
|
|
588
1946
|
sessions don't inherit your TUI default), `agent`, `baseUrl` to reuse a running server.
|
|
1947
|
+
A `model` agent's options are [over here](#agents-that-are-models).
|
|
1948
|
+
|
|
1949
|
+
**Everything else the file can hold**, each explained where it belongs — nothing is
|
|
1950
|
+
required, and the defaults are the behaviour Loom had before the key existed:
|
|
1951
|
+
|
|
1952
|
+
| key | what it turns on |
|
|
1953
|
+
|---|---|
|
|
1954
|
+
| [`git.commitPerTurn` · `worktreePerAgent` · `mergeOnHandoff` · `branchPerTask`](#git-policies-all-opt-in) | committing, isolation, carrying work across the baton, branch per card |
|
|
1955
|
+
| [`git.delivery`](#git-delivery-commit-push-or-open-a-pr) | what happens to finished work: nothing, commit, push, or a PR |
|
|
1956
|
+
| [`brain.extractor` · `brain.model`](#the-brain-sharpened) | who reads finished turns for memory |
|
|
1957
|
+
| [`brain.semantic`](#the-brain-sharpened) | the embedding channel — the one that follows a synonym |
|
|
1958
|
+
| [`budgets.perGoalUsd`](#what-a-goal-may-spend) | what one orchestra goal may spend |
|
|
1959
|
+
| [`maxConcurrentGoals`](#more-than-one-goal-when-they-cant-collide) | goals side by side, when their paths can't collide |
|
|
1960
|
+
| [`safety.snapshotBeforeRoutes`](#routing--multi-hop-pipelines) | checkpoint brain+board+config before a fleet runs unattended |
|
|
1961
|
+
| [`servers`](#the-browser-tab) | the dev servers Loom may run and preview |
|
|
1962
|
+
| [`mcps`](#odds-and-ends-that-pull-their-weight) | MCP servers offered to the agents whose CLI takes them |
|
|
1963
|
+
| [`skills`](#odds-and-ends-that-pull-their-weight) | which `SKILL.md` skills ride along in every briefing |
|
|
1964
|
+
| [`team`](#teams--see-each-others-agents) | which team this project publishes to |
|
|
1965
|
+
|
|
1966
|
+
Keys are **never** in here: a provider's API key lives in `~/.loom/providers.json`
|
|
1967
|
+
(mode 0600) or the environment, because this file is committed.
|
|
1968
|
+
|
|
1969
|
+
## Odds and ends that pull their weight
|
|
1970
|
+
|
|
1971
|
+
- **MCP health** — the daemon polls each project's HTTP MCP servers; a server
|
|
1972
|
+
that stops answering is named in the Console and **withheld from turns**
|
|
1973
|
+
(an absent tool the agent never saw beats a present tool that throws) until
|
|
1974
|
+
it answers again. `loom mcp:health`.
|
|
1975
|
+
- **Terminal scrollback survives restarts** — the pty dies with the daemon
|
|
1976
|
+
(physics), the text doesn't (persisted, seeded back under a divider that
|
|
1977
|
+
says the shell is new). A deliberately closed terminal is forgotten — only
|
|
1978
|
+
daemon death preserves.
|
|
1979
|
+
- **Console search + scope filter** — find the error from *grok* among six
|
|
1980
|
+
agents' noise; the scope menu is built from the records themselves.
|
|
1981
|
+
- **Review comments** — click a line in any diff, type, Enter; comments stage
|
|
1982
|
+
as one `file:line — comment` message in the composer, going to whichever
|
|
1983
|
+
agent you pick. No side channel: baton, budgets and the thread all apply.
|
|
1984
|
+
- **Hold-to-talk** — the composer mic records while held and transcribes via
|
|
1985
|
+
`LOOM_STT_CMD` (whisper.cpp or anything that takes `{file}` and prints
|
|
1986
|
+
text). No cloud STT: no-keys is the contract. Voice fills the box; you still
|
|
1987
|
+
press send.
|
|
1988
|
+
- **Skill authoring** — `POST /api/projects/:id/skills/author` scaffolds
|
|
1989
|
+
`skills/<id>/SKILL.md`, validated by the same parser the roster reads with.
|
|
1990
|
+
- **The spend ledger** — `loom costs --series 14`: one line per day, per-agent
|
|
1991
|
+
split inline, from the same events the budgets enforce against.
|
|
589
1992
|
|
|
590
1993
|
## Development
|
|
591
1994
|
|
|
592
1995
|
```bash
|
|
593
|
-
npm test #
|
|
1996
|
+
npm test # the whole suite (~1,300): unit + full HTTP/WS end-to-end
|
|
594
1997
|
npm run build # tsc → dist/
|
|
595
1998
|
npm run dev # run the CLI from source (tsx)
|
|
596
1999
|
```
|
|
@@ -606,6 +2009,7 @@ npm run dev # run the CLI from source (tsx)
|
|
|
606
2009
|
| `LOOM_NO_NOTIFY` | `1` silences desktop notifications. |
|
|
607
2010
|
| `LOOM_NO_PUSH` | `1` silences phone push. |
|
|
608
2011
|
| `LOOM_ROUTE_STEP_TIMEOUT_MS` | Per-hop route timeout. Default 45 min. |
|
|
2012
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Where telemetry goes. Default `http://localhost:4318`. `DO_NOT_TRACK=1` turns it all off. The rest of the observability variables — the store to read back from, self-heal, trace deep links — are in [docs/observability.md](docs/observability.md). |
|
|
609
2013
|
|
|
610
2014
|
Going the other way, Loom **sets `LOOM_TERMINAL=1`** inside every terminal it opens, so
|
|
611
2015
|
your shell profile can tell it's running in Loom's pane. (`LOOM_EXPO_PUSH_URL` and
|
|
@@ -613,7 +2017,8 @@ your shell profile can tell it's running in Loom's pane. (`LOOM_EXPO_PUSH_URL` a
|
|
|
613
2017
|
|
|
614
2018
|
## Roadmap
|
|
615
2019
|
|
|
616
|
-
- Tasks beyond GitHub —
|
|
2020
|
+
- Tasks beyond GitHub and Linear — the board's source row is GitHub / Projects / Linear
|
|
2021
|
+
today. GitLab is not in it at all; only its brand mark is in the icon set.
|
|
617
2022
|
- More adapters/bridges via the SDK — contributions welcome.
|
|
618
2023
|
|
|
619
2024
|
## Design
|
|
@@ -625,6 +2030,30 @@ Adapted from the [Orca](https://github.com/stablyai/orca) design system (MIT,
|
|
|
625
2030
|
© Lovecast Inc.); the Geist typeface is © Vercel under the SIL Open Font
|
|
626
2031
|
License 1.1. Tokens and rules: [docs/design-system.md](docs/design-system.md).
|
|
627
2032
|
|
|
2033
|
+
The thread is a reading column: replies stream in as they're written (Claude
|
|
2034
|
+
Code, OpenCode, model agents, and every orchestra worker in its task thread),
|
|
2035
|
+
each under a byline with the agent, model and time; tool use folds into one
|
|
2036
|
+
activity line; a turn ends on a footer with its duration and cost; and an
|
|
2037
|
+
orchestrator's plan is a card, not JSON.
|
|
2038
|
+
|
|
2039
|
+
## Credits
|
|
2040
|
+
|
|
2041
|
+
Loom stands on good open-source work. Full license texts are in
|
|
2042
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
2043
|
+
|
|
2044
|
+
- **[Agent Orchestrator](https://github.com/Untrivial-ai/agent-orchestrator)**
|
|
2045
|
+
(Apache-2.0, © Untrivial). Loom adapted its orchestrator/worker model, its
|
|
2046
|
+
"coordinate, never implement" orchestrator rules, its worker rules and its
|
|
2047
|
+
worktree-per-worker isolation. Loom rebuilt the orchestration around a parsed
|
|
2048
|
+
action protocol and a dependency graph, so any agent can orchestrate any mix
|
|
2049
|
+
of workers.
|
|
2050
|
+
- **[T3 Code](https://github.com/pingdotgg/t3code)** (MIT, © T3 Tools Inc.). Loom
|
|
2051
|
+
adapted its mobile connection patterns: the pairing secret in the URL fragment,
|
|
2052
|
+
resuming a stream from a known sequence, and a reconnect supervisor that forces
|
|
2053
|
+
a fresh connection after the app was backgrounded.
|
|
2054
|
+
- **[Orca](https://github.com/stablyai/orca)** (MIT). The design system (see
|
|
2055
|
+
[Design](#design)).
|
|
2056
|
+
|
|
628
2057
|
## License
|
|
629
2058
|
|
|
630
2059
|
MIT © Nivesh Gajengi
|