@desplega.ai/agent-swarm 2.6.0 → 2.7.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/README.md +2 -0
- package/dist/{acp-adapter-dgfakfpw.js → acp-adapter-8dn5tq56.js} +6 -6
- package/dist/{actions-fbqma8aa.js → actions-3r6gw7dy.js} +18 -18
- package/dist/{amp-adapter-en0jy6hs.js → amp-adapter-wjtq413p.js} +3 -3
- package/dist/{anthropic-messages-txs190dd.js → anthropic-messages-n4cby1rz.js} +4 -4
- package/dist/{app-k8a5cgpp.js → app-d53yhykk.js} +3 -3
- package/dist/{assistant-a16433gr.js → assistant-ksrawj3k.js} +20 -20
- package/dist/{boot-reembed-6c1sjfnr.js → boot-reembed-fxfw17m2.js} +11 -3
- package/dist/{boot-reembed-f4nn561n.js → boot-reembed-k5y5xbj0.js} +15 -8
- package/dist/{boot-scrub-sweep-d4w9gajf.js → boot-scrub-sweep-se41j9fd.js} +3 -3
- package/dist/{boot-seeds-9bmkspyw.js → boot-seeds-r3kwwv8b.js} +4 -4
- package/dist/{claude-adapter-nys1kjxa.js → claude-adapter-ahmyf231.js} +8 -8
- package/dist/{claude-managed-adapter-55207973.js → claude-managed-adapter-5knamegc.js} +3 -3
- package/dist/{claude-managed-setup-3cm35gey.js → claude-managed-setup-mdc7dy6b.js} +3 -3
- package/dist/{claude-sdk-session-nw5yd8cp.js → claude-sdk-session-n5wen9zk.js} +8729 -8600
- package/dist/{cli-683y0b3a.js → cli-0dqtv2jg.js} +2 -2
- package/dist/{cli-a34pr3w1.js → cli-0tww7dnz.js} +2 -2
- package/dist/{cli-w2656ka1.js → cli-0w4e4v58.js} +7 -7
- package/dist/{cli-5sckpqcx.js → cli-2sjm3t4r.js} +1 -1
- package/dist/{cli-gf2850q7.js → cli-3cywb44q.js} +18 -1
- package/dist/{cli-8wkb09q5.js → cli-3k9jej0c.js} +2 -2
- package/dist/{cli-3aqd4jaa.js → cli-3v2cyqb8.js} +3 -3
- package/dist/{cli-emhjcj2y.js → cli-5qt9vzmq.js} +89 -20
- package/dist/{cli-abkm71vr.js → cli-5rp1w4cr.js} +1 -1
- package/dist/{cli-gf95q3bm.js → cli-6vwjsetx.js} +4 -4
- package/dist/{cli-k21gx5ht.js → cli-6w2yrrc0.js} +10 -10
- package/dist/{cli-m70bajzr.js → cli-75tdydgv.js} +1 -1
- package/dist/{cli-5rj83bam.js → cli-8feswf2v.js} +47 -36
- package/dist/{cli-sxgtf5fw.js → cli-8wd3mbyj.js} +1 -1
- package/dist/{cli-ca6banwz.js → cli-9csfthbw.js} +10 -8
- package/dist/{cli-dqygeeh5.js → cli-b41p764a.js} +31 -3
- package/dist/{cli-m5a9m0ak.js → cli-c1t533hh.js} +3 -3
- package/dist/{cli-2sjhpsns.js → cli-c28jxpcv.js} +1 -1
- package/dist/{cli-bqztfyve.js → cli-cb50g9cd.js} +1 -1
- package/dist/{cli-ag2yrnt5.js → cli-d8gaphtr.js} +2 -2
- package/dist/{cli-9abdgn5t.js → cli-dgtfysne.js} +3 -3
- package/dist/{cli-nq677csy.js → cli-dscnhn18.js} +1 -1
- package/dist/{cli-3fwqy71j.js → cli-dv11pgpv.js} +39 -32
- package/dist/{cli-v49fkghp.js → cli-ekptgk2m.js} +6 -6
- package/dist/{cli-c2habvsf.js → cli-f3mzy662.js} +17 -17
- package/dist/{cli-zqne1qcj.js → cli-fk7k8s9x.js} +2 -2
- package/dist/{cli-1tem6ns9.js → cli-gt4h8cxw.js} +3 -2
- package/dist/{cli-vbhgkw9h.js → cli-h3e7bhvn.js} +3 -3
- package/dist/{cli-ffvsj2qw.js → cli-hkxqdmnb.js} +3 -3
- package/dist/{cli-a2rr0fmg.js → cli-hpkvgdad.js} +7 -4
- package/dist/{cli-art7c1zh.js → cli-hq2v4rr5.js} +37 -9
- package/dist/{cli-sqma6vw1.js → cli-k1zb7raw.js} +5 -5
- package/dist/{cli-4rvr7zfx.js → cli-k4awgvzm.js} +5 -0
- package/dist/{cli-3ndmpxxv.js → cli-k7d3hvya.js} +2 -2
- package/dist/{cli-3gfvmzht.js → cli-kbtdftbw.js} +74 -26
- package/dist/{cli-hfeb68z4.js → cli-kj69b9tw.js} +34 -6
- package/dist/{cli-az25fn5f.js → cli-kk9jy4nm.js} +2 -2
- package/dist/{cli-39aet655.js → cli-m52t4cta.js} +1 -1
- package/dist/{cli-cz37jqss.js → cli-mdadewqp.js} +5 -5
- package/dist/{cli-esakngh8.js → cli-nf6k60sn.js} +1 -1
- package/dist/{cli-j6rktc91.js → cli-nzdf09js.js} +1 -1
- package/dist/{cli-rr7zx19j.js → cli-prcyzecg.js} +12 -12
- package/dist/{cli-4hxrf0p4.js → cli-pzafyqtk.js} +393 -62
- package/dist/{cli-g8ka5j4j.js → cli-qvaa1gjp.js} +173 -52
- package/dist/{cli-d2dpf95n.js → cli-rg4b2g6j.js} +1 -1
- package/dist/{cli-kf4jree9.js → cli-rm66vqdz.js} +1 -1
- package/dist/{cli-4rf0f5vg.js → cli-s504c3w7.js} +5 -3
- package/dist/{cli-nn6vx2mq.js → cli-swkx9jm2.js} +7 -5
- package/dist/{cli-g20jjwt4.js → cli-thsaq4pd.js} +16 -16
- package/dist/{cli-aj8djyqt.js → cli-twjnyvs8.js} +2 -2
- package/dist/{cli-03gg9pd1.js → cli-typdpe8g.js} +4 -4
- package/dist/{cli-mtnj4fwa.js → cli-vg8pwmsd.js} +6 -6
- package/dist/{cli-gnsb1zxa.js → cli-vhpckzvp.js} +1 -1
- package/dist/{cli-7enjwya9.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-dv97kdzv.js → cli-wdxgsdz5.js} +2 -2
- package/dist/{cli-7a6p4e65.js → cli-wtjg6je3.js} +1 -1
- package/dist/{cli-6y51ryva.js → cli-x3cngb1h.js} +44 -12
- package/dist/{cli-n7asmce6.js → cli-xbz6mcg4.js} +3 -3
- package/dist/{cli-24j1jeeb.js → cli-y4tdxsqb.js} +3 -3
- package/dist/{cli-mvjz5an3.js → cli-yqrsp3ny.js} +7 -7
- package/dist/cli.js +31 -21
- package/dist/{codex-adapter-d95q4qsn.js → codex-adapter-7wepzq15.js} +6 -6
- package/dist/{codex-hook-brpnycn5.js → codex-hook-pbdh4xhx.js} +2 -2
- package/dist/{codex-session-runner-gdwd8h0c.js → codex-session-runner-02sb49e4.js} +6 -6
- package/dist/{commands-jqmgk54b.js → commands-3rcspxct.js} +2 -2
- package/dist/{controller-z3jq589f.js → controller-8ar4avd6.js} +3 -3
- package/dist/{controllers-connect-r2za39ef.js → controllers-connect-ajvh9h0v.js} +19 -19
- package/dist/{cursor-adapter-4mr4jgkw.js → cursor-adapter-gd0sa7bh.js} +1 -1
- package/dist/{db-w36wwb5g.js → db-dv9j7ts5.js} +2 -2
- package/dist/{db-ppv2tze7.js → db-z26vgdvj.js} +3 -3
- package/dist/{dispatcher-pv5h77vf.js → dispatcher-8m0htjq8.js} +17 -17
- package/dist/{dsh-adapter-d4pdpb3r.js → dsh-adapter-y8gyk130.js} +1 -1
- package/dist/{e2b-fn3zacvt.js → e2b-74vg9b7e.js} +1 -1
- package/dist/{e2b-222hsajc.js → e2b-jyay8wzn.js} +1 -1
- package/dist/{easter-egg-3d-4sp4dvmc.js → easter-egg-3d-28f880xx.js} +1 -1
- package/dist/{execute-jev839y1.js → execute-9seb79vh.js} +1 -1
- package/dist/{external-channel-y6nkzhnn.js → external-channel-smz7jyth.js} +2 -2
- package/dist/{getMachineId-bsd-brn0xym3.js → getMachineId-bsd-whfre6gr.js} +3 -3
- package/dist/{getMachineId-darwin-t339sfxm.js → getMachineId-darwin-5zjynb19.js} +3 -3
- package/dist/{getMachineId-linux-5eq95d8b.js → getMachineId-linux-8mksrrsq.js} +3 -3
- package/dist/{getMachineId-unsupported-qavrtc7j.js → getMachineId-unsupported-fxtqsdt2.js} +3 -3
- package/dist/{getMachineId-win-q7fe1wx7.js → getMachineId-win-nzfmz857.js} +3 -3
- package/dist/{grok-adapter-t08b9ntm.js → grok-adapter-z0nnvtbn.js} +7 -7
- package/dist/handlers-pspryh6g.js +41 -0
- package/dist/{heartbeat-vn31xwdk.js → heartbeat-f928aqyp.js} +20 -20
- package/dist/{hook-cpjs8q51.js → hook-d7c605r4.js} +7 -7
- package/dist/{hook-6d0zbvvt.js → hook-q9hv5xf9.js} +5 -5
- package/dist/{http-9c6medda.js → http-q6j4k989.js} +1000 -251
- package/dist/{index-k0yt1tvq.js → index-5nb266jr.js} +17 -17
- package/dist/{index-ysd45kgc.js → index-8fxt5eah.js} +10 -10
- package/dist/{index-zgvqzjsw.js → index-cekzwhez.js} +5 -5
- package/dist/{index-38t35h0n.js → index-ch0c1xff.js} +17 -17
- package/dist/{index-pkma1yf8.js → index-ebnjhxh1.js} +1 -1
- package/dist/{index-yqgbfgaj.js → index-ed8dy8rn.js} +20 -20
- package/dist/{index-qhjf2htm.js → index-ejqdkkqx.js} +262 -96
- package/dist/{index-dr94zbnh.js → index-farm1zsn.js} +32661 -29443
- package/dist/{index-g6k9tctj.js → index-yn1yxg85.js} +1 -1
- package/dist/{keepalive-em0117bp.js → keepalive-zzc3hks1.js} +3 -3
- package/dist/lead-c0pjcaw4.js +79 -0
- package/dist/{maintenance-kbme3qy9.js → maintenance-kqsxzpzx.js} +1 -1
- package/dist/{oauth-refresh-sweep-xysj4yse.js → oauth-refresh-sweep-7qt0g55z.js} +8 -3
- package/dist/{onboard-p6q0vz97.js → onboard-k7x13fqg.js} +121 -8
- package/dist/{opencode-adapter-esv3k9yn.js → opencode-adapter-tqvrtter.js} +5 -5
- package/dist/{otel-impl-54qhqh1z.js → otel-impl-sqkf6k3b.js} +97 -97
- package/dist/{pi-mono-adapter-mymbwhj0.js → pi-mono-adapter-m2qnafxm.js} +89 -14
- package/dist/{pricing-refresh-yb75wv33.js → pricing-refresh-wkpwhgvs.js} +3 -3
- package/dist/{rbac-roles-dexhc9x8.js → rbac-roles-dpn0c0wj.js} +7 -4
- package/dist/{rbac-roles-1vp2mw4p.js → rbac-roles-wk7xd6a6.js} +2 -2
- package/dist/{render-v2-tjay5hhf.js → render-v2-ex2m4g2k.js} +3 -3
- package/dist/{resume-8ayf37g5.js → resume-725g9b0b.js} +7 -7
- package/dist/{runtime-hqe5cqcd.js → runtime-kajas15g.js} +1 -1
- package/dist/{session-access-fdhp987s.js → session-access-0j2ezjje.js} +8 -4
- package/dist/{session-source-backfill-qgtc86z1.js → session-source-backfill-62202awe.js} +2 -2
- package/dist/{setup-4rrmqcq3.js → setup-9sj11ax6.js} +1 -1
- package/dist/{virtual-modules-52km00d6.js → virtual-modules-c6fcq6zr.js} +2 -2
- package/dist/{work-objects-p28fgd70.js → work-objects-0y07495y.js} +20 -20
- package/dist/worker-d4q9sqbc.js +79 -0
- package/openapi.json +1536 -407
- package/package.json +7 -5
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/connector-codes.ts +65 -22
- package/src/be/db/api-keys.ts +9 -0
- package/src/be/db/workflow-runs.ts +36 -5
- package/src/be/db.ts +80 -22
- package/src/be/memory/boot-reembed.ts +4 -1
- package/src/be/memory/constants.ts +6 -0
- package/src/be/memory/embedding-recovery.ts +48 -0
- package/src/be/memory/index-content.ts +5 -2
- package/src/be/memory/providers/openai-embedding.ts +65 -14
- package/src/be/memory/task-learning.ts +257 -0
- package/src/be/memory/types.ts +13 -2
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/migrations/246_connector_code_kind.sql +11 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/onboarding.ts +125 -15
- package/src/be/rbac-roles.ts +4 -1
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +4 -2
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +9 -3
- package/src/be/seed/agent-fs-provision.ts +75 -14
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/cli.tsx +9 -0
- package/src/comb/config.ts +6 -1
- package/src/commands/onboard/agent-fs-version.ts +5 -0
- package/src/commands/onboard/compose-generator.ts +117 -4
- package/src/commands/onboard/env-generator.ts +9 -0
- package/src/commands/onboard/tei-image.ts +12 -0
- package/src/commands/onboard/types.ts +7 -0
- package/src/commands/onboard.tsx +11 -1
- package/src/commands/provider-credentials.ts +2 -1
- package/src/commands/runner.ts +14 -1
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/agentfs-proxy.ts +369 -0
- package/src/http/all-routes.ts +1 -0
- package/src/http/api-keys.ts +24 -9
- package/src/http/codex-oauth-device.ts +8 -1
- package/src/http/fs.ts +64 -10
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/inbox-state.ts +27 -1
- package/src/http/index.ts +7 -4
- package/src/http/memory.ts +7 -4
- package/src/http/onboarding.ts +139 -11
- package/src/http/pages-public.ts +38 -5
- package/src/http/scripts.ts +27 -0
- package/src/http/session-access.ts +24 -0
- package/src/http/sessions.ts +10 -6
- package/src/http/tasks.ts +85 -11
- package/src/http/telemetry-ui.ts +1 -1
- package/src/http/user-roles.ts +4 -1
- package/src/http/users.ts +272 -3
- package/src/http/utils.ts +22 -3
- package/src/http/workflows.ts +10 -0
- package/src/http/x.ts +5 -0
- package/src/prompts/base-prompt.ts +45 -10
- package/src/prompts/session-templates.ts +43 -5
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +6 -1
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +99 -14
- package/src/rbac/legacy-policy.ts +1 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/scheduler.ts +2 -1
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +9 -2
- package/src/scripts-runtime/types/stdlib.d.ts +9 -1
- package/src/scripts-runtime/types/swarm-sdk.d.ts +9 -1
- package/src/tests/agent-fs-provision-seeder.test.ts +261 -92
- package/src/tests/agentfs-proxy.test.ts +1106 -0
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/base-prompt.test.ts +139 -4
- package/src/tests/codex-adapter.test.ts +46 -8
- package/src/tests/codex-oauth-device.test.ts +12 -1
- package/src/tests/comb-config-guard.test.ts +20 -0
- package/src/tests/comb-links.test.ts +79 -1
- package/src/tests/comb-status.test.ts +44 -0
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/credential-check.test.ts +4 -2
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/fixtures/onboard/baseline-states.ts +68 -0
- package/src/tests/fixtures/onboard/content.compose.yml.txt +190 -0
- package/src/tests/fixtures/onboard/content.env.txt +29 -0
- package/src/tests/fixtures/onboard/dev.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/dev.env.txt +28 -0
- package/src/tests/fixtures/onboard/full-all.compose.yml.txt +582 -0
- package/src/tests/fixtures/onboard/full-all.env.txt +59 -0
- package/src/tests/fixtures/onboard/full.compose.yml.txt +435 -0
- package/src/tests/fixtures/onboard/full.env.txt +36 -0
- package/src/tests/fixtures/onboard/research.compose.yml.txt +155 -0
- package/src/tests/fixtures/onboard/research.env.txt +28 -0
- package/src/tests/fixtures/onboard/solo.compose.yml.txt +85 -0
- package/src/tests/fixtures/onboard/solo.env.txt +26 -0
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/http-log-scrubbing.test.ts +34 -1
- package/src/tests/http-task-write-ownership.test.ts +5 -1
- package/src/tests/invites-http.test.ts +464 -0
- package/src/tests/memory-embedding-input-cap.test.ts +122 -1
- package/src/tests/memory-embedding-recovery.test.ts +137 -0
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +162 -0
- package/src/tests/onboard-env.test.ts +24 -0
- package/src/tests/onboard-non-interactive.test.ts +29 -0
- package/src/tests/onboard-tei-image.test.ts +36 -0
- package/src/tests/onboarding.test.ts +451 -4
- package/src/tests/openai-embedding-provider.test.ts +105 -2
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-mono-session-cost-on-abort.test.ts +270 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/preload-env.test.ts +4 -0
- package/src/tests/preload.ts +4 -0
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +20 -3
- package/src/tests/rbac-admission-e2e.test.ts +22 -6
- package/src/tests/rbac-admission.test.ts +342 -4
- package/src/tests/rbac-engine.test.ts +1 -0
- package/src/tests/rbac-roles.test.ts +9 -1
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +3 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/send-task-requester-gate.test.ts +64 -3
- package/src/tests/sessions-move.test.ts +39 -1
- package/src/tests/store-progress-learning.test.ts +381 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/whoami-route.test.ts +41 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/inject-learning.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-search.ts +1 -1
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +71 -1
- package/src/tools/utils.ts +12 -1
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +12 -1
- package/src/utils/constants.ts +39 -0
- package/src/utils/credentials.ts +5 -2
- package/src/utils/task-attachment-links.ts +15 -3
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/dist/handlers-eqsnmv95.js +0 -41
- package/dist/lead-23e7z59t.js +0 -79
- package/dist/worker-e3w7vye1.js +0 -79
- package/dist/{azure-openai-responses-9kfwhmn7.js → azure-openai-responses-43asa23s.js} +0 -0
- package/dist/{node-vxx896fa.js → node-t28cc56b.js} +0 -0
- package/dist/{openai-codex-responses-79s8xnbh.js → openai-codex-responses-jhr3sjtm.js} +3 -3
- package/dist/{openai-completions-tdbyts0x.js → openai-completions-gs6yn37k.js} +3 -3
- /package/dist/{openai-responses-dm3t5vqp.js → openai-responses-jqm9vwwk.js} +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Linear Delivery Loop
|
|
2
|
+
|
|
3
|
+
An Agent Swarm playbook template that moves Linear issues to merged pull requests on a five-minute tick. The docs page is https://docs.agent-swarm.dev/playbooks/linear-delivery-loop.
|
|
4
|
+
|
|
5
|
+
One script, the tick, runs every five minutes. It reads each enrolled Linear project and the pull requests on GitHub, then takes **one action per issue per tick**. The Linear workflow state is the state machine. Agents implement, fix CI and answer review. A reviewer agent approves the exact head commit before a human is pinged. A human decides what starts (Backlog to Todo) and approves the pull request. GitHub merges it, and the Linear GitHub integration sets Done. **The tick never sets Done.**
|
|
6
|
+
|
|
7
|
+
The first project run on these rules is written up in [Software factories are workflows](https://www.pleasedontdeploy.com/p/software-factories-are-workflows).
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
B["Backlog<br/>one pre-flight review"] -- "human releases" --> T["Todo"]
|
|
12
|
+
T -- "dispatch coder" --> IP["In Progress<br/>coder task live"]
|
|
13
|
+
IP -- "PR found" --> CI["Pending CI<br/>PR open, CI runs"]
|
|
14
|
+
CI -- "CI green + agent approved" --> OR["Ready for owner review"]
|
|
15
|
+
OR -- "owner approved, team owed" --> ER["External review required"]
|
|
16
|
+
OR -- "owner approved" --> AP["Approved<br/>auto-merge armed"]
|
|
17
|
+
ER -- "teams covered" --> AP
|
|
18
|
+
AP -- "GitHub merges" --> D["Done<br/>set by the Linear integration"]
|
|
19
|
+
X["Requires human action<br/>parked; a human moves it back"]
|
|
20
|
+
CI -. "escalate" .-> X
|
|
21
|
+
OR -. "escalate" .-> X
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
- **Enrolment is opt-in.** A project joins when a human adds the `delivery-loop` project label. Nothing runs without it.
|
|
27
|
+
- **One action per issue per tick.** The tick reads the issue state, the live agent task under the issue's key, and the pull request, then picks exactly one action: wait, dispatch an agent, move the state, arm auto-merge, or escalate.
|
|
28
|
+
- **Backlog gets one pre-flight review.** A researcher agent comments on each new Backlog issue once. It never changes the state.
|
|
29
|
+
- **Todo waits for blockers.** An open issue that `blocks` this one keeps it in Todo. Otherwise a coder agent starts on the issue's Linear `branchName`.
|
|
30
|
+
- **CI failures go back to the coder.** A merge conflict or a failed required check dispatches a fix task on the same branch.
|
|
31
|
+
- **An agent review gate guards the human.** With CI green, a reviewer agent returns a verdict for the exact head commit. Approved moves the issue to Ready for owner review and pings the owner once per commit. Changes requested goes back to the coder.
|
|
32
|
+
- **Human review is answered, not escalated.** An open review thread, or a human "changes requested" at the head commit, dispatches a coder fix and moves the issue to Applying feedback. With the review gate on, the fixed head goes back through Pending CI, so the agent approves every head a human sees.
|
|
33
|
+
- **Approved arms auto-merge on an agent-approved head only.** Conflicts or a failed run after approval dispatch a merge-queue fix. Before it sends any coder fix (merge-queue, CI, review thread or a human's change request), the tick disables auto-merge if it is armed, so the fix's new head cannot merge on its own. With the review gate on, it never arms that head as is: it moves the issue back to Pending CI, where CI and the reviewer agent run on the new head. The owner's earlier approval still counts unless GitHub dismissed it. GitHub merges.
|
|
34
|
+
- **Escalation parks the issue.** It sets Requires human action, comments the attempt log on the issue, sends the project lead a Slack DM and resets the attempt counters. The tick then ignores the issue until a human moves it back.
|
|
35
|
+
- **A project completes itself.** When every issue is Done, Canceled or Duplicate, the tick marks the project completed and removes the label.
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
CI["Pending CI"] -- "DIRTY: fix conflicts<br/>red: fix ci" --> CI
|
|
40
|
+
CI -- "bot thread or agent asks" --> AF["Applying feedback<br/>coder fix runs"]
|
|
41
|
+
OR["Ready for owner review"] -- "thread open or human asks" --> AF
|
|
42
|
+
ER["External review required"] -- "thread open" --> AF
|
|
43
|
+
AF -- "pushed, or threads resolved" --> CI
|
|
44
|
+
AF -- "no push, thread open: fix again" --> AF
|
|
45
|
+
AP["Approved"] -- "DIRTY or red: fix mq" --> AP
|
|
46
|
+
AP -- "new head, no agent approval:<br/>disarm auto-merge" --> CI
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
One fix runs at a time per issue: a live task under the issue's key blocks a second dispatch.
|
|
50
|
+
|
|
51
|
+
## Agents
|
|
52
|
+
|
|
53
|
+
- **[Lead](https://templates.agent-swarm.dev/official/lead)** — owns setup. Global scripts, script connections and Slack posts need the Lead identity, and the schedule runs as the agent that created it.
|
|
54
|
+
- **[Coder](https://templates.agent-swarm.dev/official/coder)** — one or more. Gets `delivery-implement` and `delivery-fix` tasks, works on the Linear branch name and pushes to the same pull request.
|
|
55
|
+
- **[Reviewer](https://templates.agent-swarm.dev/official/reviewer)** — gets `delivery-review` tasks with an output schema `{verdict, headSha, summary}`. It never pushes.
|
|
56
|
+
- **[Researcher](https://templates.agent-swarm.dev/official/researcher)** — optional. Gets one `delivery-preflight` task per new Backlog issue and comments on the issue.
|
|
57
|
+
|
|
58
|
+
The tick picks agents round-robin from the lists in its config. A reviewer that dies at boot is replaced by the next reviewer in the list.
|
|
59
|
+
|
|
60
|
+
## Tools & Skills
|
|
61
|
+
|
|
62
|
+
### Built-in (ships with agent-swarm)
|
|
63
|
+
|
|
64
|
+
- **Scripts and schedules** — the tick is a global script on a `targetType: "script"` schedule. Scheduled script runs stop at 60 s; the tick stops itself at 25 s and leaves the rest for the next tick.
|
|
65
|
+
- **Script connections** — `ctx.api.linear.graphql` and `ctx.api.github.graphql`. The credential broker injects the token only for the bound host, so no secret enters the script.
|
|
66
|
+
- **Tasks** — `task_send` with `key: shared/linear-delivery-loop/<issue>/`, `task_list` by that key for the live-task check, and `task_get` for the reviewer's verdict.
|
|
67
|
+
- **KV** — namespace `linear-delivery-loop`: `config`, `lock`, `issue/<linearIssueId>`, `rr/<role>` and `errors`.
|
|
68
|
+
- **Slack** — `slack_post` for the owner ping, team review posts, escalation DMs and error alerts.
|
|
69
|
+
|
|
70
|
+
### Community template
|
|
71
|
+
|
|
72
|
+
[`templates/community/linear-delivery-loop/`](https://github.com/desplega-ai/agent-swarm/tree/main/templates/community/linear-delivery-loop):
|
|
73
|
+
|
|
74
|
+
| File | What it is |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `scripts/linear-delivery-tick.ts` | The tick. Pure decision functions plus the I/O shell. Dry run is the default. |
|
|
77
|
+
| `scripts/linear-delivery-setup.ts` | Adds missing workflow states and the project label, and writes a starter config. Idempotent. |
|
|
78
|
+
| `config.example.json` | Every config key with its default. |
|
|
79
|
+
| `schedule.json` | The schedule, in dry run. |
|
|
80
|
+
| `lead-prompt.md` | A prompt that has your Lead do the setup. |
|
|
81
|
+
|
|
82
|
+
## Workflows / Schedules
|
|
83
|
+
|
|
84
|
+
| Schedule | Target | Cron | Args |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `linear-delivery-tick` | script `linear-delivery-tick` | `2-57/5 * * * *` (UTC) | `{ "dryRun": true }`, then `{ "dryRun": false }` |
|
|
87
|
+
|
|
88
|
+
The `2-57/5` offset keeps the tick off the top of the minute, where other schedules cluster.
|
|
89
|
+
|
|
90
|
+
```mermaid
|
|
91
|
+
flowchart TD
|
|
92
|
+
S["Schedule fires<br/>every 5 min"] --> L{"lock taken<br/>under 180 s ago?"}
|
|
93
|
+
L -- "yes" --> STOP["Stop"]
|
|
94
|
+
L -- "no" --> C["Read KV config"]
|
|
95
|
+
C --> P["List projects<br/>label delivery-loop, team"]
|
|
96
|
+
P --> DONE{"every issue Done,<br/>Canceled or Duplicate?"}
|
|
97
|
+
DONE -- "yes" --> COMP["Complete the project<br/>remove the label"]
|
|
98
|
+
DONE -- "no" --> BL["Backlog: one pre-flight review"]
|
|
99
|
+
BL --> ACT["Each active issue:<br/>one action"]
|
|
100
|
+
COMP --> REL["Delete the lock"]
|
|
101
|
+
ACT --> REL
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Setup
|
|
105
|
+
|
|
106
|
+
Every step below uses the REST API with your swarm API key. Steps 1, 2 and 6 need the **Lead** identity: pass the Lead's agent id in `X-Agent-ID`. The schedule then runs the tick as the Lead, which it needs to name agents in `task_send` and to post to Slack.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export SWARM_URL=https://api.example.com # your agent-swarm API
|
|
110
|
+
export API_KEY=... # swarm API key
|
|
111
|
+
export LEAD_ID=... # your Lead's agent id
|
|
112
|
+
H=(-H "Authorization: Bearer $API_KEY" -H "X-Agent-ID: $LEAD_ID" -H "Content-Type: application/json")
|
|
113
|
+
RAW=https://raw.githubusercontent.com/desplega-ai/agent-swarm/main/templates/community/linear-delivery-loop
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**1. Connections (Lead).** Store the two tokens as secrets, then create one GraphQL connection for each. A Linear personal API key goes in the `Authorization` header as is; a GitHub token is a bearer token. The GitHub token needs read access to pull requests and checks, and write access to enable auto-merge.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
put() { curl -s "${H[@]}" -X PUT $SWARM_URL/api/config \
|
|
120
|
+
-d "{\"scope\":\"global\",\"key\":\"$1\",\"value\":\"$2\",\"isSecret\":true}"; }
|
|
121
|
+
put LINEAR_API_KEY "$LINEAR_KEY"
|
|
122
|
+
put GITHUB_TOKEN "$GH_TOKEN"
|
|
123
|
+
|
|
124
|
+
curl -s "${H[@]}" -X POST $SWARM_URL/api/script-connections -d '{
|
|
125
|
+
"kind": "graphql", "slug": "linear", "baseUrl": "https://api.linear.app/graphql",
|
|
126
|
+
"allowedHosts": ["api.linear.app"],
|
|
127
|
+
"auth": { "type": "header", "headerName": "Authorization", "configKey": "LINEAR_API_KEY" } }'
|
|
128
|
+
curl -s "${H[@]}" -X POST $SWARM_URL/api/script-connections -d '{
|
|
129
|
+
"kind": "graphql", "slug": "github", "baseUrl": "https://api.github.com/graphql",
|
|
130
|
+
"allowedHosts": ["api.github.com"],
|
|
131
|
+
"auth": { "type": "bearer", "configKey": "GITHUB_TOKEN" } }'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**2. The scripts (Lead).** Both install at global scope, which a schedule requires.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
for s in linear-delivery-tick linear-delivery-setup; do
|
|
138
|
+
curl -s $RAW/scripts/$s.ts \
|
|
139
|
+
| jq -Rs --arg n "$s" '{name: $n, scope: "global", source: .}' \
|
|
140
|
+
| curl -s "${H[@]}" -X POST $SWARM_URL/api/scripts/upsert --data-binary @-
|
|
141
|
+
done
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**3. The Linear team.** Run setup in dry run and read `wouldCreate`. Then run it for real. It creates the missing states (Pending CI, Ready for owner review, External review required, Applying feedback, Approved, Requires human action) and the `delivery-loop` project label. Creating workflow states may need a Linear admin. If your team already uses other names, skip the creation and map them with `linear.states` in step 4.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
run() { curl -s "${H[@]}" -X POST $SWARM_URL/api/scripts/run \
|
|
148
|
+
-d "{\"name\":\"$1\",\"scope\":\"global\",\"args\":$2}" | jq '.result'; }
|
|
149
|
+
run linear-delivery-setup '{"teamKey":"ENG"}'
|
|
150
|
+
run linear-delivery-setup '{"teamKey":"ENG","dryRun":false}'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**4. The config.** Copy `config.example.json`, fill in your team key, repos, agent ids and Slack ids, then store it in KV.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
curl -s $RAW/config.example.json > config.json # edit it
|
|
157
|
+
jq '{value: .}' config.json \
|
|
158
|
+
| curl -s "${H[@]}" -X PUT $SWARM_URL/api/kv/_/linear-delivery-loop/config --data-binary @-
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**5. A dry run.** Point the tick at one existing project. It reads Linear and GitHub, writes nothing, and returns one planned action per issue. Go on only when `controls.ok` is `true`: a run that read zero issues returns `false`.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
run linear-delivery-tick '{"dryRun":true,"projectIds":["<linear-project-id>"]}' \
|
|
165
|
+
| jq '{controls, budget, plan: [.projects[].issues[] | {key, state, action, reason}]}'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**6. The schedule (Lead).**
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
curl -s $RAW/schedule.json | curl -s "${H[@]}" -X POST $SWARM_URL/api/schedules --data-binary @-
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**7. Go live.** Read a few scheduled dry runs first, then switch.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
ID=$(curl -s "${H[@]}" "$SWARM_URL/api/schedules?name=linear-delivery-tick" | jq -r '.schedules[0].id')
|
|
178
|
+
curl -s "${H[@]}" -X PATCH $SWARM_URL/api/schedules/$ID -d '{"scriptArgs":{"dryRun":false}}'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**8. Enrol a project.** In Linear, add the `delivery-loop` label to the project. Move an issue from Backlog to Todo to release it.
|
|
182
|
+
|
|
183
|
+
## Runner behavior
|
|
184
|
+
|
|
185
|
+
### Per-issue precedence
|
|
186
|
+
|
|
187
|
+
1. **Requires human action** and terminal states: nothing.
|
|
188
|
+
2. **Backlog:** one pre-flight review, then nothing.
|
|
189
|
+
3. **A stuck live task** escalates. Stuck means no update for 30 minutes and older than 120 minutes (240 for a coder task).
|
|
190
|
+
4. **A human change request** at the head commit overrides the state action. The latest trusted review among CHANGES_REQUESTED, APPROVED and DISMISSED decides.
|
|
191
|
+
5. **A merged pull request:** nothing past In Progress. Linear's Done can lag the merge by a tick.
|
|
192
|
+
6. **The state action** (the flowcharts above).
|
|
193
|
+
7. **The dispatch guard:** a live task means wait, a missing `branchName` escalates, and an attempt over its cap escalates.
|
|
194
|
+
|
|
195
|
+
### Trusted reviewers
|
|
196
|
+
|
|
197
|
+
Only a trusted human's review moves an issue or dispatches a coder: a login in `github.ownerLogins` when that list is set, else an owner, member or collaborator of the repo (GitHub `authorAssociation`). The same rule applies to owner approval and to open review threads: a thread counts when any of its comments is by a bot or a trusted human, so an outsider's reply cannot hide it. A thread with more than 30 comments counts without the check. Anyone can review a pull request on a public repo, so an outsider's approval never arms auto-merge and an outsider's comment never reaches a coder. The fix prompt quotes review text in a block that the coder treats as data, not instructions.
|
|
198
|
+
|
|
199
|
+
### Pull request lookup
|
|
200
|
+
|
|
201
|
+
1. An open or merged PR whose head branch equals the issue's `branchName`, in any configured repo.
|
|
202
|
+
2. Else, the issue key as a whole word in the head branch, within the last 300 PRs per repo. `ENG-12` never matches `ENG-123`.
|
|
203
|
+
|
|
204
|
+
A closed, unmerged PR never counts. Two matches count as none.
|
|
205
|
+
|
|
206
|
+
### CI
|
|
207
|
+
|
|
208
|
+
The tick reads the head commit's check rollup and keeps the checks GitHub marks as required for the PR. Name them in `github.requiredChecks` if the repo has no branch protection. `github.ignoredChecks` drops checks such as an approval gate. With no required check reported, a commit younger than 30 minutes waits; an older one waits and counts, and the third such tick escalates.
|
|
209
|
+
|
|
210
|
+
### Caps
|
|
211
|
+
|
|
212
|
+
| Cap | Default | At the cap |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| Implement dispatches per issue | 5 | escalate |
|
|
215
|
+
| CI fixes (conflicts and failed checks) per issue | 5 | escalate |
|
|
216
|
+
| Review fixes per issue | 5 | escalate |
|
|
217
|
+
| Merge-queue fixes per issue | 5 | escalate |
|
|
218
|
+
| Ticks with no CI reported | 3 | escalate |
|
|
219
|
+
| Attempts in total per issue | 25 | escalate |
|
|
220
|
+
| Reviewer re-dispatches after a death at boot | 2 | escalate |
|
|
221
|
+
| Infra failures per issue | 5 | escalate |
|
|
222
|
+
| Implement dispatches per tick | 2 | the rest wait for the next tick |
|
|
223
|
+
| Fix dispatches per tick | 3 | the rest wait |
|
|
224
|
+
| Reviewer and pre-flight dispatches per tick | 3 | the rest wait |
|
|
225
|
+
| Slack posts per tick | 5 | the rest wait |
|
|
226
|
+
| Wall clock per tick | 25 s | the rest wait |
|
|
227
|
+
| Lock | 180 s | a second tick stops |
|
|
228
|
+
|
|
229
|
+
Override any of them under `caps` in the config, for example `{"caps": {"implement": 3, "perTick": {"fix": 5}}}`.
|
|
230
|
+
|
|
231
|
+
### Infra failures
|
|
232
|
+
|
|
233
|
+
Tasks fail for reasons that have nothing to do with the work: provider auth errors, exhausted credits, rate limits, worker restarts. When the tick sees that its last task failed, it classifies the failure. A cancelled or superseded task, a failure reason that matches `INFRA_FAILURE_PATTERNS`, or a failure within 3 minutes with no progress is **infra**. An infra failure gives back the attempt it spent, both the kind and the total, and counts toward the infra cap. Any other failure keeps the spend. HTTP codes count only next to `HTTP` or `status`, so "512 tests failed" is a real failure.
|
|
234
|
+
|
|
235
|
+
### The lock
|
|
236
|
+
|
|
237
|
+
The tick takes the lock with an atomic `kv_incr` on `lock`. The first caller gets 1 and runs; it deletes the key when it ends. A later caller reads the lock row's `createdAt`, which `kv_incr` never moves. If the lock is older than 180 s (3 minutes), its owner died, so the caller deletes it and stops; the next tick acquires cleanly. The lock has no TTL, because a re-created expired row would keep its old `createdAt`.
|
|
238
|
+
|
|
239
|
+
### Output
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"dryRun": true,
|
|
244
|
+
"controls": { "ok": true, "projectsRead": 1, "issuesRead": 9, "prsRead": 120, "errors": [] },
|
|
245
|
+
"projects": [{ "id": "…", "name": "…", "issues": [
|
|
246
|
+
{ "key": "ENG-12", "state": "pendingCi", "action": "dispatch", "kind": "ci",
|
|
247
|
+
"reason": "required check failed: test", "deferred": false }
|
|
248
|
+
] }],
|
|
249
|
+
"budget": { "implement": 0, "fix": 1, "reviewer": 0, "slack": 0, "elapsedMs": 4120 }
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`controls.ok` is `false` when the run read zero issues or any adapter failed. In live mode, adapter errors go to the `errors` KV record and to the `slack.errors` channel, once per error streak.
|
|
254
|
+
|
|
255
|
+
## Results from a first project
|
|
256
|
+
|
|
257
|
+
The full story is in [Software factories are workflows](https://www.pleasedontdeploy.com/p/software-factories-are-workflows). The project ran with a partner: a software company with more than 80 employees and a B2B product more than 4 years old. The feature was scoped as a two-week sprint for two people: 14 non-trivial issues across multiple repos, including sensitive data and operations areas. One human owner reviewed every key pull request and decision. The template is a fresh implementation of the same rules.
|
|
258
|
+
|
|
259
|
+
| Metric | Value |
|
|
260
|
+
|---|---|
|
|
261
|
+
| Issues closed | 14 of 14 |
|
|
262
|
+
| Cycle | 7 days |
|
|
263
|
+
| Automated iterations (tick dispatches) | 103 |
|
|
264
|
+
| Agent tasks behind them | 640: 44 failed, 30 superseded |
|
|
265
|
+
| Human reviews | 43: 14 approved, 21 changes requested |
|
|
266
|
+
| Owner feedback | about 15 minutes when asked during working hours |
|
|
267
|
+
| Agent fix after a change request | pushed a median 16 min later |
|
|
268
|
+
| Runaway loops | none |
|
|
269
|
+
|
|
270
|
+
```mermaid
|
|
271
|
+
pie showData
|
|
272
|
+
title Issue-hours by bucket (1,112.8 h)
|
|
273
|
+
"Waiting on a blocking issue" : 753.9
|
|
274
|
+
"Parked in Requires human action" : 207.5
|
|
275
|
+
"Agents active" : 82.2
|
|
276
|
+
"Waiting for the code owner" : 61.2
|
|
277
|
+
"Waiting on CI" : 8.0
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Of about 1,100 issue-hours, agents worked about 80. The rest was mostly waiting for people or for CI, directly or behind a blocking issue that was itself waiting. Issues 1 to 11 were released at once as a stacked sequence with `blocks` relations, and the system created smaller sub-issues along the way. When agents were active, they usually worked on about 14 issues in parallel.
|
|
281
|
+
|
|
282
|
+
### Lessons
|
|
283
|
+
|
|
284
|
+
- **Infrastructure caused most task failures.** That grows as the loop speeds up, so the team needs room to scale its infrastructure. Every failed task cost an attempt in that run; the infra classifier above came from it.
|
|
285
|
+
- **Idempotency needs proof.** Overloading the system produced repeated tasks, broken states and unnecessary iterations. Modelling the task states in TLA+ fixed that. This template does not ship a TLA+ model. It keeps one action per issue per tick, one live task per issue, one lock per tick, and one owner ping per head commit.
|
|
286
|
+
- **Budgets counter AI slop.** Agents ignored issues or posted fake progress, and some avoided implementing a hard fix. The caps turn that into an escalation to a human instead of a loop.
|
|
287
|
+
|
|
288
|
+
### Setup notes
|
|
289
|
+
|
|
290
|
+
- **A dashboard.** The first project used a custom view of the state machine for debugging and for following the project. The template ships none: the tick's output and the `issue/<id>` KV records are the data to build one on.
|
|
291
|
+
- **Deterministic validation.** Scripts, CI checks and litmus gates meant a human was pinged only when the work was ready. Here, that is the required checks and the agent review gate.
|
|
292
|
+
- **Only selected projects.** Choosing the project was the main team discussion: complex enough to matter, not too complicated for a first run. The `delivery-loop` label keeps enrolment a human choice.
|
|
293
|
+
|
|
294
|
+
## Limits
|
|
295
|
+
|
|
296
|
+
- **The tick never sets Done.** GitHub merges and the Linear GitHub integration closes the issue. Without that integration, issues stay in Approved.
|
|
297
|
+
- **Tested scope.** The decision logic, the lock, escalation, project completion and the setup script are covered by fixture tests. A read-only dry run ran against a real Linear project and GitHub repo. The setup script's Linear writes and live mode are tested against fixtures only.
|
|
298
|
+
- **Scheduled runs stop at 60 s.** The tick budgets 25 s and defers the remaining issues to the next tick. The write-up describes a 5-minute maximum per loop, the tick interval; here, one tick stops at 25 s, and the stuck rule bounds each agent task.
|
|
299
|
+
- **KV has no compare-and-set.** Only the lock owner writes, and each issue has its own key. A stale-lock clear and a fresh acquire can still race inside one request round trip after a crash.
|
|
300
|
+
- **One reviewer gate per head commit.** A new push needs a new verdict, including a merge-queue fix pushed after approval.
|
|
301
|
+
- **Changed from the original rules:**
|
|
302
|
+
|
|
303
|
+
| Original behaviour | Here |
|
|
304
|
+
|---|---|
|
|
305
|
+
| A merge-queue fix carries recent messages from a merge-queue status channel | It carries `mergeStateStatus` and the failed required checks. Reading a Slack channel needs the Lead. |
|
|
306
|
+
| A shared agent-picker script | Round-robin over `agents.coders`, `agents.reviewers` and `agents.researchers`. |
|
|
307
|
+
| The human reviewer must be a Linear project member | `github.ownerLogins`, or the repo's owners, members and collaborators when that list is empty. |
|
|
308
|
+
| A named approval-gate job is dropped from failed jobs | Generic `github.ignoredChecks`. |
|
|
309
|
+
| CI read from the Actions run named after the PR | The head commit's check rollup, required checks only. |
|
|
310
|
+
| Separate error-log and alert keys | One `errors` record and one Slack alert per error streak. |
|
|
311
|
+
| No infra vs real failure split | Infra failures refund their attempt, capped at 5. |
|
|
312
|
+
|
|
313
|
+
## Patterns used
|
|
314
|
+
|
|
315
|
+
- **[Drain Loops](https://docs.agent-swarm.dev/playbooks/patterns/drain-loops)** — the tick drains one project issue by issue, one action at a time, until the merge loop empties it.
|
|
316
|
+
- **[HITL Gates](https://docs.agent-swarm.dev/playbooks/patterns/hitl-gates)** — a human releases work, approves each PR and handles every escalation.
|
|
317
|
+
|
|
318
|
+
## References
|
|
319
|
+
|
|
320
|
+
- [Template package](https://github.com/desplega-ai/agent-swarm/tree/main/templates/community/linear-delivery-loop)
|
|
321
|
+
- [Linear GraphQL API](https://linear.app/developers/graphql)
|
|
322
|
+
- [GitHub GraphQL: `enablePullRequestAutoMerge`](https://docs.github.com/en/graphql/reference/mutations#enablepullrequestautomerge)
|
|
323
|
+
- [GitHub GraphQL: `isRequired` on check runs](https://docs.github.com/en/graphql/reference/objects#checkrun)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Linear Delivery Loop
|
|
2
|
+
|
|
3
|
+
One cron script, the tick, moves each issue of an enrolled Linear project one step every five minutes: agents implement, fix CI and answer review, a reviewer agent approves the head commit, and a human approves before GitHub merges. Full guide: [PLAYBOOK.md](./PLAYBOOK.md) or https://docs.agent-swarm.dev/playbooks/linear-delivery-loop.
|
|
4
|
+
|
|
5
|
+
The first project run on these rules, 14 issues closed in 7 days, is written up in [Software factories are workflows](https://www.pleasedontdeploy.com/p/software-factories-are-workflows).
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
| File | What it is |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `scripts/linear-delivery-tick.ts` | The tick. Dry run is the default. |
|
|
12
|
+
| `scripts/linear-delivery-setup.ts` | Adds the missing Linear workflow states and the `delivery-loop` project label, and writes a starter config. Idempotent. |
|
|
13
|
+
| `config.example.json` | Every config key. Store your copy in KV `linear-delivery-loop/config`. |
|
|
14
|
+
| `schedule.json` | The schedule: cron `2-57/5 * * * *`, UTC, `dryRun: true`. |
|
|
15
|
+
| `lead-prompt.md` | A prompt that has your Lead do the setup. |
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Steps 1, 2 and 6 need the Lead identity (`X-Agent-ID` of your Lead).
|
|
20
|
+
|
|
21
|
+
1. Store `LINEAR_API_KEY` and `GITHUB_TOKEN` as secrets and create the `linear` and `github` GraphQL script connections.
|
|
22
|
+
2. Upsert both scripts at global scope (`POST /api/scripts/upsert`).
|
|
23
|
+
3. Run `linear-delivery-setup` with `{"teamKey":"ENG"}`, read `wouldCreate`, then run it with `"dryRun": false`.
|
|
24
|
+
4. Fill in `config.example.json` and `PUT /api/kv/_/linear-delivery-loop/config` with `{"value": <config>}`.
|
|
25
|
+
5. Run `linear-delivery-tick` with `{"dryRun": true, "projectIds": ["<id>"]}` and check `controls.ok`.
|
|
26
|
+
6. `POST /api/schedules` with `schedule.json`.
|
|
27
|
+
7. `PATCH` the schedule's `scriptArgs` to `{"dryRun": false}`.
|
|
28
|
+
8. Add the `delivery-loop` label to a Linear project. Move issues from Backlog to Todo to release them.
|
|
29
|
+
|
|
30
|
+
The exact curl commands are in [PLAYBOOK.md](./PLAYBOOK.md#setup).
|
|
31
|
+
|
|
32
|
+
## Tests
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bun run test:root -- src/tests/community-linear-delivery-loop.test.ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The tests cover every row of the state table, each cap, the lock, the PR lookup, the failure classifier, escalation, project completion, the setup script, and the `script-upsert` typecheck of both scripts.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"linear": {
|
|
3
|
+
"connection": "linear",
|
|
4
|
+
"teamKey": "ENG",
|
|
5
|
+
"projectLabel": "delivery-loop",
|
|
6
|
+
"states": {}
|
|
7
|
+
},
|
|
8
|
+
"github": {
|
|
9
|
+
"connection": "github",
|
|
10
|
+
"repos": ["acme/app"],
|
|
11
|
+
"botLogins": ["github-actions[bot]"],
|
|
12
|
+
"ownerLogins": [],
|
|
13
|
+
"ignoredChecks": [],
|
|
14
|
+
"requiredChecks": [],
|
|
15
|
+
"mergeMethod": "SQUASH"
|
|
16
|
+
},
|
|
17
|
+
"agents": {
|
|
18
|
+
"coders": ["<coder-agent-id>"],
|
|
19
|
+
"reviewers": ["<reviewer-agent-id>"],
|
|
20
|
+
"researchers": ["<researcher-agent-id>"]
|
|
21
|
+
},
|
|
22
|
+
"slack": {
|
|
23
|
+
"ownerPing": { "default": "<channel-id>", "byProject": {} },
|
|
24
|
+
"escalation": { "default": "<slack-user-or-channel-id>", "byProjectLead": {} },
|
|
25
|
+
"teamChannels": { "default": "<channel-id>", "byTeam": {} },
|
|
26
|
+
"errors": "<channel-id>"
|
|
27
|
+
},
|
|
28
|
+
"reviewGate": true,
|
|
29
|
+
"backlogPreflight": true,
|
|
30
|
+
"caps": {}
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Lead Kickoff Prompt
|
|
2
|
+
|
|
3
|
+
Copy this into your agent-swarm Lead to set up the Linear Delivery Loop.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Set up the Linear Delivery Loop from templates/community/linear-delivery-loop in the
|
|
7
|
+
desplega-ai/agent-swarm repo (main branch). Follow its PLAYBOOK.md "Setup" section.
|
|
8
|
+
|
|
9
|
+
Ask me first for anything below that is missing:
|
|
10
|
+
- Linear team key, and whether my team uses other names for any of the loop's states
|
|
11
|
+
(map them in config.linear.states).
|
|
12
|
+
- GitHub repos (owner/name) the issues' pull requests live in, and the login of any
|
|
13
|
+
review bot whose threads the coder should answer (github.botLogins).
|
|
14
|
+
- Optional: the GitHub logins whose reviews count as the owner's (github.ownerLogins).
|
|
15
|
+
Empty means the repo's owners, members and collaborators.
|
|
16
|
+
- Agent ids for coders, reviewers and researchers.
|
|
17
|
+
- Slack ids: the owner-ping channel, the escalation target (a user id DMs that user),
|
|
18
|
+
the team-review channel and the error channel.
|
|
19
|
+
- Whether the Linear and GitHub tokens are already stored as swarm secrets.
|
|
20
|
+
|
|
21
|
+
Then, as the Lead:
|
|
22
|
+
1. Create the `linear` and `github` GraphQL script connections bound to api.linear.app and
|
|
23
|
+
api.github.com. Never put a token in a script, a task or a message.
|
|
24
|
+
2. Upsert linear-delivery-tick and linear-delivery-setup at global scope from the template.
|
|
25
|
+
3. Run linear-delivery-setup with dryRun true and show me `wouldCreate`. Run it with
|
|
26
|
+
dryRun false only after I say yes.
|
|
27
|
+
4. Store the config in KV namespace linear-delivery-loop, key config.
|
|
28
|
+
5. Run linear-delivery-tick with dryRun true on one project I name. Show me controls, budget
|
|
29
|
+
and the planned action per issue. Stop if controls.ok is false and tell me why.
|
|
30
|
+
6. Create the schedule from schedule.json (it starts in dry run).
|
|
31
|
+
|
|
32
|
+
Stop before switching the schedule to dryRun false. I will read a few scheduled dry runs and
|
|
33
|
+
give an explicit yes first. Enrolling a project (the delivery-loop label) stays my call.
|
|
34
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linear-delivery-tick",
|
|
3
|
+
"description": "Linear Delivery Loop: one action per enrolled issue every 5 minutes",
|
|
4
|
+
"targetType": "script",
|
|
5
|
+
"scriptName": "linear-delivery-tick",
|
|
6
|
+
"scriptArgs": { "dryRun": true },
|
|
7
|
+
"cronExpression": "2-57/5 * * * *",
|
|
8
|
+
"timezone": "UTC",
|
|
9
|
+
"enabled": true
|
|
10
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* linear-delivery-setup: prepare a Linear team for the Linear Delivery Loop.
|
|
3
|
+
*
|
|
4
|
+
* Creates each missing workflow state and the opt-in project label, and writes
|
|
5
|
+
* a starter config to KV when none exists. Idempotent: a second run creates
|
|
6
|
+
* nothing. Dry run is the default and only reports what it would create.
|
|
7
|
+
*/
|
|
8
|
+
import type { ScriptContext } from "swarm-sdk";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
const NAMESPACE = "linear-delivery-loop";
|
|
12
|
+
|
|
13
|
+
/** Same names and keys as DEFAULT_STATES in linear-delivery-tick. */
|
|
14
|
+
export const STATES: Array<{ key: string; name: string; type: string; color: string }> = [
|
|
15
|
+
{ key: "backlog", name: "Backlog", type: "backlog", color: "#bec2c8" },
|
|
16
|
+
{ key: "todo", name: "Todo", type: "unstarted", color: "#e2e2e2" },
|
|
17
|
+
{ key: "inProgress", name: "In Progress", type: "started", color: "#f2c94c" },
|
|
18
|
+
{ key: "pendingCi", name: "Pending CI", type: "started", color: "#f2994a" },
|
|
19
|
+
{ key: "ownerReview", name: "Ready for owner review", type: "started", color: "#5e6ad2" },
|
|
20
|
+
{ key: "externalReview", name: "External review required", type: "started", color: "#9b51e0" },
|
|
21
|
+
{ key: "applyingFeedback", name: "Applying feedback", type: "started", color: "#eb5757" },
|
|
22
|
+
{ key: "approved", name: "Approved", type: "started", color: "#27ae60" },
|
|
23
|
+
{ key: "humanAction", name: "Requires human action", type: "started", color: "#4f4f4f" },
|
|
24
|
+
{ key: "done", name: "Done", type: "completed", color: "#5e6ad2" },
|
|
25
|
+
{ key: "canceled", name: "Canceled", type: "canceled", color: "#95a2b3" },
|
|
26
|
+
{ key: "duplicate", name: "Duplicate", type: "duplicate", color: "#95a2b3" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const argsSchema = z.object({
|
|
30
|
+
teamKey: z.string().min(1).describe("Linear team key, e.g. ENG"),
|
|
31
|
+
dryRun: z.boolean().optional().describe("Default true. Pass false to create."),
|
|
32
|
+
connection: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Linear GraphQL connection slug (default linear)"),
|
|
37
|
+
projectLabel: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Opt-in project label (default delivery-loop)"),
|
|
42
|
+
states: z.record(z.string(), z.string()).optional().describe("State name overrides by key"),
|
|
43
|
+
config: z
|
|
44
|
+
.record(z.string(), z.unknown())
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Starter config written when none exists"),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type GraphqlClient = {
|
|
50
|
+
graphql(query: string, variables?: Record<string, unknown>): Promise<unknown>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nodes(value: unknown, ...path: string[]): Record<string, unknown>[] {
|
|
58
|
+
let cur: unknown = value;
|
|
59
|
+
for (const part of [...path, "nodes"]) cur = isRecord(cur) ? cur[part] : undefined;
|
|
60
|
+
return Array.isArray(cur) ? cur.filter(isRecord) : [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default async function linearDeliverySetup(
|
|
64
|
+
args: z.input<typeof argsSchema>,
|
|
65
|
+
ctx: ScriptContext,
|
|
66
|
+
) {
|
|
67
|
+
const parsed = argsSchema.safeParse(args ?? {});
|
|
68
|
+
if (!parsed.success) return { ok: false, error: `invalid args: ${parsed.error.message}` };
|
|
69
|
+
const a = parsed.data;
|
|
70
|
+
const dryRun = a.dryRun !== false;
|
|
71
|
+
const slug = a.connection ?? "linear";
|
|
72
|
+
const label = a.projectLabel ?? "delivery-loop";
|
|
73
|
+
const linear = (ctx.api as unknown as Record<string, GraphqlClient | undefined>)[slug];
|
|
74
|
+
if (!linear || typeof linear.graphql !== "function") {
|
|
75
|
+
return { ok: false, error: `connection '${slug}' is missing or is not a GraphQL connection` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const created: string[] = [];
|
|
79
|
+
const existing: string[] = [];
|
|
80
|
+
const wouldCreate: string[] = [];
|
|
81
|
+
|
|
82
|
+
const teams = await linear.graphql(
|
|
83
|
+
`query LdlSetupTeam($team: String!) { teams(filter: { key: { eq: $team } }) { nodes { id states(first: 100) { nodes { id name type } } } } }`,
|
|
84
|
+
{ team: a.teamKey },
|
|
85
|
+
);
|
|
86
|
+
const team = nodes(teams, "teams")[0];
|
|
87
|
+
if (!team) return { ok: false, error: `team ${a.teamKey} not found or not visible` };
|
|
88
|
+
const have = new Set(nodes(team, "states").map((s) => String(s.name).toLowerCase()));
|
|
89
|
+
|
|
90
|
+
for (const state of STATES) {
|
|
91
|
+
const name = a.states?.[state.key] ?? state.name;
|
|
92
|
+
const item = `state: ${name}`;
|
|
93
|
+
if (have.has(name.toLowerCase())) {
|
|
94
|
+
existing.push(item);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (dryRun) {
|
|
98
|
+
wouldCreate.push(item);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
await linear.graphql(
|
|
102
|
+
`mutation LdlSetupState($input: WorkflowStateCreateInput!) { workflowStateCreate(input: $input) { success } }`,
|
|
103
|
+
{ input: { teamId: team.id, name, type: state.type, color: state.color } },
|
|
104
|
+
);
|
|
105
|
+
created.push(item);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const labels = await linear.graphql(
|
|
109
|
+
`query LdlSetupLabel($name: String!) { projectLabels(filter: { name: { eq: $name } }) { nodes { id name } } }`,
|
|
110
|
+
{ name: label },
|
|
111
|
+
);
|
|
112
|
+
const labelItem = `project label: ${label}`;
|
|
113
|
+
if (nodes(labels, "projectLabels").length > 0) existing.push(labelItem);
|
|
114
|
+
else if (dryRun) wouldCreate.push(labelItem);
|
|
115
|
+
else {
|
|
116
|
+
await linear.graphql(
|
|
117
|
+
`mutation LdlSetupLabelCreate($name: String!) { projectLabelCreate(input: { name: $name }) { success } }`,
|
|
118
|
+
{ name: label },
|
|
119
|
+
);
|
|
120
|
+
created.push(labelItem);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const configItem = `kv: ${NAMESPACE}/config`;
|
|
124
|
+
const stored = await ctx.swarm.kv_get({ namespace: NAMESPACE, key: "config" });
|
|
125
|
+
if (stored.success) existing.push(configItem);
|
|
126
|
+
else if (dryRun) wouldCreate.push(configItem);
|
|
127
|
+
else {
|
|
128
|
+
const starter = a.config ?? {};
|
|
129
|
+
const linearStarter = isRecord(starter.linear) ? starter.linear : {};
|
|
130
|
+
await ctx.swarm.kv_set({
|
|
131
|
+
namespace: NAMESPACE,
|
|
132
|
+
key: "config",
|
|
133
|
+
value: {
|
|
134
|
+
...starter,
|
|
135
|
+
linear: {
|
|
136
|
+
connection: slug,
|
|
137
|
+
projectLabel: label,
|
|
138
|
+
...(a.states ? { states: a.states } : {}),
|
|
139
|
+
...linearStarter,
|
|
140
|
+
teamKey: a.teamKey,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
created.push(configItem);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { ok: true, dryRun, teamKey: a.teamKey, created, existing, wouldCreate };
|
|
148
|
+
}
|