@bastani/atomic 0.9.43 → 0.9.44-alpha.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 (117) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +12 -0
  3. package/dist/builtin/intercom/broker/broker.ts +31 -7
  4. package/dist/builtin/intercom/broker/client.ts +65 -0
  5. package/dist/builtin/intercom/index.bundle.mjs +227 -95
  6. package/dist/builtin/intercom/live-route-refusal.ts +20 -0
  7. package/dist/builtin/intercom/package.json +1 -1
  8. package/dist/builtin/intercom/skills/intercom/SKILL.md +10 -6
  9. package/dist/builtin/intercom/types.ts +20 -1
  10. package/dist/builtin/mcp/CHANGELOG.md +7 -0
  11. package/dist/builtin/mcp/package.json +1 -1
  12. package/dist/builtin/subagents/CHANGELOG.md +9 -0
  13. package/dist/builtin/subagents/README.md +8 -8
  14. package/dist/builtin/subagents/agents/worker.md +1 -1
  15. package/dist/builtin/subagents/package.json +1 -1
  16. package/dist/builtin/subagents/skills/agent-browser/SKILL.md +1 -1
  17. package/dist/builtin/subagents/skills/cua-driver/EMBEDDING.md +21 -2
  18. package/dist/builtin/subagents/skills/cua-driver/LINUX.md +2 -2
  19. package/dist/builtin/subagents/skills/cua-driver/MACOS.md +342 -10
  20. package/dist/builtin/subagents/skills/cua-driver/README.md +4 -3
  21. package/dist/builtin/subagents/skills/cua-driver/RUNTIME.md +3 -1
  22. package/dist/builtin/subagents/skills/cua-driver/SKILL.md +7 -4
  23. package/dist/builtin/subagents/skills/cua-driver/WINDOWS.md +6 -5
  24. package/dist/builtin/subagents/skills/cua-driver/WORKFLOW.md +15 -0
  25. package/dist/builtin/subagents/skills/herdr/SKILL.md +22 -10
  26. package/dist/builtin/subagents/skills/subagent/SKILL.md +8 -5
  27. package/dist/builtin/subagents/src/extension/index.bundle.mjs +6 -6
  28. package/dist/builtin/web-access/package.json +1 -1
  29. package/dist/builtin/workflows/CHANGELOG.md +14 -0
  30. package/dist/builtin/workflows/README.md +1 -1
  31. package/dist/builtin/workflows/builtin/{chunk-frqxd4d1.js → chunk-k8d7ehjx.js} +22 -0
  32. package/dist/builtin/workflows/builtin/{chunk-3s95f7ry.js → chunk-qxpymnaf.js} +1 -1
  33. package/dist/builtin/workflows/builtin/{chunk-edhf30ef.js → chunk-zjecnjxx.js} +1 -1
  34. package/dist/builtin/workflows/builtin/goal.js +2 -2
  35. package/dist/builtin/workflows/builtin/index.js +3 -3
  36. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  37. package/dist/builtin/workflows/package.json +1 -1
  38. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1750 -598
  39. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +1786 -600
  40. package/dist/builtin/workflows/src/index.js +1802 -650
  41. package/dist/builtin/workflows/src/local-worker-entry.mjs +1786 -600
  42. package/dist/cli/args.js +1 -1
  43. package/dist/cli/args.js.map +1 -1
  44. package/dist/core/exec.d.ts.map +1 -1
  45. package/dist/core/exec.js +11 -2
  46. package/dist/core/exec.js.map +1 -1
  47. package/dist/core/package-manager-resolver.d.ts.map +1 -1
  48. package/dist/core/package-manager-resolver.js +25 -14
  49. package/dist/core/package-manager-resolver.js.map +1 -1
  50. package/dist/core/package-manager-types.d.ts +8 -0
  51. package/dist/core/package-manager-types.d.ts.map +1 -1
  52. package/dist/core/package-manager-types.js.map +1 -1
  53. package/dist/core/package-manager.d.ts +1 -1
  54. package/dist/core/package-manager.d.ts.map +1 -1
  55. package/dist/core/package-manager.js.map +1 -1
  56. package/dist/core/resource-loader-extension-warnings.d.ts +4 -1
  57. package/dist/core/resource-loader-extension-warnings.d.ts.map +1 -1
  58. package/dist/core/resource-loader-extension-warnings.js +15 -0
  59. package/dist/core/resource-loader-extension-warnings.js.map +1 -1
  60. package/dist/core/resource-loader-package-resources.d.ts.map +1 -1
  61. package/dist/core/resource-loader-package-resources.js +1 -0
  62. package/dist/core/resource-loader-package-resources.js.map +1 -1
  63. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  64. package/dist/core/resource-loader-reload.js +5 -3
  65. package/dist/core/resource-loader-reload.js.map +1 -1
  66. package/dist/extensions/mcp/client/oauth/flow.d.ts.map +1 -1
  67. package/dist/extensions/mcp/client/oauth/flow.js +4 -1
  68. package/dist/extensions/mcp/client/oauth/flow.js.map +1 -1
  69. package/dist/extensions/mcp/client/oauth/types.d.ts +1 -0
  70. package/dist/extensions/mcp/client/oauth/types.d.ts.map +1 -1
  71. package/dist/extensions/mcp/client/oauth/types.js +8 -4
  72. package/dist/extensions/mcp/client/oauth/types.js.map +1 -1
  73. package/dist/extensions/mcp/client/transports/streamable-http.d.ts.map +1 -1
  74. package/dist/extensions/mcp/client/transports/streamable-http.js +2 -1
  75. package/dist/extensions/mcp/client/transports/streamable-http.js.map +1 -1
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/main.js +20 -10
  81. package/dist/main.js.map +1 -1
  82. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/model-selector.js +4 -2
  84. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  85. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/scoped-models-selector.js +4 -2
  87. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  88. package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  89. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/tool-execution.js +11 -1
  91. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  92. package/dist/modes/interactive/model-search.d.ts +10 -0
  93. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  94. package/dist/modes/interactive/model-search.js +21 -0
  95. package/dist/modes/interactive/model-search.js.map +1 -1
  96. package/dist/package-manager-cli.d.ts.map +1 -1
  97. package/dist/package-manager-cli.js +5 -0
  98. package/dist/package-manager-cli.js.map +1 -1
  99. package/dist/utils/tools-manager.d.ts.map +1 -1
  100. package/dist/utils/tools-manager.js +30 -35
  101. package/dist/utils/tools-manager.js.map +1 -1
  102. package/docs/computer-use.md +5 -5
  103. package/docs/custom-provider/streaming.md +9 -14
  104. package/docs/environment-variables.md +1 -0
  105. package/docs/intercom.md +5 -1
  106. package/docs/packages.md +2 -0
  107. package/docs/settings.md +2 -2
  108. package/docs/skills.md +2 -2
  109. package/docs/subagents.md +38 -9
  110. package/docs/workflows/api-reference.md +1 -1
  111. package/docs/workflows/authoring.md +1 -1
  112. package/docs/workflows/operations.md +8 -6
  113. package/docs/workflows/reliable-design.md +23 -5
  114. package/examples/extensions/custom-provider-anthropic/index.ts +31 -31
  115. package/examples/extensions/todo.ts +7 -0
  116. package/npm-shrinkwrap.json +32 -32
  117. package/package.json +3 -3
package/docs/subagents.md CHANGED
@@ -108,9 +108,38 @@ Completed, failed, interrupted, and cancelled noninteractive children cannot ans
108
108
 
109
109
  ### Peer coordination
110
110
 
111
- Children do not have to route everything through the supervisor. Children launched in one parallel set, or with the same explicit `group`, share an Intercom group, and children launched by a workflow stage inherit that stage's invocation group. A child can run `intercom({ action: "list" })` to see live siblings and use ordinary `send` or `ask` with them directly: a locator hands file paths to the analyzer, two writers agree who owns which files or who runs the shared test suite, a debugger reuses a sibling's verified reproduction, or one reviewer challenges another's finding with evidence before each returns its own verdict. `contact_supervisor` stays reserved for supervisor decisions, and ordinary Intercom group restrictions still apply.
111
+ Peer-to-peer (swarm) coordination is the default. There are no limits on communication between agents; it is encouraged whenever you need to. The supervisor is not a message relay: relaying adds latency, loses detail, and fills the supervisor's context, while children that talk directly converge faster. Children launched in one parallel set, or with the same explicit `group`, share an Intercom group, and children launched by a workflow stage inherit that stage's invocation group. A child can run `intercom({ action: "list" })` to see live siblings and use ordinary `send` or `ask` with them directly: a locator hands file paths to the analyzer, two writers agree who owns which files or who runs the shared test suite, a debugger reuses a sibling's verified reproduction, or one reviewer challenges another's finding with evidence before each returns its own verdict. `contact_supervisor` stays reserved for supervisor decisions, and ordinary Intercom group restrictions still apply.
112
112
 
113
- Peers do not coordinate spontaneously. When related work runs in parallel, name the peers and the expected exchange in each task prompt, keep it bounded, and require each child to return its own complete result rather than deferring to a sibling. Scope, product, and architecture decisions still go to the supervisor. Peer asks and one blocking supervisor request can wait concurrently in the same child; mutual asks work as long as both children process inbound work. The `intercom` skill (`/skill:intercom`) has copy-paste peer patterns.
113
+ Children coordinate spontaneously: they discover peers with `list`, broadcast findings, claim shared files or expensive steps, ask and answer each other, hand work along a chain, and debate as much as is useful, then each returns its own complete result rather than deferring to a sibling. Explicit user approval gates and authorization for destructive or external actions still apply. Peer asks and one blocking supervisor request can wait concurrently in the same child; mutual asks work as long as both children process inbound work. The `intercom` skill (`/skill:intercom`) has copy-paste peer patterns.
114
+
115
+ For example, three reviewers launched as one parallel set work as a swarm, and the parent reads their three final results:
116
+
117
+ ```typescript
118
+ const peerRules =
119
+ "Your siblings review the same diff. Run intercom list to find them, share every confirmed finding, " +
120
+ "ask them whatever you need, and debate disagreements with evidence. Then return your own structured verdict.";
121
+
122
+ subagent({
123
+ tasks: [
124
+ { agent: "reviewer", task: `Review correctness of the auth changes. ${peerRules}` },
125
+ { agent: "reviewer", task: `Review security of the auth changes. ${peerRules}` },
126
+ { agent: "reviewer", task: `Review test coverage of the auth changes. ${peerRules}` },
127
+ ],
128
+ });
129
+ ```
130
+
131
+ ### Scaling a swarm
132
+
133
+ Swarm size scales with the task. For very complex work, or when you ask for heavy parallelism, the agent can run hundreds or thousands of agents. They organize themselves into teams and coordinate peer-to-peer, like an organization, and useful behavior emerges from their exchanges rather than from one central plan.
134
+
135
+ These are the limits that apply:
136
+
137
+ - **Children per parallel set.** One set holds up to `parallel.maxTasks` children (default 1000, no hard cap). `parallel.concurrency` and `parallel.runningCapacity` (each default 1000) bound how many run at once per parent. For more than 1000, raise all three, as shown in [Context and execution modes](#context-and-execution-modes).
138
+ - **Nesting.** Subagent delegation is one level deep and cannot be configured: a child cannot call `subagent`. To nest teams, start them from workflow stages. Each stage can launch its own parallel set, and `ctx.workflow(...)` nests child workflows up to the workflow `maxDepth` (default 3, configurable).
139
+ - **Workflow fan-out.** `ctx.parallel(...)` has no task-count limit. The workflow `defaultConcurrency` setting (default 1000) or a per-call `concurrency` bounds how many stages run at once. Set `workflows.localWorkers` to run first-level child workflows in a pool of worker processes. See [Workflow configuration](/workflows/operations#workflow-configuration) and [Local workflow workers](/workflows/operations#local-workflow-workers).
140
+ - **Teams.** Intercom groups have no member limit. Give each team its own `group` so its members coordinate internally. Team members reach their lead with `contact_supervisor`, the lead steers them, and the leads, as stages of one workflow, talk to each other in the invocation group.
141
+
142
+ For example, 20 parallel workflow stages that each launch 50 children form a swarm of 1,000 agents. The workflow and subagent defaults already run them all at once: up to 1000 stages and 1000 running children per parent. Lower `defaultConcurrency` or a per-call `concurrency` to run fewer stages at a time. Beyond these settings, the practical ceiling is your provider rate limits, machine memory, and any workflow admission limits you set.
114
143
 
115
144
  ### Single-child handoff
116
145
 
@@ -289,18 +318,18 @@ Fresh children inherit the invoking session's SDK configuration, resource-discov
289
318
 
290
319
  Top-level parallel calls apply these settings from `~/.atomic/agent/extensions/subagent/config.json` after expanding each task's optional `count`:
291
320
 
292
- - `parallel.maxTasks` defaults to 50 and limits the number of tasks in a parallel set. There is no hard upper cap; raise or lower it as needed.
293
- - `parallel.concurrency` defaults to 3 and limits how many children are dispatched at once. Per-call `concurrency` takes precedence.
294
- - `parallel.runningCapacity` defaults to 4 and limits simultaneously running child turns per parent.
321
+ - `parallel.maxTasks` defaults to 1000 and limits the number of tasks in a parallel set. There is no hard upper cap; raise or lower it as needed.
322
+ - `parallel.concurrency` defaults to 1000 and limits how many children are dispatched at once. Per-call `concurrency` takes precedence.
323
+ - `parallel.runningCapacity` defaults to 1000 and limits simultaneously running child turns per parent.
295
324
 
296
- To run up to 100 children at once, raise both concurrency and running capacity as well as the task limit:
325
+ To run more than 1000 children at once, raise concurrency and running capacity as well as the task limit:
297
326
 
298
327
  ```json
299
328
  {
300
329
  "parallel": {
301
- "maxTasks": 100,
302
- "concurrency": 100,
303
- "runningCapacity": 100
330
+ "maxTasks": 2000,
331
+ "concurrency": 2000,
332
+ "runningCapacity": 2000
304
333
  }
305
334
  }
306
335
  ```
@@ -802,7 +802,7 @@ readonly concurrency?: number;
802
802
  readonly failFast?: boolean;
803
803
  ```
804
804
 
805
- `WorkflowParallelOptions` uses `concurrency` to bound active tasks in an authored `ctx.parallel(...)`. When omitted, the runtime uses the workflow's `defaultConcurrency` setting, which defaults to `3`; explicit configuration and per-call concurrency remain honored. Parallel execution is fail-fast unless `failFast` is explicitly `false`.
805
+ `WorkflowParallelOptions` uses `concurrency` to bound active tasks in an authored `ctx.parallel(...)`. When omitted, the runtime uses the workflow's `defaultConcurrency` setting, which defaults to `1000`; explicit configuration and per-call concurrency remain honored. Parallel execution is fail-fast unless `failFast` is explicitly `false`.
806
806
 
807
807
  With `failFast: false`, every step settles before the call rejects. When any step failed, the rejection is usually an `AggregateError` whose `errors` hold the step failures and whose `partialResults` holds one entry per step, in step order: the task result for each step that completed, and `undefined` for a step that failed or did not run. Two rejections are not wrapped and have no `partialResults`: a workflow exit, and a `WorkflowBudgetExceededError` when every failed step exhausted a budget. Check for `partialResults` and rethrow anything else unchanged:
808
808
 
@@ -1000,7 +1000,7 @@ A child exposes only outputs declared in `outputs` and returned from `run` or su
1000
1000
 
1001
1001
  Missing required outputs, schema type mismatches, and non-JSON-serializable returned values fail normal child completion before the parent continues; child `ctx.exit({ outputs })` allows missing required outputs but still validates every provided key and sets `child.exited === true` so parent code must handle the partial shape.
1002
1002
 
1003
- Pass only imported workflow definitions to `ctx.workflow(...)`, not registry names or paths. Missing modules or invalid exports fail discovery. Nested children count against `maxDepth`, default `4` total workflow levels.
1003
+ Pass only imported workflow definitions to `ctx.workflow(...)`, not registry names or paths. Missing modules or invalid exports fail discovery. Nested children count against `maxDepth`, default `3` total workflow levels.
1004
1004
 
1005
1005
  A missing, empty, or invalid child graph retains a boundary summary rather than displaying unrelated nodes. Failed or skipped boundaries also retain summaries. Use `stageName` for a concise custom boundary label.
1006
1006
 
@@ -814,8 +814,8 @@ Example config:
814
814
  "team": { "path": "./workflows/team.ts" },
815
815
  "shared": { "path": "/shared/team/workflows" }
816
816
  },
817
- "defaultConcurrency": 3,
818
- "maxDepth": 4,
817
+ "defaultConcurrency": 1000,
818
+ "maxDepth": 3,
819
819
  "budget": { "maxDurationMs": 0, "maxTokens": 0, "maxCost": 0, "warnAtPercent": 80 },
820
820
  "persistRuns": true,
821
821
  "statusFile": false,
@@ -834,8 +834,8 @@ Runtime config defaults:
834
834
 
835
835
  | Key | Default | Purpose |
836
836
  |-----|---------|---------|
837
- | `defaultConcurrency` | `3` | Default stage concurrency and concurrency for authored `ctx.parallel(...)` execution |
838
- | `maxDepth` | `4` | Maximum workflow nesting depth |
837
+ | `defaultConcurrency` | `1000` | Default stage concurrency and concurrency for authored `ctx.parallel(...)` execution |
838
+ | `maxDepth` | `3` | Maximum workflow nesting depth |
839
839
  | `budget` | `{ maxDurationMs: 0, maxTokens: 0, maxCost: 0, warnAtPercent: 80 }` | Default per-run budget declaration; `0` disables a dimension; warnings default to `80` percent |
840
840
  | `persistRuns` | `true` | Persist run metadata for status/resume/history |
841
841
  | `statusFile` | `false` | Write a derived status file; defaults under `.atomic/workflows/status.json` when enabled |
@@ -873,7 +873,7 @@ Local workers are opt-in. With no `localWorkers` setting or `RunOpts.localWorker
873
873
  }
874
874
  ```
875
875
 
876
- The root workflow remains in its original process and owns the run graph, run controls, journal writes and Intercom routes. It can be an inline closure. Only an entire first-level `ctx.workflow(child)` subtree can move to a local worker. Nested workflows stay on that worker. Independent stage placement is not supported. Register every worker-eligible child definition by its module path and export name. The worker loads the module again in its own process, so the definition must be importable rather than a closure. Missing or incompatible definitions fail the run; Atomic does not fall back to local execution.
876
+ The root workflow remains in its original process and owns the run graph, run controls and the root's journal writes. It can be an inline closure. The pool adds one coordinator process per root, and the worker processes run under it: the coordinator handles worker messages and heartbeats, pool journal writes, and the Intercom routes of pooled children, so a busy pool does not slow down the interactive TUI. Only an entire first-level `ctx.workflow(child)` subtree can move to a local worker. Nested workflows stay on that worker. Independent stage placement is not supported. Register every worker-eligible child definition by its module path and export name. The worker loads the module again in its own process, so the definition must be importable rather than a closure. Missing or incompatible definitions fail the run; Atomic does not fall back to local execution.
877
877
 
878
878
  Worker definitions are loaded in the SDK with `loadWorkerWorkflow(absolutePath, exportName)`, then passed to `ctx.workflow(...)`. The SDK also accepts `RunOpts.localWorkers`; the settings form applies to CLI launches. Paths in worker settings resolve against the invocation working directory. `RunOpts.localWorkers` paths resolve against `cwd` (or the current working directory when omitted). On resume, Atomic checks the saved worker options (`count` and `definitions`) and definition entry-module digests. A changed definition or worker configuration is refused. The digest covers the entry file, not its imported dependency graph.
879
879
 
@@ -887,10 +887,12 @@ The root's existing `/workflow status`, `/workflow stages`, steering, pause, qui
887
887
 
888
888
  Each worker process must report that it is ready within `localWorkers.startupTimeoutMs` milliseconds, 30000 by default. When a pool starts, it runs children as soon as one worker is ready. A worker that exits or times out during that startup is left out of the pool, and the run fails only when no worker starts. A replacement worker that cannot start fails the run. `startupTimeoutMs` is not saved with the root, so you can change it before resuming.
889
889
 
890
- A worker that exits is detected immediately, and its child is replayed from its last committed checkpoint on a replacement worker. A worker that is still running keeps its child as long as it keeps responding, even when the root is briefly too busy to process its heartbeats. Only a running worker that has not responded for `localWorkers.leaseMs` milliseconds (30000 by default) is stopped and its child replayed. When a root is resumed after its process crashed, children that were held by the crashed process's workers are replayed on the new workers right away.
890
+ A worker that exits is detected immediately, and its child is replayed from its last committed checkpoint on a replacement worker. A worker that is still running keeps its child as long as it keeps responding, even when the pool is briefly too busy to process its heartbeats. Only a running worker that has not responded for `localWorkers.leaseMs` milliseconds (30000 by default) is stopped and its child replayed. When a root is resumed after its process crashed, children that were held by the crashed process's workers are replayed on the new workers right away.
891
891
 
892
892
  A worker that exits or is killed while it runs a child does not stop the root. The root fences that worker's attempt and replays the child from its last committed checkpoint on a replacement worker, up to three attempts per child.
893
893
 
894
+ If the root process exits, even when it is killed, the pool's coordinator and workers exit with it. Resume the root to continue its children. If the coordinator process itself dies, its workers exit with it, and each pooled child the root is waiting on fails with the retryable `worker_lost` fault and the message `Local worker pool coordinator exited (…)`.
895
+
894
896
  Each worker writes its standard error to `worker-logs/<root run ID>/<worker ID>.log` in the directory that holds the workflow journal. With the default journal, that is `~/.atomic/workflows/worker-logs/`. Characters in the root run ID that are unsafe in a file name are percent-encoded. The errors `Local worker exited before readiness`, `Local worker startup timed out after … ms`, and `Local worker retry limit` name the log file and include the end of the worker's output. Atomic keeps the logs of workers that fail. It deletes the logs of workers that are still running when the pool shuts down normally.
895
897
 
896
898
  ### Worker resource limits
@@ -1325,14 +1325,32 @@ Constructive quorum relies on existing Intercom mechanics: every workflow invoca
1325
1325
 
1326
1326
  #### Peer coordination between stages
1327
1327
 
1328
- Quorum is one instance of a general capability: stages in the same invocation are peers, not only targets for the supervisor to steer. A live stage can `intercom({ action: "list" })` its siblings and `send`/`ask` them directly to debate a finding with evidence, hand off file paths and reproductions, claim shared files or an expensive suite or build, or learn what a sibling already verified. A `send` to a known pending sibling queues until that stage starts, and an `ask` to a completed sibling that retains a valid conversation reopens it for a post-mortem turn. Delegated subagents inside a stage inherit the same group, so a stage's children can talk to each other and to sibling stages. Named subgroups (`group: "name"` or `group: true`) isolate their members from sibling subgroups while the invocation context keeps directional control over them.
1328
+ Quorum is one instance of a general capability, and peer-to-peer (swarm) exchange is the default. There are no limits on communication between agents; it is encouraged whenever you need to. The supervisor is not a message relay: relaying adds latency, loses detail, and fills its context, while stages that exchange evidence directly converge faster. Stages in the same invocation are peers, not only targets for the supervisor to steer. A live stage can `intercom({ action: "list" })` its siblings and `send`/`ask` them directly to debate a finding with evidence, hand off file paths and reproductions, claim shared files or an expensive suite or build, or learn what a sibling already verified. A `send` to a known pending sibling queues until that stage starts, and an `ask` to a completed sibling that retains a valid conversation reopens it for a post-mortem turn. Delegated subagents inside a stage inherit the same group, so a stage's children can talk to each other and to sibling stages. Named subgroups (`group: "name"` or `group: true`) isolate their members from sibling subgroups while the invocation context keeps directional control over them.
1329
1329
 
1330
- Design peer exchange the way you design any other handoff:
1330
+ How stages work as a swarm:
1331
1331
 
1332
- - Stages do not coordinate spontaneously. Say in each prompt who the peers are (or the `list` pattern that finds them) and what they exchange.
1333
- - Bound it: one evidence round, one ownership claim per shared step, one ask per fact. An unbounded conversation between parallel stages burns budget and blurs who decided what.
1332
+ - Stages coordinate spontaneously. They discover peers with `list` and broadcast findings, claim shared files or an expensive step, ask and answer each other, hand work along a chain, and debate for as long as it is useful.
1334
1333
  - Keep every stage's output its own. A peer's claim is evidence to inspect, not a verdict to copy; each stage still returns its own structured result to the reducer or downstream stage.
1335
- - Peer messages are activity inside already-running stages, not graph edges. They never replace `previous`, artifacts, or declared outputs for durable handoffs, and scope or acceptance decisions stay with a supervisor or a `ctx.ui` gate.
1334
+ - Peer messages are activity inside already-running stages, not graph edges. They never replace `previous`, artifacts, or declared outputs for durable handoffs. Explicit user approval gates and authorization for destructive or external actions still apply.
1335
+
1336
+ A minimal swarm: three parallel investigators share findings directly, and the workflow collects each stage's own result.
1337
+
1338
+ ```typescript
1339
+ const swarm = [
1340
+ "Your sibling stages investigate other angles of the same failure.",
1341
+ "Run intercom list to find them and talk to them as much as you need: share findings,",
1342
+ "claim any shared suite before running it, ask instead of guessing, and debate disagreements.",
1343
+ "Then return your own findings.",
1344
+ ].join("\n");
1345
+
1346
+ const results = await ctx.parallel([
1347
+ { name: "logs", prompt: `Trace the timeout through the service logs.\n${swarm}` },
1348
+ { name: "code", prompt: `Trace the timeout through the retry code.\n${swarm}` },
1349
+ { name: "repro", prompt: `Build a minimal reproduction of the timeout.\n${swarm}` },
1350
+ ]);
1351
+ ```
1352
+
1353
+ Swarm size scales with the task. For very complex work, or when the user asks for heavy parallelism, fan out to hundreds or thousands of agents: wide `ctx.parallel(...)` stages, a subagent parallel set inside each stage, and child workflows via `ctx.workflow(...)` (optionally on `workflows.localWorkers`). Give each team a named subgroup so its members coordinate internally while the team leads, as sibling stages, coordinate in the invocation group. Up to 1000 stages run at once by default; lower `defaultConcurrency` or pass `concurrency` to run fewer; `maxDepth` (default 3) bounds child-workflow nesting, and subagent children cannot delegate further. See [Scaling a swarm](/subagents#scaling-a-swarm) for every limit.
1336
1354
 
1337
1355
  #### Pattern diagrams
1338
1356
 
@@ -19,7 +19,7 @@ import {
19
19
  type TextContent,
20
20
  type ThinkingContent,
21
21
  type Tool,
22
- type ToolCall,
22
+ ToolCallArgumentsStream,
23
23
  type ToolResultMessage,
24
24
  type TranscriptContext,
25
25
  } from "@bastani/pi-ai/compat";
@@ -378,8 +378,11 @@ function streamCustomAnthropic(
378
378
  }
379
379
  const anthropicStream = client.messages.stream({ ...params }, { signal: options?.signal });
380
380
  stream.push({ type: "start", partial: output });
381
- type Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number };
382
- const blocks = output.content as Block[];
381
+
382
+ // Stream state per Anthropic block index, kept off the content blocks so it never reaches partial messages.
383
+ // ToolCallArgumentsStream reads each argument delta once and shows the arguments parsed so far.
384
+ const openBlocks = new Map<number, { contentIndex: number; arguments?: ToolCallArgumentsStream }>();
385
+
383
386
  for await (const event of anthropicStream) {
384
387
  if (event.type === "message_start") {
385
388
  output.usage.input = event.message.usage.input_tokens || 0;
@@ -391,15 +394,12 @@ function streamCustomAnthropic(
391
394
  calculateCost(model, output.usage);
392
395
  } else if (event.type === "content_block_start") {
393
396
  if (event.content_block.type === "text") {
394
- output.content.push({ type: "text", text: "", index: event.index } as any);
397
+ output.content.push({ type: "text", text: "" });
398
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
395
399
  stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
396
400
  } else if (event.content_block.type === "thinking") {
397
- output.content.push({
398
- type: "thinking",
399
- thinking: "",
400
- thinkingSignature: "",
401
- index: event.index,
402
- } as any);
401
+ output.content.push({ type: "thinking", thinking: "", thinkingSignature: "" });
402
+ openBlocks.set(event.index, { contentIndex: output.content.length - 1 });
403
403
  stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
404
404
  } else if (event.content_block.type === "tool_use") {
405
405
  output.content.push({
@@ -409,15 +409,19 @@ function streamCustomAnthropic(
409
409
  ? fromClaudeCodeName(event.content_block.name, context.tools)
410
410
  : event.content_block.name,
411
411
  arguments: {},
412
- partialJson: "",
413
- index: event.index,
414
- } as any);
412
+ });
413
+ openBlocks.set(event.index, {
414
+ contentIndex: output.content.length - 1,
415
+ arguments: new ToolCallArgumentsStream(),
416
+ });
415
417
  stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
416
418
  }
417
419
  } else if (event.type === "content_block_delta") {
418
- const index = blocks.findIndex((b) => b.index === event.index);
419
- const block = blocks[index];
420
- if (!block) continue;
420
+ const open = openBlocks.get(event.index);
421
+ if (!open) continue;
422
+ const index = open.contentIndex;
423
+ const block = output.content[index]!;
424
+
421
425
  if (event.delta.type === "text_delta" && block.type === "text") {
422
426
  block.text += event.delta.text;
423
427
  stream.push({ type: "text_delta", contentIndex: index, delta: event.delta.text, partial: output });
@@ -429,11 +433,8 @@ function streamCustomAnthropic(
429
433
  delta: event.delta.thinking,
430
434
  partial: output,
431
435
  });
432
- } else if (event.delta.type === "input_json_delta" && block.type === "toolCall") {
433
- (block as any).partialJson += event.delta.partial_json;
434
- try {
435
- block.arguments = JSON.parse((block as any).partialJson);
436
- } catch {}
436
+ } else if (event.delta.type === "input_json_delta" && block.type === "toolCall" && open.arguments) {
437
+ block.arguments = open.arguments.append(event.delta.partial_json);
437
438
  stream.push({
438
439
  type: "toolcall_delta",
439
440
  contentIndex: index,
@@ -444,19 +445,19 @@ function streamCustomAnthropic(
444
445
  block.thinkingSignature = (block.thinkingSignature || "") + (event.delta as any).signature;
445
446
  }
446
447
  } else if (event.type === "content_block_stop") {
447
- const index = blocks.findIndex((b) => b.index === event.index);
448
- const block = blocks[index];
449
- if (!block) continue;
450
- delete (block as any).index;
448
+ const open = openBlocks.get(event.index);
449
+ if (!open) continue;
450
+ openBlocks.delete(event.index);
451
+ const index = open.contentIndex;
452
+ const block = output.content[index]!;
453
+
451
454
  if (block.type === "text") {
452
455
  stream.push({ type: "text_end", contentIndex: index, content: block.text, partial: output });
453
456
  } else if (block.type === "thinking") {
454
457
  stream.push({ type: "thinking_end", contentIndex: index, content: block.thinking, partial: output });
455
- } else if (block.type === "toolCall") {
456
- try {
457
- block.arguments = JSON.parse((block as any).partialJson);
458
- } catch {}
459
- delete (block as any).partialJson;
458
+ } else if (block.type === "toolCall" && open.arguments) {
459
+ // One full parse of the complete text; malformed arguments are reported in `argumentsError`.
460
+ Object.assign(block, open.arguments.finish());
460
461
  stream.push({ type: "toolcall_end", contentIndex: index, toolCall: block, partial: output });
461
462
  }
462
463
  } else if (event.type === "message_delta") {
@@ -478,7 +479,6 @@ function streamCustomAnthropic(
478
479
  stream.push({ type: "done", reason: output.stopReason as "stop" | "length" | "toolUse", message: output });
479
480
  stream.end();
480
481
  } catch (error) {
481
- for (const block of output.content) delete (block as any).index;
482
482
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
483
483
  output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
484
484
  stream.push({ type: "error", reason: output.stopReason, error: output });
@@ -275,6 +275,13 @@ export default function (pi: ExtensionAPI) {
275
275
 
276
276
  case "clear":
277
277
  return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all todos"), 0, 0);
278
+
279
+ default:
280
+ return new Text(
281
+ result.content[0]?.type === "text" ? theme.fg("error", result.content[0].text) : "",
282
+ 0,
283
+ 0,
284
+ );
278
285
  }
279
286
  },
280
287
  });
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.43",
3
+ "version": "0.9.44-alpha.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bastani/atomic",
9
- "version": "0.9.43",
9
+ "version": "0.9.44-alpha.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@bastani/atomic-natives": "0.9.43",
13
- "@bastani/pi-ai": "0.9.43",
12
+ "@bastani/atomic-natives": "0.9.44-alpha.1",
13
+ "@bastani/pi-ai": "0.9.44-alpha.1",
14
14
  "@earendil-works/pi-agent-core": "1.4.0",
15
15
  "@earendil-works/pi-codemode": "1.4.0",
16
16
  "@earendil-works/pi-tui": "1.4.0",
@@ -427,18 +427,18 @@
427
427
  }
428
428
  },
429
429
  "node_modules/@bastani/atomic-natives": {
430
- "version": "0.9.43",
431
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.43.tgz",
430
+ "version": "0.9.44-alpha.1",
431
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.44-alpha.1.tgz",
432
432
  "license": "MIT",
433
433
  "optionalDependencies": {
434
- "@bastani/atomic-natives-darwin-arm64": "0.9.43",
435
- "@bastani/atomic-natives-darwin-x64": "0.9.43",
436
- "@bastani/atomic-natives-linux-arm64-gnu": "0.9.43",
437
- "@bastani/atomic-natives-linux-arm64-musl": "0.9.43",
438
- "@bastani/atomic-natives-linux-x64-gnu": "0.9.43",
439
- "@bastani/atomic-natives-linux-x64-musl": "0.9.43",
440
- "@bastani/atomic-natives-win32-arm64-msvc": "0.9.43",
441
- "@bastani/atomic-natives-win32-x64-msvc": "0.9.43"
434
+ "@bastani/atomic-natives-darwin-arm64": "0.9.44-alpha.1",
435
+ "@bastani/atomic-natives-darwin-x64": "0.9.44-alpha.1",
436
+ "@bastani/atomic-natives-linux-arm64-gnu": "0.9.44-alpha.1",
437
+ "@bastani/atomic-natives-linux-arm64-musl": "0.9.44-alpha.1",
438
+ "@bastani/atomic-natives-linux-x64-gnu": "0.9.44-alpha.1",
439
+ "@bastani/atomic-natives-linux-x64-musl": "0.9.44-alpha.1",
440
+ "@bastani/atomic-natives-win32-arm64-msvc": "0.9.44-alpha.1",
441
+ "@bastani/atomic-natives-win32-x64-msvc": "0.9.44-alpha.1"
442
442
  },
443
443
  "engines": {
444
444
  "bun": ">=1.4.2",
@@ -446,8 +446,8 @@
446
446
  }
447
447
  },
448
448
  "node_modules/@bastani/atomic-natives-darwin-arm64": {
449
- "version": "0.9.43",
450
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.43.tgz",
449
+ "version": "0.9.44-alpha.1",
450
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.44-alpha.1.tgz",
451
451
  "license": "MIT",
452
452
  "os": [
453
453
  "darwin"
@@ -458,8 +458,8 @@
458
458
  "optional": true
459
459
  },
460
460
  "node_modules/@bastani/atomic-natives-darwin-x64": {
461
- "version": "0.9.43",
462
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.43.tgz",
461
+ "version": "0.9.44-alpha.1",
462
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.44-alpha.1.tgz",
463
463
  "license": "MIT",
464
464
  "os": [
465
465
  "darwin"
@@ -470,8 +470,8 @@
470
470
  "optional": true
471
471
  },
472
472
  "node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
473
- "version": "0.9.43",
474
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.43.tgz",
473
+ "version": "0.9.44-alpha.1",
474
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.44-alpha.1.tgz",
475
475
  "license": "MIT",
476
476
  "os": [
477
477
  "linux"
@@ -485,8 +485,8 @@
485
485
  "optional": true
486
486
  },
487
487
  "node_modules/@bastani/atomic-natives-linux-arm64-musl": {
488
- "version": "0.9.43",
489
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.43.tgz",
488
+ "version": "0.9.44-alpha.1",
489
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.44-alpha.1.tgz",
490
490
  "license": "MIT",
491
491
  "os": [
492
492
  "linux"
@@ -500,8 +500,8 @@
500
500
  "optional": true
501
501
  },
502
502
  "node_modules/@bastani/atomic-natives-linux-x64-gnu": {
503
- "version": "0.9.43",
504
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.43.tgz",
503
+ "version": "0.9.44-alpha.1",
504
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.44-alpha.1.tgz",
505
505
  "license": "MIT",
506
506
  "os": [
507
507
  "linux"
@@ -515,8 +515,8 @@
515
515
  "optional": true
516
516
  },
517
517
  "node_modules/@bastani/atomic-natives-linux-x64-musl": {
518
- "version": "0.9.43",
519
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.43.tgz",
518
+ "version": "0.9.44-alpha.1",
519
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.44-alpha.1.tgz",
520
520
  "license": "MIT",
521
521
  "os": [
522
522
  "linux"
@@ -530,8 +530,8 @@
530
530
  "optional": true
531
531
  },
532
532
  "node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
533
- "version": "0.9.43",
534
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.43.tgz",
533
+ "version": "0.9.44-alpha.1",
534
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.44-alpha.1.tgz",
535
535
  "license": "MIT",
536
536
  "os": [
537
537
  "win32"
@@ -542,8 +542,8 @@
542
542
  "optional": true
543
543
  },
544
544
  "node_modules/@bastani/atomic-natives-win32-x64-msvc": {
545
- "version": "0.9.43",
546
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.43.tgz",
545
+ "version": "0.9.44-alpha.1",
546
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.44-alpha.1.tgz",
547
547
  "license": "MIT",
548
548
  "os": [
549
549
  "win32"
@@ -554,8 +554,8 @@
554
554
  "optional": true
555
555
  },
556
556
  "node_modules/@bastani/pi-ai": {
557
- "version": "0.9.43",
558
- "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.43.tgz",
557
+ "version": "0.9.44-alpha.1",
558
+ "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.44-alpha.1.tgz",
559
559
  "license": "MIT",
560
560
  "dependencies": {
561
561
  "@anthropic-ai/sdk": "0.129.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.43",
3
+ "version": "0.9.44-alpha.1",
4
4
  "description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "atomicConfig": {
@@ -89,8 +89,8 @@
89
89
  "prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
90
90
  },
91
91
  "dependencies": {
92
- "@bastani/atomic-natives": "0.9.43",
93
- "@bastani/pi-ai": "0.9.43",
92
+ "@bastani/atomic-natives": "0.9.44-alpha.1",
93
+ "@bastani/pi-ai": "0.9.44-alpha.1",
94
94
  "@earendil-works/pi-agent-core": "1.4.0",
95
95
  "@earendil-works/pi-codemode": "1.4.0",
96
96
  "@earendil-works/pi-tui": "1.4.0",