@desplega.ai/agent-swarm 1.131.0 → 1.133.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.
Files changed (265) hide show
  1. package/README.md +2 -2
  2. package/dist/{actions-w53mbns9.js → actions-cskpayd7.js} +11 -9
  3. package/dist/{anthropic-messages-rjmd2e7n.js → anthropic-messages-tpxtbx7b.js} +28 -12
  4. package/dist/{app-m9gwqsqv.js → app-ang77nbb.js} +8 -6
  5. package/dist/{assistant-bp65m6f3.js → assistant-b4exebdw.js} +16 -13
  6. package/dist/{azure-openai-responses-c0yjmbfd.js → azure-openai-responses-m3921t4y.js} +8 -8
  7. package/dist/{boot-reembed-62p0at2k.js → boot-reembed-ps2aftpb.js} +9 -7
  8. package/dist/{boot-reembed-xmfp6wdh.js → boot-reembed-znyk3nxb.js} +10 -8
  9. package/dist/{boot-scrub-logs-rr8q2dms.js → boot-scrub-logs-2ax3xgz1.js} +7 -5
  10. package/dist/{claude-adapter-2y47mrkd.js → claude-adapter-3ct8kgxw.js} +13 -9
  11. package/dist/{claude-managed-adapter-gv19tdew.js → claude-managed-adapter-akx4ntf2.js} +2 -2
  12. package/dist/{cli-t1xze5qh.js → cli-13w2hwkh.js} +1 -1
  13. package/dist/{cli-efxfnq6q.js → cli-1532szb1.js} +8 -6
  14. package/dist/{cli-d2tfkcga.js → cli-23d3rseb.js} +33 -17
  15. package/dist/{cli-nm5gtp1x.js → cli-2a75z9c5.js} +196 -7
  16. package/dist/{cli-fywms69x.js → cli-2t30rj0d.js} +1 -1
  17. package/dist/{cli-cvgjwnh1.js → cli-3c3tdgab.js} +10 -10
  18. package/dist/{cli-pm6xbh84.js → cli-3zzxa9jz.js} +46 -16
  19. package/dist/{cli-cdeg08wx.js → cli-4k6233gc.js} +1 -1
  20. package/dist/{cli-t55yw3a1.js → cli-5p2tz05r.js} +2 -2
  21. package/dist/{cli-0rcyq6d9.js → cli-5z2yhzvd.js} +125 -47
  22. package/dist/{cli-3kf51j15.js → cli-6zfq31nj.js} +9 -3
  23. package/dist/{cli-er3a4bb0.js → cli-8m9de10a.js} +6 -1
  24. package/dist/{cli-gm9cbhbq.js → cli-8t14vg9w.js} +27 -8
  25. package/dist/{cli-72q8m8gk.js → cli-9nfc2es8.js} +6 -6
  26. package/dist/{cli-5ze9rv97.js → cli-9ta3pwms.js} +11 -4
  27. package/dist/{cli-78xwvkhj.js → cli-avr0ns7z.js} +2 -2
  28. package/dist/{cli-n6h8qt8n.js → cli-c897wyhw.js} +12 -7
  29. package/dist/{cli-2q72qz2s.js → cli-dgk20ft7.js} +15 -6
  30. package/dist/cli-et1bbwqf.js +13 -0
  31. package/dist/{cli-bqw0mt1n.js → cli-f5w27pj3.js} +3 -3
  32. package/dist/{cli-2wadx7p2.js → cli-f8dre4bz.js} +2 -2
  33. package/dist/{cli-e7ckcg4v.js → cli-ftwpv451.js} +170 -387
  34. package/dist/{cli-g6b8x1h8.js → cli-g854s2dw.js} +1 -1
  35. package/dist/{cli-5kx36evn.js → cli-ggmdxeqa.js} +13 -8
  36. package/dist/cli-hkwfe3qj.js +530 -0
  37. package/dist/{cli-5dsbcjjw.js → cli-jqwnqx9m.js} +1 -1
  38. package/dist/{cli-9dr5qzc0.js → cli-ny122vh4.js} +21 -10
  39. package/dist/{cli-5v4rw6yd.js → cli-pvzqhbs9.js} +2 -2
  40. package/dist/cli-pzq5rjym.js +15 -0
  41. package/dist/{cli-xpxde7rm.js → cli-qes8ef67.js} +48 -30
  42. package/dist/{cli-sajgpkqg.js → cli-qs9hhsvb.js} +1723 -325
  43. package/dist/cli-qys3rhhz.js +27 -0
  44. package/dist/{cli-rzbpmxdr.js → cli-r6tpm37h.js} +2 -2
  45. package/dist/{cli-qv61hp37.js → cli-rch80c76.js} +2 -2
  46. package/dist/{cli-0258203w.js → cli-rpp0x4zy.js} +137 -17
  47. package/dist/{cli-5n6e5yfn.js → cli-s1k0wxhe.js} +12 -7
  48. package/dist/{cli-psh3c4va.js → cli-sfd9v7sj.js} +1 -1
  49. package/dist/{cli-70xqj0z3.js → cli-sh3ytsyj.js} +49 -39
  50. package/dist/{cli-zsspvvm2.js → cli-w1mp9jy7.js} +1 -1
  51. package/dist/{cli-d3x3x1c9.js → cli-xdb4mpnp.js} +2 -2
  52. package/dist/{cli-57khth04.js → cli-xmyqv00z.js} +120 -4
  53. package/dist/{cli-yw8vghx2.js → cli-xv8t8p3c.js} +65 -32
  54. package/dist/{cli-f1am9szv.js → cli-xxv5pn4f.js} +1 -1
  55. package/dist/{cli-t76vw5db.js → cli-y08jvxzx.js} +601 -140
  56. package/dist/{cli-a4jzdpa3.js → cli-zfbf3srj.js} +650 -245
  57. package/dist/{cli-a40rnf71.js → cli-zmq89k34.js} +6 -6
  58. package/dist/{cli-qmdnhpej.js → cli-zr9q011v.js} +23 -13
  59. package/dist/cli.js +22 -22
  60. package/dist/{codex-adapter-d24q75wk.js → codex-adapter-bef93pq4.js} +9 -7
  61. package/dist/{codex-hook-hq54t9ft.js → codex-hook-e8dba65p.js} +3 -2
  62. package/dist/{codex-session-runner-g50vcsex.js → codex-session-runner-5pkxys7x.js} +9 -7
  63. package/dist/{commands-c3nrc5hg.js → commands-e23s1bff.js} +9 -7
  64. package/dist/{db-nt7fg3tw.js → db-m3nabatn.js} +17 -5
  65. package/dist/{google-generative-ai-hysrd8qx.js → google-generative-ai-8maw55xh.js} +11 -8
  66. package/dist/{google-vertex-p5arrga0.js → google-vertex-8vjqmx8g.js} +11 -8
  67. package/dist/{handlers-fkr6swat.js → handlers-akazedvr.js} +16 -13
  68. package/dist/{hook-77wpvnjc.js → hook-ze4cpf0v.js} +55 -41
  69. package/dist/{hook-g019yehy.js → hook-zfqyyenw.js} +10 -7
  70. package/dist/{http-jewj0syk.js → http-sja0n6kw.js} +730 -150
  71. package/dist/index-0gr8vd6k.js +39 -0
  72. package/dist/{index-7n2xpbd4.js → index-354vkhdv.js} +154 -74
  73. package/dist/{index-070v4eyb.js → index-5qthek5t.js} +2 -3
  74. package/dist/{index-hfvggd0z.js → index-95t3cqq5.js} +16 -14
  75. package/dist/{index-9znhar3x.js → index-sea66619.js} +3 -4
  76. package/dist/{index-6gm5he8h.js → index-vpn36mma.js} +15 -13
  77. package/dist/{keepalive-j0fjvqfd.js → keepalive-3sp3meqj.js} +16 -10
  78. package/dist/{lead-1n8x30xe.js → lead-mefeewkf.js} +39 -36
  79. package/dist/{maintenance-gvev11sa.js → maintenance-8tbrbjfg.js} +10 -8
  80. package/dist/mistral-conversations-00zrp7g4.js +717 -0
  81. package/dist/{oauth-refresh-sweep-s9ecsvh0.js → oauth-refresh-sweep-srjwjz6m.js} +8 -6
  82. package/dist/{onboard-pvm5b38w.js → onboard-mt2tgyjg.js} +7 -7
  83. package/dist/{openai-codex-responses-5g27pd6f.js → openai-codex-responses-map32h2s.js} +18 -18
  84. package/dist/{openai-completions-3vkmfz2a.js → openai-completions-w871b1cq.js} +12 -11
  85. package/dist/{openai-responses-dg4t7j7a.js → openai-responses-b0j7xnw8.js} +15 -12
  86. package/dist/{opencode-adapter-1gm38aba.js → opencode-adapter-b39jefcj.js} +5 -3
  87. package/dist/{openrouter-images-g3gm8931.js → openrouter-images-srfkywy0.js} +4 -4
  88. package/dist/{otel-impl-p6jvgd9c.js → otel-impl-z6pxt3j9.js} +224 -9
  89. package/dist/{pi-mono-adapter-t091s27g.js → pi-mono-adapter-btvqmj58.js} +1138 -408
  90. package/dist/{pricing-refresh-k49rcyed.js → pricing-refresh-kyq8cezh.js} +9 -7
  91. package/dist/{rbac-roles-cr80g96t.js → rbac-roles-3ryfrfna.js} +8 -6
  92. package/dist/{rbac-roles-30pspvy7.js → rbac-roles-6qfvbee1.js} +9 -7
  93. package/dist/{render-v2-xm457ng1.js → render-v2-p61ppjey.js} +8 -6
  94. package/dist/{seed-pricing-yhpv35fr.js → seed-pricing-j56bbj4y.js} +8 -6
  95. package/dist/{setup-nytnmwwr.js → setup-tpr6c1kn.js} +2 -2
  96. package/dist/{worker-msytr2cw.js → worker-9w1v0mbb.js} +39 -36
  97. package/openapi.json +542 -16
  98. package/package.json +6 -6
  99. package/src/be/budget-refusal-notify.ts +14 -7
  100. package/src/be/db-queries/task-artifacts.ts +182 -0
  101. package/src/be/db.ts +783 -169
  102. package/src/be/events.ts +5 -0
  103. package/src/be/migrations/130_scratch_script_retention_grace.sql +6 -0
  104. package/src/be/migrations/131_session_logs_created_at_index.sql +2 -0
  105. package/src/be/migrations/132_multi_runtime_instances.sql +44 -0
  106. package/src/be/migrations/133_workflow_run_trigger_type.sql +12 -0
  107. package/src/be/migrations/135_backfill_task_pull_request_attachments.sql +228 -0
  108. package/src/be/migrations/136_task_requester_provenance.sql +37 -0
  109. package/src/be/migrations/137_memory_retrieval_composite_index.sql +1 -0
  110. package/src/be/multi-runtime.ts +430 -0
  111. package/src/be/scripts/db.ts +43 -1
  112. package/src/be/scripts/extract-schema.ts +3 -1
  113. package/src/be/scripts/retention.ts +164 -0
  114. package/src/be/scripts/run-saved.ts +16 -1
  115. package/src/be/scripts/typecheck.ts +3 -2
  116. package/src/be/seed-skills/index.ts +7 -0
  117. package/src/be/steering.ts +1 -1
  118. package/src/be/swarm-config-guard.ts +18 -0
  119. package/src/commands/credential-wait.ts +54 -0
  120. package/src/commands/onboard/steps/core-credentials.tsx +1 -1
  121. package/src/commands/onboard/steps/custom-templates.tsx +1 -1
  122. package/src/commands/onboard/steps/harness-credentials.tsx +1 -1
  123. package/src/commands/onboard/steps/health-check.tsx +1 -1
  124. package/src/commands/onboard/steps/start.tsx +1 -1
  125. package/src/commands/profile-sync-audit.ts +56 -0
  126. package/src/commands/profile-sync.ts +287 -15
  127. package/src/commands/provider-credentials.ts +46 -19
  128. package/src/commands/runner.ts +196 -33
  129. package/src/commands/templates.ts +22 -0
  130. package/src/github/handlers.ts +55 -9
  131. package/src/heartbeat/heartbeat.ts +71 -12
  132. package/src/hooks/hook.ts +49 -33
  133. package/src/http/active-sessions.ts +11 -1
  134. package/src/http/agents.ts +261 -30
  135. package/src/http/all-routes.ts +1 -0
  136. package/src/http/config.ts +13 -2
  137. package/src/http/core.ts +102 -4
  138. package/src/http/db-query-bounded.ts +313 -0
  139. package/src/http/db-query-shared.ts +249 -0
  140. package/src/http/db-query.ts +54 -42
  141. package/src/http/events.ts +2 -0
  142. package/src/http/index.ts +27 -2
  143. package/src/http/mcp-bridge.ts +37 -3
  144. package/src/http/mcp.ts +12 -0
  145. package/src/http/memory.ts +13 -2
  146. package/src/http/openapi.ts +1 -0
  147. package/src/http/poll.ts +70 -27
  148. package/src/http/route-def.ts +43 -9
  149. package/src/http/schedules.ts +2 -2
  150. package/src/http/scripts.ts +31 -4
  151. package/src/http/session-data.ts +44 -0
  152. package/src/http/tasks.ts +2 -2
  153. package/src/http/users.ts +74 -52
  154. package/src/http/webhooks.ts +5 -2
  155. package/src/http/workflows.ts +1 -4
  156. package/src/http/x.ts +14 -0
  157. package/src/integrations/kapso/inbound.ts +1 -1
  158. package/src/jira/outbound.ts +39 -8
  159. package/src/jira/webhook-lifecycle.ts +13 -2
  160. package/src/linear/outbound.ts +43 -10
  161. package/src/oauth/keepalive.ts +13 -2
  162. package/src/prompts/base-prompt.ts +1 -1
  163. package/src/prompts/session-templates.ts +45 -7
  164. package/src/providers/claude-adapter.ts +6 -0
  165. package/src/providers/claude-managed-models.ts +34 -10
  166. package/src/providers/codex-adapter.ts +6 -0
  167. package/src/providers/opencode-adapter.ts +6 -0
  168. package/src/providers/pi-mono-adapter.ts +8 -0
  169. package/src/providers/pricing-sources.md +24 -0
  170. package/src/queue-stall-alarm.ts +216 -0
  171. package/src/scheduler/scheduler.ts +7 -0
  172. package/src/script-workflows/executor.ts +2 -1
  173. package/src/scripts-runtime/executors/native.ts +2 -1
  174. package/src/scripts-runtime/executors/types.ts +5 -0
  175. package/src/scripts-runtime/loader.ts +5 -0
  176. package/src/scripts-runtime/swarm-config.ts +8 -0
  177. package/src/scripts-runtime/swarm-sdk.ts +7 -0
  178. package/src/scripts-runtime/types/stdlib.d.ts +1 -0
  179. package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
  180. package/src/tests/budget-claim-gate.test.ts +20 -0
  181. package/src/tests/claude-adapter.test.ts +32 -0
  182. package/src/tests/claude-managed-adapter.test.ts +38 -7
  183. package/src/tests/codex-adapter.test.ts +30 -0
  184. package/src/tests/create-task-validation.test.ts +128 -0
  185. package/src/tests/credential-wait.test.ts +67 -1
  186. package/src/tests/db-query.test.ts +711 -2
  187. package/src/tests/http-api-integration.test.ts +81 -0
  188. package/src/tests/http-users.test.ts +166 -1
  189. package/src/tests/identity-field-budget.test.ts +176 -0
  190. package/src/tests/mcp-tools-user.test.ts +34 -0
  191. package/src/tests/mcp-user-route.test.ts +17 -0
  192. package/src/tests/metrics-http.test.ts +44 -0
  193. package/src/tests/multi-runtime-registration.test.ts +2703 -0
  194. package/src/tests/openapi-response-contract.test.ts +24 -0
  195. package/src/tests/openapi-route-headers.test.ts +83 -0
  196. package/src/tests/opencode-adapter.test.ts +40 -0
  197. package/src/tests/outbound-listener-rejection.test.ts +138 -0
  198. package/src/tests/pi-mono-adapter.test.ts +98 -0
  199. package/src/tests/profile-sync-warning.test.ts +277 -0
  200. package/src/tests/profile-sync.test.ts +232 -6
  201. package/src/tests/prompt-template-session.test.ts +32 -3
  202. package/src/tests/queue-stall-alarm.test.ts +226 -0
  203. package/src/tests/requester-comms.test.ts +32 -0
  204. package/src/tests/runner-requester-profile.test.ts +38 -0
  205. package/src/tests/sandboxed-process.test.ts +45 -0
  206. package/src/tests/schedule-target-type.test.ts +38 -0
  207. package/src/tests/script-connections.test.ts +16 -3
  208. package/src/tests/scripts-external-api.test.ts +8 -3
  209. package/src/tests/scripts-http.test.ts +12 -0
  210. package/src/tests/scripts-retention.test.ts +384 -0
  211. package/src/tests/scripts-runtime-identity.test.ts +375 -0
  212. package/src/tests/scripts-runtime.test.ts +4 -3
  213. package/src/tests/sdk-allowlist.test.ts +47 -0
  214. package/src/tests/send-task-requested-by.test.ts +9 -0
  215. package/src/tests/session-costs.test.ts +506 -1
  216. package/src/tests/shutdown-error-scrubbing.test.ts +92 -0
  217. package/src/tests/steering-core.test.ts +28 -0
  218. package/src/tests/task-artifacts.test.ts +386 -0
  219. package/src/tests/task-attachments-schema.test.ts +17 -1
  220. package/src/tests/task-completion-idempotency.test.ts +57 -0
  221. package/src/tests/task-pull-request-backfill-migration.test.ts +321 -0
  222. package/src/tests/update-profile-api.test.ts +67 -39
  223. package/src/tests/update-profile-auth.test.ts +48 -4
  224. package/src/tests/user-attribution-migration.test.ts +54 -0
  225. package/src/tests/user-identity.test.ts +72 -0
  226. package/src/tests/workflow-async-v2.test.ts +39 -1
  227. package/src/tests/workflow-http-v2.test.ts +3 -2
  228. package/src/tests/workflow-mcp-trigger-schema.test.ts +52 -3
  229. package/src/tests/workflow-retry-v2.test.ts +45 -1
  230. package/src/tests/workflow-swarm-script.test.ts +14 -5
  231. package/src/tools/db-query.ts +18 -8
  232. package/src/tools/get-task-details.ts +24 -3
  233. package/src/tools/manage-user.ts +36 -3
  234. package/src/tools/poll-task.ts +93 -14
  235. package/src/tools/script-common.ts +5 -0
  236. package/src/tools/send-task.ts +1 -4
  237. package/src/tools/store-progress.ts +13 -2
  238. package/src/tools/swarm-config/delete-config.ts +9 -0
  239. package/src/tools/swarm-config/set-config.ts +3 -2
  240. package/src/tools/task-action.ts +30 -0
  241. package/src/tools/task-tool-ctx.ts +8 -1
  242. package/src/tools/update-profile.ts +16 -21
  243. package/src/tools/utils.ts +5 -0
  244. package/src/tools/workflows/trigger-workflow.ts +7 -1
  245. package/src/types.ts +165 -1
  246. package/src/utils/github-pull-request.ts +76 -0
  247. package/src/utils/identity-field-budget.ts +73 -0
  248. package/src/utils/multi-runtime.ts +20 -0
  249. package/src/utils/requester-comms.ts +24 -0
  250. package/src/utils/sandboxed-process.ts +40 -14
  251. package/src/workflows/engine.ts +22 -2
  252. package/src/workflows/executors/swarm-script.ts +24 -1
  253. package/src/workflows/resume.ts +19 -11
  254. package/src/workflows/retry-poller.ts +1 -0
  255. package/src/workflows/triggers.ts +4 -0
  256. package/templates/schedules/daily-compounding-reflection/content.md +12 -3
  257. package/templates/skills/db-query-guidance/config.json +14 -0
  258. package/templates/skills/db-query-guidance/content.md +57 -0
  259. package/dist/cli-3te4jdkm.js +0 -225
  260. package/dist/cli-bmze986d.js +0 -7
  261. package/dist/cli-kh3ba2zf.js +0 -607
  262. package/dist/index-hp1kdbjj.js +0 -36
  263. package/dist/mistral-conversations-81fwav0j.js +0 -30578
  264. package/dist/otel-nqa12wf3.js +0 -3149
  265. package/dist/{pi-messages-tqj8q6b9.js → pi-messages-sssy32x4.js} +3 -3
@@ -2,18 +2,23 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { addMinutes } from "date-fns";
3
3
  import * as z from "zod";
4
4
  import {
5
+ getActiveTaskCount,
5
6
  getAgentById,
6
7
  getDb,
7
8
  getOfferedTasksForAgent,
8
9
  getPendingTaskForAgent,
9
10
  getUnassignedTasksCount,
11
+ hasCapacity,
10
12
  incrementEmptyPollCount,
11
13
  MAX_EMPTY_POLLS,
12
14
  resetEmptyPollCount,
13
15
  startTask,
14
16
  updateAgentStatus,
15
17
  } from "@/be/db";
18
+ import { touchRuntimeInstance } from "@/be/multi-runtime";
16
19
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
20
+ import type { AgentTask } from "@/types";
21
+ import { isMultiRuntimeEnabled } from "@/utils/multi-runtime";
17
22
  import { looseAgentTaskOutputSchema } from "./get-task-details";
18
23
 
19
24
  const DEFAULT_POLL_INTERVAL_MS = 2000;
@@ -70,6 +75,28 @@ export const registerPollTaskTool = (server: McpServer) => {
70
75
  // touching the bookkeeping path below.
71
76
  const wasBudgetRefused: boolean = false;
72
77
 
78
+ // Second dispatch entrypoint alongside HTTP /api/poll, so it needs the
79
+ // same gate: a process whose runtime is gone must not pick up work
80
+ // beside its replacement. Touch validates ownership+liveness and
81
+ // refreshes it; it cannot revive a retired runtime.
82
+ if (
83
+ isMultiRuntimeEnabled() &&
84
+ !(
85
+ requestInfo.runtimeInstanceId &&
86
+ touchRuntimeInstance(requestInfo.runtimeInstanceId, agentId)
87
+ )
88
+ ) {
89
+ return toolOk("No task available.", {
90
+ details: "No task available for this runtime.",
91
+ data: {
92
+ yourAgentId: requestInfo.agentId,
93
+ offeredTasks: [],
94
+ availableCount: 0,
95
+ waitedForSeconds: 0,
96
+ },
97
+ });
98
+ }
99
+
73
100
  const agent = getAgentById(agentId);
74
101
  if (!agent) {
75
102
  return toolErr(`Agent with ID "${agentId}" not found in the swarm.`, {
@@ -104,26 +131,78 @@ export const registerPollTaskTool = (server: McpServer) => {
104
131
  // Poll for pending tasks
105
132
  while (new Date() < maxTime) {
106
133
  // Fetch and update in a single transaction to avoid race conditions
107
- const startedTask = getDb().transaction(() => {
108
- const agentNow = getAgentById(agentId)!;
134
+ const outcome = getDb().transaction(
135
+ (): AgentTask | "at-capacity" | "runtime-unavailable" | null => {
136
+ // The entry gate only proves liveness when the long poll began;
137
+ // the runtime must be live at the exact moment work is acquired,
138
+ // so revalidate in the same transaction that can start the task.
139
+ // Touch refreshes an already-live row and cannot revive one.
140
+ if (
141
+ isMultiRuntimeEnabled() &&
142
+ !(
143
+ requestInfo.runtimeInstanceId &&
144
+ touchRuntimeInstance(requestInfo.runtimeInstanceId, agentId)
145
+ )
146
+ ) {
147
+ return "runtime-unavailable";
148
+ }
149
+
150
+ const agentNow = getAgentById(agentId)!;
151
+
152
+ if (agentNow.status !== "busy") {
153
+ updateAgentStatus(agentId, "idle");
154
+ }
109
155
 
110
- if (agentNow.status !== "busy") {
111
- updateAgentStatus(agentId, "idle");
112
- }
156
+ const pendingTask = getPendingTaskForAgent(agentId);
157
+ if (!pendingTask) return null;
113
158
 
114
- const pendingTask = getPendingTaskForAgent(agentId);
115
- if (!pendingTask) return null;
159
+ // Logical capacity is decided inside the same transaction as the
160
+ // start transition: several runtimes of one agent race this
161
+ // dispatch, and a check outside it would let each of them start a
162
+ // task past the agent's limit. Same gate as HTTP /api/poll.
163
+ if (!hasCapacity(agentId)) return "at-capacity";
116
164
 
117
- const maybeTask = startTask(pendingTask.id);
165
+ const maybeTask = startTask(pendingTask.id);
118
166
 
119
- if (maybeTask) {
120
- // Update automatically in case the agent forgets xd
121
- updateAgentStatus(agentId, "busy");
122
- }
167
+ if (maybeTask) {
168
+ // Update automatically in case the agent forgets xd
169
+ updateAgentStatus(agentId, "busy");
170
+ }
123
171
 
124
- return maybeTask;
125
- })();
172
+ return maybeTask;
173
+ },
174
+ )();
175
+
176
+ if (outcome === "runtime-unavailable") {
177
+ // The runtime was retired while this call waited: stop immediately
178
+ // rather than keep polling as it, and skip the exit counter — this
179
+ // is a refusal, not an empty poll.
180
+ return toolOk("No task available.", {
181
+ details: "No task available for this runtime.",
182
+ data: {
183
+ yourAgentId: requestInfo.agentId,
184
+ offeredTasks: [],
185
+ availableCount: 0,
186
+ waitedForSeconds: Math.round((Date.now() - now.getTime()) / 1000),
187
+ },
188
+ });
189
+ }
190
+
191
+ if (outcome === "at-capacity") {
192
+ // A capacity refusal is not an empty poll (refused ≠ empty, D-R3):
193
+ // return without advancing the exit counter.
194
+ return toolOk("No task available.", {
195
+ details: `You are at capacity (${getActiveTaskCount(agentId)} active task(s)). Complete a task before polling for more.`,
196
+ data: {
197
+ yourAgentId: requestInfo.agentId,
198
+ offeredTasks: [],
199
+ availableCount: getUnassignedTasksCount(),
200
+ waitedForSeconds: Math.round((Date.now() - now.getTime()) / 1000),
201
+ },
202
+ });
203
+ }
126
204
 
205
+ const startedTask = outcome;
127
206
  if (startedTask) {
128
207
  // Reset empty poll count when task is assigned
129
208
  resetEmptyPollCount(agentId);
@@ -172,6 +172,11 @@ export async function proxyScriptsApi(args: {
172
172
  if (args.requestInfo.sourceTaskId) {
173
173
  headers["X-Source-Task-Id"] = args.requestInfo.sourceTaskId;
174
174
  }
175
+ // Runtime identity is process context from the worker's MCP session — it
176
+ // rides the proxy like the agent identity, never as tool or script input.
177
+ if (args.requestInfo.runtimeInstanceId) {
178
+ headers["X-Runtime-Instance-ID"] = args.requestInfo.runtimeInstanceId;
179
+ }
175
180
  const res = await fetch(`${apiBaseUrl()}${args.path}`, {
176
181
  method: args.method,
177
182
  headers,
@@ -223,11 +223,8 @@ export async function sendTaskHandler(
223
223
 
224
224
  const creatorAgentId = ctx.kind === "owner" ? ctx.agentId : undefined;
225
225
  const sourceTaskId = ctx.kind === "owner" ? ctx.sourceTaskId : undefined;
226
- const callerTask = sourceTaskId ? getTaskById(sourceTaskId) : null;
227
226
  const requestedByUserId =
228
- ctx.kind === "user"
229
- ? ctx.userId
230
- : (inputRequestedByUserId ?? callerTask?.requestedByUserId ?? undefined);
227
+ ctx.kind === "user" ? ctx.userId : (inputRequestedByUserId ?? undefined);
231
228
 
232
229
  if (ctx.kind === "owner" && agentId === ctx.agentId) {
233
230
  return toolErr("Cannot send a task to yourself, are you drunk?", {
@@ -24,6 +24,7 @@ import {
24
24
  import { createWorkerTaskFollowUp } from "@/tasks/worker-follow-up";
25
25
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
26
26
  import { AgentTaskStatusSchema, AttachmentInputSchema, isTerminalTaskStatus } from "@/types";
27
+ import { scrubSecrets } from "../utils/secret-scrubber";
27
28
 
28
29
  // Phase 11: the `cost` / `costData` field was removed from this tool's input
29
30
  // schema. Adapters (claude/codex/pi/opencode/devin/claude-managed) are the
@@ -400,7 +401,12 @@ export const registerStoreProgressTool = (server: McpServer) => {
400
401
  } catch {
401
402
  // Non-blocking — task completion memory failure should not affect task status
402
403
  }
403
- })();
404
+ })().catch((err) =>
405
+ console.error(
406
+ "[store-progress] task completion memory write failed:",
407
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
408
+ ),
409
+ );
404
410
  }
405
411
 
406
412
  if (shouldRunTerminalSideEffects) {
@@ -436,7 +442,12 @@ export const registerStoreProgressTool = (server: McpServer) => {
436
442
  err instanceof Error ? err.message : String(err),
437
443
  );
438
444
  }
439
- })();
445
+ })().catch((err) =>
446
+ console.error(
447
+ "[store-progress] server rater run failed:",
448
+ scrubSecrets(err instanceof Error ? err.message : String(err)),
449
+ ),
450
+ );
440
451
  }
441
452
 
442
453
  // Create follow-up task for the lead when a worker task finishes.
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
3
  import { deleteSwarmConfig, getAgentById, getSwarmConfigLookupById } from "@/be/db";
4
+ import { AGENT_MAX_TASKS_CONFIG_KEY, resetAgentMaxTasksMirror } from "@/be/multi-runtime";
4
5
  import { scheduleIntegrationsReload } from "@/http/core";
5
6
  import { can } from "@/rbac";
6
7
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
@@ -56,6 +57,14 @@ export const registerDeleteConfigTool = (server: McpServer) => {
56
57
  }
57
58
 
58
59
  const deleted = deleteSwarmConfig(id);
60
+ if (
61
+ deleted &&
62
+ existing.scope === "agent" &&
63
+ existing.scopeId &&
64
+ existing.key === AGENT_MAX_TASKS_CONFIG_KEY
65
+ ) {
66
+ resetAgentMaxTasksMirror(existing.scopeId);
67
+ }
59
68
  if (!deleted) {
60
69
  return toolErr(`Failed to delete config entry "${id}".`, {
61
70
  data: { yourAgentId: requestInfo.agentId },
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import * as z from "zod";
3
- import { getAgentById, maskSecrets, upsertSwarmConfig } from "@/be/db";
3
+ import { getAgentById, maskSecrets } from "@/be/db";
4
+ import { upsertSwarmConfigWithPolicyMirror } from "@/be/multi-runtime";
4
5
  import {
5
6
  isReservedConfigKey,
6
7
  reservedKeyError,
@@ -110,7 +111,7 @@ export const registerSetConfigTool = (server: McpServer) => {
110
111
  return toolErr(validationError, { data: { yourAgentId: requestInfo.agentId } });
111
112
  }
112
113
 
113
- const config = upsertSwarmConfig({
114
+ const config = upsertSwarmConfigWithPolicyMirror({
114
115
  scope,
115
116
  scopeId: scope === "global" ? null : scopeId,
116
117
  key,
@@ -27,6 +27,7 @@ import {
27
27
  releaseTask,
28
28
  updateTaskClaudeSessionId,
29
29
  } from "@/be/db";
30
+ import { touchRuntimeInstance } from "@/be/multi-runtime";
30
31
  import { assertOwnsTask, ownerCtx, type ToolCtx } from "@/tools/task-tool-ctx";
31
32
  import {
32
33
  createToolRegistrar,
@@ -42,6 +43,7 @@ import {
42
43
  ReasoningEffortSchema,
43
44
  splitLegacyModelAlias,
44
45
  } from "@/types";
46
+ import { isMultiRuntimeEnabled } from "@/utils/multi-runtime";
45
47
  import { looseAgentTaskOutputSchema } from "./get-task-details";
46
48
 
47
49
  export const TaskActionSchema = z.enum([
@@ -140,6 +142,28 @@ function agentOnlyActionResult(): SwarmToolResult {
140
142
  return toolErr("This action is only available to worker agents.");
141
143
  }
142
144
 
145
+ /**
146
+ * Work-acquisition gate shared by claim and accept — the same dispatch gate
147
+ * as HTTP poll and poll-task: a process whose runtime is gone must not take
148
+ * work beside its replacement. Identity travels as request context, never as
149
+ * a tool argument; touch refreshes liveness and cannot revive a retired
150
+ * runtime. Returns null when acquisition may proceed.
151
+ */
152
+ function staleRuntimeResult(ctx: ToolCtx, agentId: string): TaskActionResult | null {
153
+ if (!isMultiRuntimeEnabled()) return null;
154
+ if (
155
+ ctx.kind === "owner" &&
156
+ ctx.runtimeInstanceId &&
157
+ touchRuntimeInstance(ctx.runtimeInstanceId, agentId)
158
+ ) {
159
+ return null;
160
+ }
161
+ return {
162
+ success: false,
163
+ message: "This runtime is no longer registered. Re-register before taking work.",
164
+ };
165
+ }
166
+
143
167
  function taskActionResult(result: TaskActionResult, agentId?: string): SwarmToolResult {
144
168
  const { refusalSideEffects: _omit, success, message, ...rest } = result;
145
169
  const data = {
@@ -285,6 +309,8 @@ export async function taskActionHandler(
285
309
  if (!taskId) {
286
310
  return { success: false, message: "Task ID is required for 'claim' action." };
287
311
  }
312
+ const staleClaimRuntime = staleRuntimeResult(ctx, agentId);
313
+ if (staleClaimRuntime) return staleClaimRuntime;
288
314
  // Check capacity before claiming
289
315
  if (!hasCapacity(agentId)) {
290
316
  const activeCount = getActiveTaskCount(agentId);
@@ -388,6 +414,10 @@ export async function taskActionHandler(
388
414
  if (!taskId) {
389
415
  return { success: false, message: "Task ID is required for 'accept' action." };
390
416
  }
417
+ // Accept is a work-acquisition transition too — gate it before the
418
+ // budget admission below so a retired process cannot take the offer.
419
+ const staleAcceptRuntime = staleRuntimeResult(ctx, agentId);
420
+ if (staleAcceptRuntime) return staleAcceptRuntime;
391
421
  const existingTask = getTaskById(taskId);
392
422
  if (!existingTask) {
393
423
  return { success: false, message: `Task "${taskId}" not found.` };
@@ -3,13 +3,20 @@ import type { AgentTask, User } from "@/types";
3
3
  import { type RequestInfo, type SwarmToolResult, toolErr } from "./utils";
4
4
 
5
5
  export type ToolCtx =
6
- | { kind: "owner"; agentId?: string; sourceTaskId?: string; sessionId?: string }
6
+ | {
7
+ kind: "owner";
8
+ agentId?: string;
9
+ runtimeInstanceId?: string;
10
+ sourceTaskId?: string;
11
+ sessionId?: string;
12
+ }
7
13
  | { kind: "user"; userId: string; user: User; sessionId?: string };
8
14
 
9
15
  export function ownerCtx(info: RequestInfo): ToolCtx {
10
16
  return {
11
17
  kind: "owner",
12
18
  agentId: info.agentId,
19
+ runtimeInstanceId: info.runtimeInstanceId,
13
20
  sourceTaskId: info.sourceTaskId,
14
21
  sessionId: info.sessionId,
15
22
  };
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import * as z from "zod";
6
- import { getAgentById, updateAgentName, updateAgentProfile } from "@/be/db";
6
+ import { getAgentById, updateAgentProfile } from "@/be/db";
7
7
  import { can } from "@/rbac";
8
8
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
9
9
  import { type Agent, AgentAvatarSchema, AgentStatusSchema, ProviderNameSchema } from "@/types";
@@ -110,7 +110,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
110
110
  .max(MAX_PROFILE_FILE_LENGTH)
111
111
  .optional()
112
112
  .describe(
113
- "Personal CLAUDE.md content. Loaded on session start and synced back on session end. Use for persistent notes and instructions.",
113
+ "Personal CLAUDE.md content. Loaded on session start and synced back on session end. Above 20,000 characters, updates may only keep or reduce the stored size; move durable overflow into memory.",
114
114
  ),
115
115
  soulMd: z
116
116
  .string()
@@ -118,7 +118,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
118
118
  .max(MAX_PROFILE_FILE_LENGTH)
119
119
  .optional()
120
120
  .describe(
121
- "Soul content: persona and behavioral directives. Updates both DB and /workspace/SOUL.md. Must be at least 200 characters to prevent accidental corruption.",
121
+ "Soul content: persona and behavioral directives. Updates both DB and /workspace/SOUL.md. Must be at least 200 characters; above 10,000 characters, updates may only keep or reduce the stored size.",
122
122
  ),
123
123
  identityMd: z
124
124
  .string()
@@ -126,7 +126,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
126
126
  .max(MAX_PROFILE_FILE_LENGTH)
127
127
  .optional()
128
128
  .describe(
129
- "Identity content: expertise and working style. Updates both DB and /workspace/IDENTITY.md. Must be at least 200 characters to prevent accidental corruption.",
129
+ "Identity content: expertise and working style. Updates both DB and /workspace/IDENTITY.md. Must be at least 200 characters; above 10,000 characters, updates may only keep or reduce the stored size.",
130
130
  ),
131
131
  setupScript: z
132
132
  .string()
@@ -140,7 +140,7 @@ export const registerUpdateProfileTool = (server: McpServer) => {
140
140
  .max(MAX_PROFILE_FILE_LENGTH)
141
141
  .optional()
142
142
  .describe(
143
- "Environment-specific operational knowledge. Repos, services, SSH hosts, APIs, device names — anything specific to your setup. Synced to /workspace/TOOLS.md.",
143
+ "Environment-specific operational knowledge. Synced to /workspace/TOOLS.md. Above 20,000 characters, updates may only keep or reduce the stored size; move durable overflow into memory.",
144
144
  ),
145
145
  heartbeatMd: z
146
146
  .string()
@@ -250,21 +250,14 @@ export const registerUpdateProfileTool = (server: McpServer) => {
250
250
  }
251
251
  }
252
252
 
253
- // Update name if provided
254
- if (name !== undefined) {
255
- agent = updateAgentName(targetAgentId, name);
256
- if (!agent) {
257
- return toolErr("Target agent not found.", {
258
- data: { yourAgentId: requestInfo.agentId },
259
- });
260
- }
261
- }
262
-
263
- // Update profile fields if provided. `avatar` is spread in only when
264
- // present so `null` (reset) stays distinguishable from "not provided".
253
+ // Apply name and profile fields in one DB transaction so validation or
254
+ // update failures cannot leave a partially-applied combined payload.
255
+ // `avatar` is spread in only when present so `null` (reset) stays
256
+ // distinguishable from "not provided".
265
257
  agent = updateAgentProfile(
266
258
  targetAgentId,
267
259
  {
260
+ name,
268
261
  description,
269
262
  role,
270
263
  capabilities,
@@ -282,6 +275,12 @@ export const registerUpdateProfileTool = (server: McpServer) => {
282
275
  },
283
276
  );
284
277
 
278
+ // Do not perform audit or workspace-file side effects when the target
279
+ // disappeared before the transactional update could find it.
280
+ if (!agent) {
281
+ return toolErr("Agent not found.", { data: { yourAgentId: requestInfo.agentId } });
282
+ }
283
+
285
284
  if (setupScript !== undefined && previousSetupScript !== undefined) {
286
285
  try {
287
286
  const diff = await computeSetupScriptDiff(previousSetupScript, setupScript);
@@ -348,10 +347,6 @@ export const registerUpdateProfileTool = (server: McpServer) => {
348
347
  }
349
348
  }
350
349
 
351
- if (!agent) {
352
- return toolErr("Agent not found.", { data: { yourAgentId: requestInfo.agentId } });
353
- }
354
-
355
350
  const updatedFields: string[] = [];
356
351
  if (name !== undefined) updatedFields.push("name");
357
352
  if (description !== undefined) updatedFields.push("description");
@@ -32,6 +32,8 @@ export function markScriptSdkRequestOrigin<T extends object>(meta: T): T {
32
32
  export type RequestInfo = {
33
33
  sessionId: string | undefined;
34
34
  agentId: string | undefined;
35
+ /** Calling worker process, when multi-runtime mode is on. */
36
+ runtimeInstanceId: string | undefined;
35
37
  sourceTaskId: string | undefined;
36
38
  contextKey: string | undefined;
37
39
  callOrigin: "mcp" | "script-sdk";
@@ -41,6 +43,8 @@ export const getRequestInfo = (req: Meta): RequestInfo => {
41
43
  const agentIdHeader = req.requestInfo?.headers?.["x-agent-id"];
42
44
  const sourceTaskIdHeader = req.requestInfo?.headers?.["x-source-task-id"];
43
45
  const contextKeyHeader = req.requestInfo?.headers?.["x-context-key"];
46
+ const runtimeHeader = req.requestInfo?.headers?.["x-runtime-instance-id"];
47
+ const runtimeInstanceId = Array.isArray(runtimeHeader) ? runtimeHeader[0] : runtimeHeader;
44
48
 
45
49
  let agentId: string | undefined;
46
50
  if (Array.isArray(agentIdHeader)) {
@@ -66,6 +70,7 @@ export const getRequestInfo = (req: Meta): RequestInfo => {
66
70
  return {
67
71
  sessionId: req.sessionId || undefined,
68
72
  agentId,
73
+ runtimeInstanceId: typeof runtimeInstanceId === "string" ? runtimeInstanceId : undefined,
69
74
  sourceTaskId,
70
75
  contextKey,
71
76
  callOrigin: scriptSdkRequestOrigins.has(req) ? "script-sdk" : "mcp",
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
+ import { resolveTaskAuditUserId } from "@/be/audit-user";
3
4
  import { getWorkflow, getWorkflowRun } from "@/be/db";
4
5
  import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
5
6
  import { getExecutorRegistry, startWorkflowExecution } from "@/workflows";
@@ -28,7 +29,7 @@ export const registerTriggerWorkflowTool = (server: McpServer) => {
28
29
  triggerSchema: z.record(z.string(), z.unknown()).optional(),
29
30
  }),
30
31
  },
31
- async ({ id, triggerData }) => {
32
+ async ({ id, triggerData }, requestInfo) => {
32
33
  try {
33
34
  const workflow = getWorkflow(id);
34
35
  if (!workflow) {
@@ -41,6 +42,11 @@ export const registerTriggerWorkflowTool = (server: McpServer) => {
41
42
  workflow,
42
43
  triggerData ?? {},
43
44
  getExecutorRegistry(),
45
+ {
46
+ triggerType: "manual",
47
+ requestedByUserId:
48
+ resolveTaskAuditUserId(requestInfo.sourceTaskId, requestInfo.agentId) ?? undefined,
49
+ },
44
50
  );
45
51
 
46
52
  // Check if the run was skipped due to cooldown