@desplega.ai/agent-swarm 1.147.0 → 1.149.0

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 (185) hide show
  1. package/README.md +13 -1
  2. package/dist/{acp-adapter-9n319wqc.js → acp-adapter-4jncb126.js} +12 -5
  3. package/dist/{actions-2vxqvpr9.js → actions-31vp3ae7.js} +10 -9
  4. package/dist/{app-w066xfy0.js → app-1hh67x8w.js} +6 -6
  5. package/dist/{assistant-kt31dj2h.js → assistant-z66dmz6p.js} +13 -11
  6. package/dist/{boot-reembed-az5rassp.js → boot-reembed-fqzxxzjh.js} +7 -7
  7. package/dist/{boot-reembed-j3mm3rfz.js → boot-reembed-wxa8ya0s.js} +8 -8
  8. package/dist/{boot-scrub-logs-b6h54817.js → boot-scrub-logs-jnyndh79.js} +5 -5
  9. package/dist/{claude-adapter-w1z04ann.js → claude-adapter-3qpa9w0j.js} +9 -9
  10. package/dist/{claude-managed-adapter-sactwn31.js → claude-managed-adapter-qvsmh6mv.js} +2 -2
  11. package/dist/{claude-sdk-session-f0cc1kec.js → claude-sdk-session-2yxgjs5d.js} +9 -9
  12. package/dist/{cli-y6b6fb76.js → cli-08b07b4r.js} +1 -1
  13. package/dist/{cli-8fc8de14.js → cli-0z2v4nhw.js} +1 -1
  14. package/dist/{cli-de14znh8.js → cli-19f354qr.js} +2 -2
  15. package/dist/{cli-hbv0kq6w.js → cli-2qts1hys.js} +3 -3
  16. package/dist/{cli-kpv03zhj.js → cli-3apbzgyk.js} +27 -15
  17. package/dist/{cli-1th1728g.js → cli-3q5ejx9p.js} +2 -2
  18. package/dist/{cli-d8brzxjb.js → cli-4wtq5jjv.js} +2 -2
  19. package/dist/{cli-ct0et58h.js → cli-5my3bjsd.js} +1 -1
  20. package/dist/{cli-cn6mmc7f.js → cli-6664w7y4.js} +1 -1
  21. package/dist/{cli-yz3djrm0.js → cli-6pqcb4q0.js} +53 -13
  22. package/dist/{cli-719k9j2c.js → cli-7smrsr25.js} +3 -3
  23. package/dist/{cli-rw51bq3j.js → cli-9fy6nk6g.js} +1 -1
  24. package/dist/{cli-zhjmqxzh.js → cli-9hspd3dp.js} +2 -2
  25. package/dist/{cli-3tgwnf43.js → cli-aae6hz6a.js} +4 -4
  26. package/dist/{cli-89xfd7f9.js → cli-cr12paw2.js} +5 -5
  27. package/dist/{cli-b5bq9crk.js → cli-d8ftsp62.js} +8 -8
  28. package/dist/{cli-4nr988h6.js → cli-e95cx1eb.js} +3 -3
  29. package/dist/{cli-n8508nre.js → cli-gjhjfeeg.js} +1 -1
  30. package/dist/{cli-p1d5b073.js → cli-gv88e1vf.js} +1 -1
  31. package/dist/{cli-rvqz34y5.js → cli-jecc01cb.js} +1 -1
  32. package/dist/{cli-8v0g1yc8.js → cli-k0cr4kat.js} +1 -1
  33. package/dist/{cli-mmemxxdc.js → cli-kgz4np97.js} +4 -4
  34. package/dist/{cli-w9kb3bk1.js → cli-kjwt6hdf.js} +3 -1
  35. package/dist/{cli-f7dsy61y.js → cli-mnmsfd1w.js} +6 -2
  36. package/dist/{cli-54wwve05.js → cli-n3dz2942.js} +37 -26
  37. package/dist/{cli-12fz972k.js → cli-nye12xk5.js} +8 -5
  38. package/dist/{cli-tzvk9haz.js → cli-phsxnkp6.js} +20 -20
  39. package/dist/{cli-53s590z8.js → cli-q50ef8g0.js} +1 -1
  40. package/dist/{cli-f146mn5s.js → cli-qaacms9y.js} +7 -6
  41. package/dist/{cli-xqcq9y5e.js → cli-r2ap2czm.js} +1 -0
  42. package/dist/{cli-m8yrsg97.js → cli-sd5pv3b1.js} +3 -3
  43. package/dist/{cli-0b6y7kxm.js → cli-xm87hzbw.js} +1 -1
  44. package/dist/{cli-ftht3zzj.js → cli-yab68w40.js} +285 -11
  45. package/dist/{cli-atcve9y8.js → cli-yb9qhqam.js} +227 -59
  46. package/dist/{cli-smby5m17.js → cli-ygweqcn4.js} +133 -22
  47. package/dist/{cli-dttph1bw.js → cli-ykxrnd1q.js} +2 -2
  48. package/dist/{cli-m9sxbkhm.js → cli-zvz62chv.js} +3 -3
  49. package/dist/cli.js +16 -14
  50. package/dist/{codex-adapter-zw4dwsx7.js → codex-adapter-8meawydj.js} +4 -4
  51. package/dist/{codex-hook-bw8p5nh4.js → codex-hook-hfrt9shr.js} +2 -2
  52. package/dist/{codex-session-runner-k981s65v.js → codex-session-runner-gjgxyp5j.js} +4 -4
  53. package/dist/{commands-sykrxx7e.js → commands-hz3nb95f.js} +5 -5
  54. package/dist/{db-sfbsc8wt.js → db-ggz97zbm.js} +5 -5
  55. package/dist/{e2b-6hb103d8.js → e2b-p04vmtx0.js} +1 -1
  56. package/dist/{handlers-0aspcejm.js → handlers-nbzdzn9k.js} +14 -11
  57. package/dist/{hook-hxccs7m5.js → hook-bks2q1f6.js} +7 -7
  58. package/dist/{hook-4nvqj2sb.js → hook-kq9wp0ep.js} +8 -8
  59. package/dist/{http-y06z517d.js → http-hrf77v87.js} +96 -45
  60. package/dist/{index-tce1rss4.js → index-6nsbg88z.js} +443 -144
  61. package/dist/{index-x5j894zf.js → index-8hmeg8j1.js} +14 -14
  62. package/dist/{index-k8znfx0z.js → index-cqtebbhb.js} +13 -13
  63. package/dist/{index-hssww7kj.js → index-n28pw1zt.js} +15 -15
  64. package/dist/{keepalive-w733ax66.js → keepalive-dxc12gvg.js} +7 -7
  65. package/dist/{lead-75bjqa9d.js → lead-yckfn18d.js} +32 -32
  66. package/dist/{maintenance-w4f1zjck.js → maintenance-6kfb2m5e.js} +8 -8
  67. package/dist/{oauth-refresh-sweep-wdetky26.js → oauth-refresh-sweep-1x1ac722.js} +6 -6
  68. package/dist/{onboard-bb5net8s.js → onboard-dcyt1609.js} +4 -3
  69. package/dist/{opencode-adapter-fef1mrn2.js → opencode-adapter-dce4wskd.js} +2 -2
  70. package/dist/{otel-impl-0w7c14ap.js → otel-impl-tbk3e5dr.js} +2 -2
  71. package/dist/{pi-mono-adapter-2has4gnp.js → pi-mono-adapter-zvshk5cj.js} +2 -2
  72. package/dist/{pricing-refresh-9nzsrgcq.js → pricing-refresh-kaqg567e.js} +7 -7
  73. package/dist/{rbac-roles-z1jrffbt.js → rbac-roles-ammq6rd2.js} +5 -5
  74. package/dist/{rbac-roles-r3hqjp09.js → rbac-roles-cf97v1vd.js} +6 -6
  75. package/dist/{render-v2-vbr89fnb.js → render-v2-80w603vy.js} +6 -6
  76. package/dist/{seed-pricing-g62hy1pk.js → seed-pricing-zbrn83xv.js} +6 -6
  77. package/dist/{setup-gnfqnptp.js → setup-fe66kczs.js} +2 -2
  78. package/dist/{worker-kznxp4qm.js → worker-qmb2d1e2.js} +32 -32
  79. package/dist/{x-n2phr5vm.js → x-n3e0v3xa.js} +2 -2
  80. package/openapi.json +46 -4
  81. package/package.json +3 -1
  82. package/src/agentmail/handlers.ts +5 -0
  83. package/src/automation-preflight-alert.ts +41 -0
  84. package/src/be/automation-preflight.ts +3 -3
  85. package/src/be/budget-refusal-notify.ts +1 -0
  86. package/src/be/db/tasks/read.ts +5 -0
  87. package/src/be/db.ts +132 -2
  88. package/src/be/migrations/150_deferred_task_waits.sql +18 -0
  89. package/src/be/migrations/151_repair_scheduled_task_required_params.sql +39 -0
  90. package/src/be/migrations/152_routing_decisions.sql +44 -0
  91. package/src/be/scripts/typecheck.ts +33 -22
  92. package/src/be/seed-scripts/catalog/delegate.ts +5 -1
  93. package/src/be/seed-skills/bundled-files.generated.json +10 -0
  94. package/src/be/seed-skills/index.ts +3 -0
  95. package/src/be/steering.ts +1 -0
  96. package/src/be/swarm-config-guard.ts +11 -0
  97. package/src/commands/onboard/steps/post-task.tsx +1 -0
  98. package/src/github/handlers.ts +9 -0
  99. package/src/gitlab/handlers.ts +4 -0
  100. package/src/heartbeat/heartbeat.ts +8 -0
  101. package/src/http/approval-requests.ts +1 -0
  102. package/src/http/apps.ts +1 -0
  103. package/src/http/config.ts +15 -6
  104. package/src/http/mcp.ts +31 -4
  105. package/src/http/script-runs.ts +6 -0
  106. package/src/http/tasks.ts +52 -30
  107. package/src/integrations/kapso/inbound.ts +1 -0
  108. package/src/jira/sync.ts +2 -0
  109. package/src/linear/sync.ts +2 -0
  110. package/src/prompts/session-templates.ts +29 -15
  111. package/src/scheduler/deferred-task-waits.ts +123 -0
  112. package/src/scheduler/schedule-task.ts +42 -0
  113. package/src/scheduler/scheduler.ts +22 -31
  114. package/src/script-workflows/workflow-ctx.ts +2 -0
  115. package/src/scripts-runtime/types/stdlib.d.ts +38 -24
  116. package/src/scripts-runtime/types/swarm-sdk.d.ts +38 -24
  117. package/src/server.ts +10 -1
  118. package/src/slack/actions.ts +1 -0
  119. package/src/slack/assistant.ts +2 -0
  120. package/src/slack/handlers.ts +3 -0
  121. package/src/slack/thread-buffer.ts +2 -0
  122. package/src/tasks/worker-follow-up.ts +9 -0
  123. package/src/tests/acp-adapter.test.ts +35 -6
  124. package/src/tests/acp-session-token.test.ts +67 -0
  125. package/src/tests/asset-key-api.test.ts +15 -2
  126. package/src/tests/asset-key-mcp.test.ts +2 -0
  127. package/src/tests/automation-preflight-alert.test.ts +91 -0
  128. package/src/tests/claude-worker-parity.test.ts +3 -0
  129. package/src/tests/config-session-auth.test.ts +143 -0
  130. package/src/tests/defer-task.test.ts +117 -6
  131. package/src/tests/deferred-task-wake.test.ts +456 -0
  132. package/src/tests/heartbeat-reroute-decision.test.ts +1 -0
  133. package/src/tests/http-api-integration.test.ts +33 -4
  134. package/src/tests/mcp-input-ergonomics.test.ts +386 -0
  135. package/src/tests/model-control.test.ts +18 -5
  136. package/src/tests/opencode-adapter.test.ts +2 -2
  137. package/src/tests/promote-draft-task-route.test.ts +4 -0
  138. package/src/tests/prompt-template-session.test.ts +13 -4
  139. package/src/tests/rbac-wire-e2e.test.ts +2 -2
  140. package/src/tests/routing-decision-persistence.test.ts +520 -0
  141. package/src/tests/routing-reason-contract.test.ts +89 -0
  142. package/src/tests/routing-reason-inventory.test.ts +55 -0
  143. package/src/tests/schedule-target-type.test.ts +48 -1
  144. package/src/tests/scheduled-task-required-params-migration.test.ts +182 -0
  145. package/src/tests/scheduled-tasks.test.ts +9 -5
  146. package/src/tests/script-connections.test.ts +4 -0
  147. package/src/tests/script-runs-http.test.ts +31 -0
  148. package/src/tests/scripts-typecheck.test.ts +11 -0
  149. package/src/tests/secret-scrubber.test.ts +12 -0
  150. package/src/tests/send-task-output-schema.test.ts +66 -1
  151. package/src/tests/send-task-requested-by.test.ts +1 -0
  152. package/src/tests/send-task-slack-routing-guard.test.ts +2 -0
  153. package/src/tests/store-progress-blocked-waiting-gate.test.ts +151 -0
  154. package/src/tests/swarm-tool-result-gate.test.ts +24 -0
  155. package/src/tests/task-tool-manifest.test.ts +47 -0
  156. package/src/tests/task-tool-preload-mcp.test.ts +125 -0
  157. package/src/tests/task-tools-ctx.test.ts +2 -0
  158. package/src/tests/tool-output-agent-id.test.ts +1 -0
  159. package/src/tests/ui-followup-lead-delegation.test.ts +6 -1
  160. package/src/tests/workflow-agent-task.test.ts +24 -1
  161. package/src/tests/workflow-engine-v2.test.ts +58 -2
  162. package/src/tools/accept-steer.ts +0 -1
  163. package/src/tools/defer-task.ts +73 -21
  164. package/src/tools/memory-get.ts +10 -3
  165. package/src/tools/memory-rate.ts +19 -7
  166. package/src/tools/memory-search.ts +2 -1
  167. package/src/tools/memory-store.ts +25 -5
  168. package/src/tools/send-task.ts +40 -2
  169. package/src/tools/skills/skill-publish.ts +1 -0
  170. package/src/tools/store-progress.ts +78 -4
  171. package/src/tools/templates.ts +2 -1
  172. package/src/tools/utils.ts +33 -2
  173. package/src/types.ts +14 -1
  174. package/src/utils/acp-session-token.ts +14 -4
  175. package/src/utils/secret-scrubber.ts +2 -0
  176. package/src/utils/task-tool-manifest.ts +33 -0
  177. package/src/workflows/engine.ts +4 -1
  178. package/src/workflows/executors/agent-task.ts +7 -1
  179. package/templates/ai-toolbox.manifest.json +6 -1
  180. package/templates/skills/comms/config.json +18 -0
  181. package/templates/skills/comms/content.md +74 -0
  182. package/templates/skills/comms/files/references/ste-rules.md +73 -0
  183. package/templates/skills/comms/files/references/visual-shapes.md +112 -0
  184. package/templates/skills/swarm-scripts/SKILL.md +3 -2
  185. package/templates/skills/swarm-scripts/content.md +3 -2
@@ -246,6 +246,23 @@ const slackReadNudge = (r: SwarmToolResult): string | undefined => {
246
246
  : undefined;
247
247
  };
248
248
 
249
+ // Sole caller (storeProgressBlockedWaitingNudge) only reaches this once ms is
250
+ // past BLOCKED_WAITING_MIN_ELAPSED_MS (3 minutes), so there's no sub-minute case.
251
+ function formatIdleDuration(ms: number): string {
252
+ const minutes = Math.round(ms / 60_000);
253
+ if (minutes < 60) return `${minutes}m`;
254
+ const hours = Math.floor(minutes / 60);
255
+ const remMinutes = minutes % 60;
256
+ return remMinutes > 0 ? `${hours}h${remMinutes}m` : `${hours}h`;
257
+ }
258
+
259
+ const storeProgressBlockedWaitingNudge = (r: SwarmToolResult): string | undefined => {
260
+ if (!r.ok) return undefined;
261
+ const ms = (r.data as { blockedWaitingElapsedMs?: unknown } | undefined)?.blockedWaitingElapsedMs;
262
+ if (typeof ms !== "number") return undefined;
263
+ return `That reads as blocked-waiting, ${formatIdleDuration(ms)} since your last update — call defer-task so a wake-up task resumes you instead of polling manually.`;
264
+ };
265
+
249
266
  const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined => {
250
267
  if (!r.ok) return undefined;
251
268
  const hint = (r.data as { longScriptTimeoutHint?: unknown } | undefined)?.longScriptTimeoutHint;
@@ -260,6 +277,7 @@ const workflowLongScriptTimeoutNudge = (r: SwarmToolResult): string | undefined
260
277
  export const NUDGES: Record<string, (result: SwarmToolResult) => string | undefined> = {
261
278
  "defer-task": (r) =>
262
279
  r.ok ? "Stop working on this task now; the wake-up task will carry your note." : undefined,
280
+ "store-progress": storeProgressBlockedWaitingNudge,
263
281
  "script-run": scriptRunNudge,
264
282
  "script-upsert": scriptAuthoringNudge,
265
283
  "launch-script-run": scriptAuthoringNudge,
@@ -745,6 +763,13 @@ type ToolConfig<
745
763
  _meta?: Record<string, unknown>;
746
764
  };
747
765
 
766
+ const preloadedToolsByServer = new WeakMap<McpServer, ReadonlySet<string>>();
767
+
768
+ /** Configure before registration. The set belongs to one MCP session, never the fleet. */
769
+ export function setPreloadedTools(server: McpServer, names: readonly string[]): void {
770
+ preloadedToolsByServer.set(server, new Set(names));
771
+ }
772
+
748
773
  /**
749
774
  * Creates a tool registration helper that automatically extracts request info
750
775
  * and passes it as the second parameter to the callback.
@@ -770,10 +795,13 @@ export const createToolRegistrar = (server: McpServer) => {
770
795
  config: ToolConfig<InputArgs, OutputArgs>,
771
796
  cb: ToolCallbackWithInfo<InputArgs>,
772
797
  ) => {
798
+ const toolConfig = preloadedToolsByServer.get(server)?.has(name)
799
+ ? { ...config, _meta: { ...config._meta, "anthropic/alwaysLoad": true } }
800
+ : config;
773
801
  // When inputSchema is undefined, the MCP SDK calls handler(extra) with a single arg.
774
802
  // When inputSchema is defined, it calls handler(args, extra) with two args.
775
803
  if (config.inputSchema === undefined) {
776
- return server.registerTool(name, config, (async (meta: Meta) => {
804
+ return server.registerTool(name, toolConfig, (async (meta: Meta) => {
777
805
  const requestInfo = getRequestInfo(meta);
778
806
  return withSpan(
779
807
  "mcp.tool",
@@ -793,7 +821,10 @@ export const createToolRegistrar = (server: McpServer) => {
793
821
  }) as Parameters<typeof server.registerTool>[2]);
794
822
  }
795
823
 
796
- return server.registerTool(name, config, (async (args: InferInput<InputArgs>, meta: Meta) => {
824
+ return server.registerTool(name, toolConfig, (async (
825
+ args: InferInput<InputArgs>,
826
+ meta: Meta,
827
+ ) => {
797
828
  const requestInfo = getRequestInfo(meta);
798
829
  return withSpan(
799
830
  // Span name carries the tool: a static `mcp.tool` is unreadable in a
package/src/types.ts CHANGED
@@ -340,6 +340,15 @@ export const AgentTaskSourceSchema = z.enum([
340
340
  ]);
341
341
  export type AgentTaskSource = z.infer<typeof AgentTaskSourceSchema>;
342
342
 
343
+ export const RoutingReasonSchema = z.enum([
344
+ "skill",
345
+ "continuity",
346
+ "overflow",
347
+ "human_pinned",
348
+ "reroute_fault",
349
+ ]);
350
+ export type RoutingReason = z.infer<typeof RoutingReasonSchema>;
351
+
343
352
  // ---------------------------------------------------------------------------
344
353
  // Harness Provider
345
354
  // ---------------------------------------------------------------------------
@@ -504,6 +513,8 @@ export const AgentTaskSchema = z
504
513
  title: z.string().optional(), // Human-facing display title override (e.g. session rename); falls back to `task` when unset
505
514
  status: AgentTaskStatusSchema,
506
515
  source: AgentTaskSourceSchema.default("mcp"),
516
+ routingReason: RoutingReasonSchema.optional(),
517
+ routingNote: z.string().max(200).optional(),
507
518
 
508
519
  // Task metadata
509
520
  taskType: z.string().max(50).optional(), // e.g., "bug", "feature", "chore"
@@ -664,6 +675,8 @@ export const CreateTaskOptionsSchema = z.object({
664
675
  agentId: z.string().nullable().optional(),
665
676
  creatorAgentId: z.string().optional(),
666
677
  source: AgentTaskSourceSchema.optional(),
678
+ routingReason: RoutingReasonSchema.optional(),
679
+ routingNote: z.string().max(200).optional(),
667
680
  taskType: z.string().max(50).optional(),
668
681
  tags: z.array(z.string()).optional(),
669
682
  priority: z.number().int().min(0).max(100).optional(),
@@ -1936,7 +1949,7 @@ export const WorkflowNodeSchema = z
1936
1949
  config: z
1937
1950
  .record(z.string(), z.unknown())
1938
1951
  .describe(
1939
- "Executor-specific config. For agent-task: { template, outputSchema?, agentId?, tags?, priority?, dir?, vcsRepo?, model? }. " +
1952
+ "Executor-specific config. For agent-task: { template, outputSchema?, agentId?, routingReason?, routingNote?, tags?, priority?, dir?, vcsRepo?, model? }; configured agentId defaults routingReason to human_pinned. " +
1940
1953
  "For script: { runtime, script, args?, timeout? }. " +
1941
1954
  "For swarm-script: { scriptName, scope?, pinHash?, args?, fsMode?, timeoutMs? (1000-300000) }. " +
1942
1955
  "Agent-task templates and ordinary config values support {{interpolation}} from the node's inputs context, including trigger and declared upstream aliases. " +
@@ -5,6 +5,9 @@
5
5
  * key for a short-lived aseph_ bearer before handing credentials to the
6
6
  * ACP target process.
7
7
  */
8
+ import { scrubSecrets } from "./secret-scrubber";
9
+
10
+ const REVOKE_TIMEOUT_MS = 5000;
8
11
 
9
12
  /** Wall-clock TTL for a session token: 24 hours. Long enough for any realistic
10
13
  * ACP session; the token is actively revoked when the session finishes anyway. */
@@ -38,7 +41,8 @@ export async function mintAcpSessionToken(
38
41
 
39
42
  /**
40
43
  * Ask the swarm API to revoke a previously-minted `aseph_` token. Best-effort:
41
- * errors are logged but never propagated to the caller.
44
+ * the request is bounded, and errors are logged but never propagated to the
45
+ * caller. ACP completion does not wait for this cleanup.
42
46
  */
43
47
  export async function revokeAcpSessionToken(
44
48
  apiUrl: string,
@@ -46,11 +50,17 @@ export async function revokeAcpSessionToken(
46
50
  tokenId: string,
47
51
  ): Promise<void> {
48
52
  try {
49
- await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens/${tokenId}`, {
53
+ const res = await fetch(`${apiUrl.replace(/\/+$/, "")}/api/sessions/tokens/${tokenId}`, {
50
54
  method: "DELETE",
51
55
  headers: { Authorization: `Bearer ${apiKey}` },
56
+ signal: AbortSignal.timeout(REVOKE_TIMEOUT_MS),
52
57
  });
53
- } catch {
54
- // best-effort — token will expire on its own
58
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
59
+ } catch (error) {
60
+ console.warn(
61
+ scrubSecrets(
62
+ `[acp] Session token revoke failed: ${error instanceof Error ? error.message : String(error)}`,
63
+ ),
64
+ );
55
65
  }
56
66
  }
@@ -104,6 +104,8 @@ const TOKEN_REGEXES: ReadonlyArray<{ name: string; re: RegExp }> = [
104
104
  { name: "github_pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
105
105
  // GitHub classic/OAuth tokens (ghp_, gho_, ghu_, ghs_, ghr_)
106
106
  { name: "github_token", re: new RegExp(String.raw`${TB}gh[pousr]_[A-Za-z0-9]{20,}\b`, "g") },
107
+ // ACP ephemeral session tokens (base62 payload)
108
+ { name: "acp_session_token", re: new RegExp(String.raw`${TB}aseph_[A-Za-z0-9]{20,}\b`, "g") },
107
109
  // GitLab personal access tokens
108
110
  { name: "gitlab_pat", re: new RegExp(String.raw`${TB}glpat-[A-Za-z0-9_-]{20,}\b`, "g") },
109
111
  // Anthropic API keys (must match before the generic sk- rule below)
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { ALL_TOOLS } from "../tools/tool-config";
3
+
4
+ const toolNames = z
5
+ .array(z.string().refine((name) => ALL_TOOLS.has(name), "Unknown swarm tool"))
6
+ .max(16);
7
+
8
+ /** Exact task-type and schedule keys; a schedule entry overrides its task type. */
9
+ export const taskToolManifestSchema = z.strictObject({
10
+ taskTypes: z.record(z.string().min(1).max(50), toolNames).optional(),
11
+ schedules: z.record(z.uuid(), toolNames).optional(),
12
+ });
13
+
14
+ export function parseTaskToolManifest(value: string) {
15
+ return taskToolManifestSchema.parse(JSON.parse(value));
16
+ }
17
+
18
+ export function selectTaskTools(
19
+ manifest: z.infer<typeof taskToolManifestSchema>,
20
+ task: { taskType?: string; scheduleId?: string; slackChannelId?: string },
21
+ ): string[] {
22
+ const fromSchedule =
23
+ task.scheduleId && Object.hasOwn(manifest.schedules ?? {}, task.scheduleId)
24
+ ? manifest.schedules?.[task.scheduleId]
25
+ : undefined;
26
+ const fromType =
27
+ task.taskType && Object.hasOwn(manifest.taskTypes ?? {}, task.taskType)
28
+ ? manifest.taskTypes?.[task.taskType]
29
+ : undefined;
30
+ return [...new Set(fromSchedule ?? fromType ?? [])].filter(
31
+ (name) => !name.startsWith("slack-") || Boolean(task.slackChannelId),
32
+ );
33
+ }
@@ -1,3 +1,4 @@
1
+ import { notifyAutomationPreflightFailure } from "../automation-preflight-alert";
1
2
  import {
2
3
  getAutomationSetupStates,
3
4
  preflightAutomation,
@@ -91,13 +92,15 @@ export async function startWorkflowExecution(
91
92
  await getAutomationSetupStates(),
92
93
  );
93
94
  if (preflight.state === "needs_setup") {
94
- return await recordWorkflowPreflightFailure({
95
+ const { runId, recorded } = await recordWorkflowPreflightFailure({
95
96
  workflowId: workflow.id,
96
97
  triggerType: options.triggerType ?? "manual",
97
98
  triggerData,
98
99
  failureReason: preflight.failureReason!,
99
100
  createdBy: options.requestedByUserId,
100
101
  });
102
+ if (recorded) await notifyAutomationPreflightFailure(preflight);
103
+ return runId;
101
104
  }
102
105
 
103
106
  // Templates can consume install params outside the graph definition (most
@@ -6,6 +6,7 @@ import {
6
6
  FollowUpConfigSchema,
7
7
  ModelTierSchema,
8
8
  ReasoningEffortSchema,
9
+ RoutingReasonSchema,
9
10
  splitLegacyModelAlias,
10
11
  } from "../../types";
11
12
  import type { ExecutorResult } from "./base";
@@ -13,11 +14,13 @@ import { BaseExecutor } from "./base";
13
14
 
14
15
  // ─── Config / Output Schemas ────────────────────────────────
15
16
 
16
- const AgentTaskConfigSchema = z.object({
17
+ export const AgentTaskConfigSchema = z.object({
17
18
  template: z.string(),
18
19
  // Plain string, NOT .uuid(): agents may join with custom IDs (AGENT_ID env /
19
20
  // join-swarm agentId), so a UUID filter would reject legitimate agents.
20
21
  agentId: z.string().optional(),
22
+ routingReason: RoutingReasonSchema.optional(),
23
+ routingNote: z.string().max(200).optional(),
21
24
  tags: z.array(z.string()).optional(),
22
25
  priority: z.number().int().min(0).max(100).optional(),
23
26
  offerMode: z.boolean().optional(),
@@ -98,6 +101,9 @@ export class AgentTaskExecutor extends BaseExecutor<
98
101
  {
99
102
  key: effectiveKey,
100
103
  agentId: config.agentId ?? null,
104
+ // A configured workflow target is an author pin, including existing definitions.
105
+ routingReason: config.routingReason ?? (config.agentId ? "human_pinned" : undefined),
106
+ routingNote: config.routingNote,
101
107
  source: "workflow",
102
108
  tags: config.tags,
103
109
  priority: config.priority,
@@ -1,5 +1,5 @@
1
1
  {
2
- "commit": "a4e5e77923eaabc644f8290396ef341d041cdce1",
2
+ "commit": "9748aabf7252c258378302240d73f7516d113777",
3
3
  "excludedSkills": [
4
4
  "feedback"
5
5
  ],
@@ -11,6 +11,10 @@
11
11
  "templates/skills/brainstorming/files/template.md": "ae0aabc02a307db0550f9026aca33fb471608c31f7a0b65ecc781073f17cc953",
12
12
  "templates/skills/code-reviewing/config.json": "dde1298b15d55428b898a0421033d1aa140ef152d335d49345059e29deab96f2",
13
13
  "templates/skills/code-reviewing/content.md": "85e9aa3b276384ab3f3bc570aece2e8c103a8614c19129378cf4dfec3457a18b",
14
+ "templates/skills/comms/config.json": "5561ee129f68a03e89b21458afebcd79afd2a8c177c0ba5dad00ffa21ee57bc3",
15
+ "templates/skills/comms/content.md": "bd1d33b4af13eb36dfd87552bb35ecd200ac0a0dd40d409b50dff448708ce5ea",
16
+ "templates/skills/comms/files/references/ste-rules.md": "6ee2ed5d6b49d2e421b6c9d22310a5b589fa1abe81de06213e4b89fbc1271e74",
17
+ "templates/skills/comms/files/references/visual-shapes.md": "5e07e9250ad4cdc877af2281525185f0de89c01f90a077463a2c77c8a7b998e8",
14
18
  "templates/skills/delegate-work/config.json": "5b1542eb0be359bb8a17117fcf87917e0607d1b939ae3266ddffacdc14efb281",
15
19
  "templates/skills/delegate-work/content.md": "c9562bd17846d3595d40172ecfdc6f85bb99eaf23597eb9d5aa9635d5c14d079",
16
20
  "templates/skills/delegate-work/files/scripts/codex-exec.sh": "c2222986c46ac608403f28f201230cb34c7d7ac41a2262dc2c8dbd99c8645dbb",
@@ -74,6 +78,7 @@
74
78
  "ask-user",
75
79
  "brainstorming",
76
80
  "code-reviewing",
81
+ "comms",
77
82
  "delegate-work",
78
83
  "design-docs",
79
84
  "engineering-standards",
@@ -0,0 +1,18 @@
1
+ {
2
+ "category": "skills",
3
+ "description": "Re-express something so it lands — re-explain your last reply in plain casual language, show the current topic visually (diagram, code-shape sketch, HTML artifact), do both at once, or rewrite artifact text into unambiguous ASD-STE100 English. Use when the user runs /desplega:comms, types /bro, says 'say it simpler', 'bro what', 'I don't follow', 'show me', 'draw this', 'visualize', 'disambiguate this', or 'STE100 rewrite'. Not for creative or marketing copy.",
4
+ "displayName": "Comms",
5
+ "kind": "skill",
6
+ "name": "comms",
7
+ "placeholders": [],
8
+ "runAllSeedersCandidate": true,
9
+ "slug": "comms",
10
+ "systemDefault": true,
11
+ "tags": [
12
+ "ai-toolbox",
13
+ "agents",
14
+ "workflow"
15
+ ],
16
+ "title": "Comms",
17
+ "version": "1.0.0"
18
+ }
@@ -0,0 +1,74 @@
1
+ # /comms — make it land
2
+
3
+ Merged from three MIT sources: [bro-skill](https://github.com/luchasarie/bro-skill) (Simpler), the local show-me skill (Visual), and [asd-ste100-skill](https://github.com/desplega-ai/asd-ste100-skill) (Precise).
4
+
5
+ One skill, three base modes plus a joint one. Pick from the **target** of the request, not from the wording alone.
6
+
7
+ ## Dispatch
8
+
9
+ | Target of the request | Mode |
10
+ |---|---|
11
+ | Your own previous message — "simpler", "bro what", "didn't get it", "rephrase" | **Simpler** |
12
+ | A concept, flow, architecture, or change — "show me", "draw", "diagram", "what does this look like" | **Visual** |
13
+ | Your previous message AND it describes structure (a flow, a tree, an architecture, a sequence) — or the user asks for both ("simpler, and draw it") | **Joint** |
14
+ | Artifact text a machine or reader must parse without a back-channel — tool description, error message, prompt, doc paragraph — "disambiguate", "rewrite", "STE" | **Precise** |
15
+
16
+ Dispatch rules:
17
+
18
+ - If the request names a mode, obey it.
19
+ - If the target is your previous message and it is plain prose, use Simpler. If it describes structure, prefer Joint — a small visual usually lands faster than more words.
20
+ - Never mix registers: no casual flavor in Precise output, no STE flatness in Simpler output. Precise never combines with the other modes.
21
+ - If there is nothing to re-express (no previous message, no artifact, no topic), say so in one line.
22
+
23
+ ## Mode: Simpler
24
+
25
+ Re-explain YOUR most recent assistant message like you're explaining it to a smart friend over a beer.
26
+
27
+ 1. **Re-explain, don't re-answer.** Never answer a new question, never add new information, never use tools. You are only re-expressing what you already said.
28
+ 2. **Simpler, not necessarily shorter.** The goal is "impossible to misunderstand", not "fewer words". Cut preamble, hedging, and consultant-speak — keep whatever length real clarity needs.
29
+ 3. **Facts survive verbatim.** Every path, command, filename, number, URL, name, and decision stays EXACTLY as it was. Simplify the explanation around the facts, never the facts themselves.
30
+ 4. **Light casual flavor.** Direct and informal ("basically...", "the point is...", "ok so..."). A touch of personality — don't turn it into a meme.
31
+ 5. **Same language.** If the original message was in another language, the simpler version stays in that language.
32
+ 6. **Flatten structure.** Drop headers and ceremony. Tables become plain sentences. Keep a short list only if the original genuinely had multiple parts.
33
+
34
+ ## Mode: Visual
35
+
36
+ Help the user understand the current topic visually. Skip the preamble and keep prose brief. Pick the smallest view that makes the key point clear:
37
+
38
+ - **Pseudocode** for logic or an algorithm.
39
+ - **Call tree** for runtime control flow.
40
+ - **Component tree** for UI structure, including the state and module boundaries that matter.
41
+ - **Shallow file tree** for file responsibility or a broad refactor.
42
+ - **Mermaid** for component interaction, control flow, or data flow.
43
+ - **`diff`** when the point is what changes and the surrounding shape already exists — diff the tree or pseudocode itself, not raw code. Match the diff shape to the topic.
44
+ - **Whole code block** when most of it is new, when omitted context would hide ownership or order, or when the user needs a copyable target shape.
45
+ - **One focused HTML file** for a visual UI, layout, state comparison, or concept too dense for Mermaid — a diagram, infographic, or short slide deck. Match the product's colors, type, spacing, and components; use real labels and data; support desktop and mobile. Then `Bash(open path/to/comms-{description}.html)`.
46
+
47
+ Place each visual next to the short text it supports. Keep only the calls, files, props, states, and boundaries needed to answer the current question. Use one shape, maybe several — never all. Concrete example shapes: `references/visual-shapes.md`.
48
+
49
+ **Delivery:** write the visual explanation to `/tmp/YYYY-MM-DD-HHMM-comms-<topic>.md` and open it with `file-review` (via Bash, `run_in_background: true`, `timeout: 600000`) — it rich-renders the markdown and lets the user leave inline comments; process their comments when the window closes. If `file-review` is not on PATH, put the visual in chat instead. Keep `Bash(open ...)` for HTML artifacts. Skip file-review only for a single small visual that reads fine in chat.
50
+
51
+ ## Mode: Joint
52
+
53
+ Simpler + Visual in one document: Simpler-mode prose with Visual-mode shapes placed right after the sentence each one supports. Follow both rule sets — casual register for the words, smallest-view discipline for the visuals. Deliver via file-review, like Visual mode.
54
+
55
+ ## Mode: Precise
56
+
57
+ Rewrite the given text under ASD-STE100 structural discipline so no reader — human or agent — can misparse it. Full rule tables, sub-mode detail, and process: `references/ste-rules.md`.
58
+
59
+ Core rules:
60
+
61
+ - Active voice. Simple tenses ("we received", not "we have received").
62
+ - One instruction per sentence. ≤20 words for instructions, ≤25 for descriptions.
63
+ - No semicolons. No phrasal verbs ("start", not "spin up"). No noun stacks over 3 words. No dropped words.
64
+ - One name per thing — never rotate synonyms for the same referent.
65
+ - Verb over nominalization ("analyze the log", not "perform an analysis of the log").
66
+ - **Keep every hedge and qualifier.** "May have failed" never becomes "failed". A rewrite that changes confidence is a different claim, not a simplification.
67
+ - Never add a fact the source did not state.
68
+
69
+ Two sub-modes:
70
+
71
+ - **Strict** — tool descriptions, error messages, prompts, procedures, safety text: every rule.
72
+ - **STE-flavored** — READMEs, PR text, explanatory prose: structural rules in full, lexical rules advisory.
73
+
74
+ Output: the rewritten text and nothing else — no preamble, no mode announcement, no change summary. If you deliberately kept a longer phrasing to preserve precision, add one line prefixed `Kept as-is:`. When asked to "show the diff" or "explain the changes", output the before/after rule table from `references/ste-rules.md` instead.
@@ -0,0 +1,73 @@
1
+ # Precise mode — ASD-STE100 rules
2
+
3
+ Condensed from [asd-ste100-skill](https://github.com/desplega-ai/asd-ste100-skill) (MIT), which encodes the rule categories of ASD-STE100 Issue 9 (Jan 2025).
4
+
5
+ This encodes the standard's rule *categories*, not ASD's ~900-word approved dictionary (free to obtain, not free to redistribute). Structural rules are checkable from the description alone — apply them with confidence. Lexical rules depend on the dictionary — apply them as a direction of travel, and never imply dictionary compliance.
6
+
7
+ ## Structural rules — apply these
8
+
9
+ | Rule | Do | Don't |
10
+ |---|---|---|
11
+ | Active voice | "The agent deletes the file." | "The file is deleted (by the agent)." — unless the actor is genuinely unknown or irrelevant |
12
+ | No phrasal verbs | "Remove the panel." / "Start the job." | "Take off the panel." / "Spin up the job." |
13
+ | One instruction per sentence | "Open the file. Read line 3." | "Open the file and read line 3, then check if it matches." |
14
+ | Sentence length | ≤20 words for instructions, ≤25 for descriptions | Long compound/subordinate-clause sentences |
15
+ | No semicolons | Split into separate sentences | Any semicolon at all |
16
+ | Noun clusters | ≤3 words stacked as a noun phrase | 4+ word noun stacks ("high pressure fuel pump inlet valve assembly") |
17
+ | No ellipsis | Keep subject, verb, and article explicit | Drop words to save space ("Files not backed up will be lost" → ambiguous which files) |
18
+ | Keep modality | "The request **may have** failed." stays "may have" | Promote a hedge to a fact, or invent a certainty the source did not state |
19
+ | Paragraph limits | One topic per paragraph, ≤6 sentences | Multi-topic paragraphs |
20
+ | Lists for sequences | Numbered/bulleted list for 3+ steps or conditions | A sequence buried in one prose sentence |
21
+
22
+ ## Lexical rules — direction of travel only
23
+
24
+ | Rule | Do | Don't |
25
+ |---|---|---|
26
+ | One word, one meaning | Pick one verb for one action and reuse it every time | Rotate synonyms ("check"/"verify"/"confirm") for the same action |
27
+ | One part of speech per word | "Apply oil to the valve" (oil = noun) | "Oil the valve" (oil = verb) |
28
+ | Verb, not noun | "Analyze the log." | "Perform an analysis of the log." |
29
+ | Domain terms | Keep needed technical terms; define each once if not common English | Jargon never defined |
30
+
31
+ ## Simple tenses — one exception
32
+
33
+ STE permits infinitive, imperative, simple present, simple past, simple future, and past participle as adjective. It excludes present perfect: "we received the report", not "we have received the report". Exception: where the compound form carries information the simple form cannot — current relevance ("the job has completed" = output available now), or a hedge like "may have failed" — keep it and flag the departure.
34
+
35
+ ## Scan checklist
36
+
37
+ Scan for all six before rewriting. Each is mechanical — you can point at the exact word that breaks it.
38
+
39
+ 1. **Synonym rotation** — the same thing has several names ("the user", "the customer", "the client"). Fix: one name, every time.
40
+ 2. **Hedge stacking** — qualifiers pile up until nothing is asserted ("it is important to note that this may potentially help to improve"). Fix: state the claim or delete it.
41
+ 3. **Nominalization** — an action frozen into a noun ("perform an analysis of"). Fix: use the verb.
42
+ 4. **Marketing adjectives** — seamless, robust, powerful, blazing-fast. Fix: delete, or replace with the measurement that earns the claim.
43
+ 5. **Run-on sentences** — ideas joined by semicolons or em dashes. Fix: one idea per sentence.
44
+ 6. **Soft phrasal verbs** — spin up, reach out, dive into, kick off. Fix: the single plain verb (start, contact, read, begin).
45
+
46
+ ## Process
47
+
48
+ 1. Pick the sub-mode (Strict or STE-flavored). Keep the choice internal unless asked.
49
+ 2. Read the input once for meaning before rewriting anything.
50
+ 3. Walk it sentence by sentence; flag every violation. In STE-flavored, flag lexical rules but do not enforce them.
51
+ 4. Rewrite each flagged sentence, preserving the original meaning exactly. If a rewrite would drop necessary precision (a safety condition, scope qualifier, number), keep the longer phrasing and flag it. Check modality before committing — a shorter sentence that upgrades a hedge to a fact is a different claim. Never add a fact the source did not state.
52
+ 5. Output the rewritten text alone. If the input already complies, say so — do not force changes.
53
+
54
+ ## Diff table format (on request)
55
+
56
+ When asked to "show the diff" / "which rules did it break" / "before/after":
57
+
58
+ ```markdown
59
+ | Rule violated | Original | Simplified |
60
+ |---|---|---|
61
+ | Present perfect tense | "We have received your request." | "We received your request." |
62
+ | Noun cluster (4+ words) | "the agent task queue priority handler" | "the handler that sets task-queue priority" |
63
+
64
+ Mode: Strict. 7 violations found.
65
+ ```
66
+
67
+ Follow with one line naming anything deliberately not simplified, and why.
68
+
69
+ ## Boundaries
70
+
71
+ - Not a certified STE authoring tool — a clarity tool inspired by the standard. For aerospace-grade compliance, check word-by-word against the official dictionary from [asd-ste100.org](https://www.asd-ste100.org/STE_downloads.html).
72
+ - Fixes form, not substance: a hollow paragraph rewritten under these rules is a clean hollow paragraph. Say so instead of polishing it.
73
+ - Stop when the sentence is unambiguous, not when it is shortest.
@@ -0,0 +1,112 @@
1
+ # Visual mode — example shapes
2
+
3
+ Concrete examples for each view the Visual mode can produce. Source: the show-me skill (bundled into this skill).
4
+
5
+ Logic or an algorithm as pseudocode:
6
+
7
+ ```text
8
+ on(save)
9
+ if content is unchanged
10
+ return cached result
11
+ write new content
12
+ return fresh result
13
+ ```
14
+
15
+ Runtime control flow as a call tree:
16
+
17
+ ```text
18
+ submitForm
19
+ createSession
20
+ persistPrompt
21
+ launchAgent
22
+ navigateToSession
23
+ ```
24
+
25
+ UI structure as a component tree, including state and module boundaries that matter:
26
+
27
+ ```tsx
28
+ <SessionPage> (apps/example/src/routes/session.tsx)
29
+ useSessionEvents()
30
+ <SessionToolbar>
31
+ <RunSkillButton> (packages/ui)
32
+ ```
33
+
34
+ File responsibility or a broad refactor as a shallow file tree:
35
+
36
+ ```text
37
+ src/
38
+ ├── commands/ # parses user actions
39
+ ├── sessions/ # owns session state
40
+ └── transport/ # sends API requests
41
+ ```
42
+
43
+ Component interaction, control flow, or data flow with Mermaid:
44
+
45
+ ```mermaid
46
+ sequenceDiagram
47
+ participant User
48
+ participant UI
49
+ participant Daemon
50
+ User->>UI: choose command
51
+ UI->>Daemon: send expanded prompt
52
+ Daemon-->>UI: stream result
53
+ ```
54
+
55
+ `diff` shapes — match the diff to the topic.
56
+
57
+ Component change:
58
+
59
+ ```diff
60
+ <SessionPage>
61
+ useSessionEvents()
62
+ <SessionToolbar>
63
+ + <RunSkillButton />
64
+ <SessionTimeline>
65
+ + <SkillResultCard />
66
+ ```
67
+
68
+ File-layout change:
69
+
70
+ ```diff
71
+ src/
72
+ ├── commands/
73
+ +│ └── show-me.ts # expands the slash command
74
+ ├── sessions/
75
+ -└── transport.ts
76
+ +└── transport/
77
+ + ├── client.ts
78
+ + └── stream.ts
79
+ ```
80
+
81
+ Call-tree change:
82
+
83
+ ```diff
84
+ submitForm
85
+ createSession
86
+ persistPrompt
87
+ + expandSkillMention
88
+ launchAgent
89
+ - navigateToSession
90
+ + navigateToSession
91
+ + subscribeToEvents
92
+ ```
93
+
94
+ State or control-flow change:
95
+
96
+ ```diff
97
+ on(save)
98
+ - write content
99
+ + if content is unchanged
100
+ + return cached result
101
+ + write new content
102
+ + invalidate cache
103
+ ```
104
+
105
+ Whole block when most of it is new or a copyable target shape is needed:
106
+
107
+ ```ts
108
+ function expandSkill(command: string): string {
109
+ const skillName = command.slice(1)
110
+ return `use the ${skillName} skill`
111
+ }
112
+ ```
@@ -37,7 +37,7 @@ The swarm ships named scripts at global scope. Each one replaces a multi-step to
37
37
  | `task-context-gathering` | `{ taskId, queries: [...] }` | the task plus a deduplicated multi-query memory recall |
38
38
  | `smart-recall` | `{ queries: [...] }` | multi-query memory recall without the task |
39
39
  | `memory-dedup-check` | `{ text, threshold? }` | near-duplicates before you store a memory |
40
- | `delegate` | `{ agentName, task, parentTaskId? }` | a subtask for an agent by name, returns `{ taskId }` |
40
+ | `delegate` | `{ agentName, task, routingReason, parentTaskId? }` | a subtask for an agent by name, returns `{ taskId }` |
41
41
  | `wait-for-task` | `{ taskId }` | waits up to about 25 s for a terminal state, returns `{ done, status, output }`; call again while `done` is false |
42
42
  | `get-child-outputs` | `{ parentTaskId }` | every child with status and output |
43
43
  | `complete-task` | `{ taskId, output }` | finish a task from inside a script |
@@ -74,6 +74,7 @@ export default async function (args: z.infer<typeof argsSchema>, ctx: ScriptCont
74
74
  ### What `ctx` holds
75
75
 
76
76
  - `ctx.swarm.*`: the swarm SDK. `task_get`, `task_send`, `task_storeProgress`, `task_action`, `task_list`, `slack_reply`, `memory_search`, `memory_store`, `kv_get`, `kv_getOrNull`, `kv_set`, `kv_delete`, `kv_incr`, `kv_list`, `swarm_get`, `agent_info`, `db_query`, and more. `kv_getOrNull` returns the entry, `null` on a missing key, and throws on other errors.
77
+ - `ctx.swarm.task_send` requires `routingReason` whenever `agentId` is present. Use `human_pinned` for an explicit/configured choice, `skill` for a role or specialization match, `continuity` for the same worker/session, `reroute_fault` for a fault handoff, or `overflow` for capacity/pool escalation. Omit both `agentId` and routing fields for the pool.
77
78
  - `ctx.swarm.config`: `apiKey`, `agentId`, `mcpBaseUrl`, and `ctx.swarm.config.get("KEY")` for user config values. All are `Redacted` wrappers that stringify to `<redacted>`. Never unwrap one into a return value, a log line, or a request body you build by hand.
78
79
  - `ctx.api.<slug>` and `ctx.mcp.<slug>`: typed clients for registered connections. They exist only for registered connections. Introspect with `Object.keys(ctx.api ?? {})` and `Object.keys(ctx.mcp ?? {})`.
79
80
  - `ctx.stdlib`: `fetch`, `fetchJson` (retries, 30 s timeout), `grep`, `glob`, `table`, `Redacted`.
@@ -81,7 +82,7 @@ export default async function (args: z.infer<typeof argsSchema>, ctx: ScriptCont
81
82
 
82
83
  ### Durable workflow scripts
83
84
 
84
- `launch-script-run` runs a script as a durable, journaled run with a different `ctx`: `ctx.run` (`id`, `agentId`, `args`) and `ctx.step.rawLlm(label, config)`, `ctx.step.agentTask(label, config)`, `ctx.step.swarmScript(label, config)`, plus `ctx.swarm.*`, `ctx.stdlib`, `ctx.logger`. Durable runs have no `ctx.api`, no `ctx.mcp`, and no `ctx.swarm.config`. Call a connection from an inner script through `ctx.step.swarmScript`. See the `script-workflows` skill.
85
+ `launch-script-run` runs a script as a durable, journaled run with a different `ctx`: `ctx.run` (`id`, `agentId`, `args`) and `ctx.step.rawLlm(label, config)`, `ctx.step.agentTask(label, config)`, `ctx.step.swarmScript(label, config)`, plus `ctx.swarm.*`, `ctx.stdlib`, `ctx.logger`. Durable runs have no `ctx.api`, no `ctx.mcp`, and no `ctx.swarm.config`. A configured `ctx.step.agentTask` `agentId` records `human_pinned` unless `routingReason` is supplied. Call a connection from an inner script through `ctx.step.swarmScript`. See the `script-workflows` skill.
85
86
 
86
87
  ## Inline script pattern
87
88
 
@@ -32,7 +32,7 @@ The swarm ships named scripts at global scope. Each one replaces a multi-step to
32
32
  | `task-context-gathering` | `{ taskId, queries: [...] }` | the task plus a deduplicated multi-query memory recall |
33
33
  | `smart-recall` | `{ queries: [...] }` | multi-query memory recall without the task |
34
34
  | `memory-dedup-check` | `{ text, threshold? }` | near-duplicates before you store a memory |
35
- | `delegate` | `{ agentName, task, parentTaskId? }` | a subtask for an agent by name, returns `{ taskId }` |
35
+ | `delegate` | `{ agentName, task, routingReason, parentTaskId? }` | a subtask for an agent by name, returns `{ taskId }` |
36
36
  | `wait-for-task` | `{ taskId }` | waits up to about 25 s for a terminal state, returns `{ done, status, output }`; call again while `done` is false |
37
37
  | `get-child-outputs` | `{ parentTaskId }` | every child with status and output |
38
38
  | `complete-task` | `{ taskId, output }` | finish a task from inside a script |
@@ -69,6 +69,7 @@ export default async function (args: z.infer<typeof argsSchema>, ctx: ScriptCont
69
69
  ### What `ctx` holds
70
70
 
71
71
  - `ctx.swarm.*`: the swarm SDK. `task_get`, `task_send`, `task_storeProgress`, `task_action`, `task_list`, `slack_reply`, `memory_search`, `memory_store`, `kv_get`, `kv_getOrNull`, `kv_set`, `kv_delete`, `kv_incr`, `kv_list`, `swarm_get`, `agent_info`, `db_query`, and more. `kv_getOrNull` returns the entry, `null` on a missing key, and throws on other errors.
72
+ - `ctx.swarm.task_send` requires `routingReason` whenever `agentId` is present. Use `human_pinned` for an explicit/configured choice, `skill` for a role or specialization match, `continuity` for the same worker/session, `reroute_fault` for a fault handoff, or `overflow` for capacity/pool escalation. Omit both `agentId` and routing fields for the pool.
72
73
  - `ctx.swarm.config`: `apiKey`, `agentId`, `mcpBaseUrl`, and `ctx.swarm.config.get("KEY")` for user config values. All are `Redacted` wrappers that stringify to `<redacted>`. Never unwrap one into a return value, a log line, or a request body you build by hand.
73
74
  - `ctx.api.<slug>` and `ctx.mcp.<slug>`: typed clients for registered connections. They exist only for registered connections. Introspect with `Object.keys(ctx.api ?? {})` and `Object.keys(ctx.mcp ?? {})`.
74
75
  - `ctx.stdlib`: `fetch`, `fetchJson` (retries, 30 s timeout), `grep`, `glob`, `table`, `Redacted`.
@@ -76,7 +77,7 @@ export default async function (args: z.infer<typeof argsSchema>, ctx: ScriptCont
76
77
 
77
78
  ### Durable workflow scripts
78
79
 
79
- `launch-script-run` runs a script as a durable, journaled run with a different `ctx`: `ctx.run` (`id`, `agentId`, `args`) and `ctx.step.rawLlm(label, config)`, `ctx.step.agentTask(label, config)`, `ctx.step.swarmScript(label, config)`, plus `ctx.swarm.*`, `ctx.stdlib`, `ctx.logger`. Durable runs have no `ctx.api`, no `ctx.mcp`, and no `ctx.swarm.config`. Call a connection from an inner script through `ctx.step.swarmScript`. See the `script-workflows` skill.
80
+ `launch-script-run` runs a script as a durable, journaled run with a different `ctx`: `ctx.run` (`id`, `agentId`, `args`) and `ctx.step.rawLlm(label, config)`, `ctx.step.agentTask(label, config)`, `ctx.step.swarmScript(label, config)`, plus `ctx.swarm.*`, `ctx.stdlib`, `ctx.logger`. Durable runs have no `ctx.api`, no `ctx.mcp`, and no `ctx.swarm.config`. A configured `ctx.step.agentTask` `agentId` records `human_pinned` unless `routingReason` is supplied. Call a connection from an inner script through `ctx.step.swarmScript`. See the `script-workflows` skill.
80
81
 
81
82
  ## Inline script pattern
82
83