@bastani/atomic 0.9.19-alpha.3 → 0.9.19-alpha.5

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 (164) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +8 -0
  3. package/dist/builtin/intercom/index.bundle.mjs +117 -26
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/package.json +1 -1
  6. package/dist/builtin/subagents/CHANGELOG.md +18 -0
  7. package/dist/builtin/subagents/README.md +15 -15
  8. package/dist/builtin/subagents/agents/debugger.md +2 -2
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/skills/subagent/SKILL.md +26 -26
  11. package/dist/builtin/subagents/src/extension/index.bundle.mjs +140 -58
  12. package/dist/builtin/web-access/package.json +1 -1
  13. package/dist/builtin/workflows/CHANGELOG.md +27 -0
  14. package/dist/builtin/workflows/README.md +14 -15
  15. package/dist/builtin/workflows/builtin/{chunk-mygmtz6e.js → chunk-1nbq96fh.js} +19 -19
  16. package/dist/builtin/workflows/builtin/{chunk-zggjyg5z.js → chunk-n8zysf1t.js} +1 -1
  17. package/dist/builtin/workflows/builtin/{chunk-r9qq7xfj.js → chunk-vhmnzdj9.js} +37 -37
  18. package/dist/builtin/workflows/builtin/{chunk-62v3gg9f.js → chunk-yeee2jrc.js} +11 -11
  19. package/dist/builtin/workflows/builtin/goal.js +2 -2
  20. package/dist/builtin/workflows/builtin/index.js +4 -4
  21. package/dist/builtin/workflows/builtin/open-claude-design.js +1 -1
  22. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  23. package/dist/builtin/workflows/package.json +5 -5
  24. package/dist/builtin/workflows/src/extension/index.bundle.mjs +180 -239
  25. package/dist/builtin/workflows/src/index.js +19 -2
  26. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -1
  27. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -1
  28. package/dist/core/agent-session-auto-compaction.js +1 -1
  29. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  30. package/dist/core/agent-session-compaction.js +1 -1
  31. package/dist/core/agent-session-compaction.js.map +1 -1
  32. package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
  33. package/dist/core/agent-session-custom-message-commit.js +33 -2
  34. package/dist/core/agent-session-custom-message-commit.js.map +1 -1
  35. package/dist/core/agent-session-message-queue.d.ts +1 -1
  36. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  37. package/dist/core/agent-session-message-queue.js +20 -6
  38. package/dist/core/agent-session-message-queue.js.map +1 -1
  39. package/dist/core/agent-session-methods.d.ts +4 -1
  40. package/dist/core/agent-session-methods.d.ts.map +1 -1
  41. package/dist/core/agent-session-methods.js.map +1 -1
  42. package/dist/core/agent-session-persistent-custom-messages.d.ts +1 -1
  43. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
  44. package/dist/core/agent-session-persistent-custom-messages.js +13 -3
  45. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
  46. package/dist/core/agent-session-post-tool-compaction.js +1 -1
  47. package/dist/core/agent-session-post-tool-compaction.js.map +1 -1
  48. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  49. package/dist/core/agent-session-prompt.js +83 -0
  50. package/dist/core/agent-session-prompt.js.map +1 -1
  51. package/dist/core/agent-session-queue-pause.d.ts.map +1 -1
  52. package/dist/core/agent-session-queue-pause.js +11 -0
  53. package/dist/core/agent-session-queue-pause.js.map +1 -1
  54. package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
  55. package/dist/core/agent-session-tool-hooks.js +2 -0
  56. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  57. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  58. package/dist/core/agent-session-tool-registry.js +12 -0
  59. package/dist/core/agent-session-tool-registry.js.map +1 -1
  60. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  61. package/dist/core/agent-session-transfer.js +7 -0
  62. package/dist/core/agent-session-transfer.js.map +1 -1
  63. package/dist/core/agent-session-types.d.ts +2 -0
  64. package/dist/core/agent-session-types.d.ts.map +1 -1
  65. package/dist/core/agent-session-types.js +2 -0
  66. package/dist/core/agent-session-types.js.map +1 -1
  67. package/dist/core/agent-session.d.ts +4 -0
  68. package/dist/core/agent-session.d.ts.map +1 -1
  69. package/dist/core/agent-session.js +22 -0
  70. package/dist/core/agent-session.js.map +1 -1
  71. package/dist/core/extensions/context-types.d.ts +6 -0
  72. package/dist/core/extensions/context-types.d.ts.map +1 -1
  73. package/dist/core/extensions/context-types.js.map +1 -1
  74. package/dist/core/extensions/loader-virtual-modules.js +3 -3
  75. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  76. package/dist/core/extensions/runner-context.d.ts +1 -1
  77. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  78. package/dist/core/extensions/runner-context.js +9 -3
  79. package/dist/core/extensions/runner-context.js.map +1 -1
  80. package/dist/core/extensions/runner.js +1 -1
  81. package/dist/core/extensions/runner.js.map +1 -1
  82. package/dist/core/extensions/workflow-events.d.ts +1 -1
  83. package/dist/core/extensions/workflow-events.d.ts.map +1 -1
  84. package/dist/core/extensions/workflow-events.js.map +1 -1
  85. package/dist/core/sdk-exports.d.ts +1 -1
  86. package/dist/core/sdk-exports.d.ts.map +1 -1
  87. package/dist/core/sdk-exports.js +1 -1
  88. package/dist/core/sdk-exports.js.map +1 -1
  89. package/dist/core/settings-manager-basic-accessors.d.ts +5 -3
  90. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  91. package/dist/core/settings-manager-basic-accessors.js +23 -8
  92. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  93. package/dist/core/settings-manager.d.ts +1 -1
  94. package/dist/core/settings-manager.d.ts.map +1 -1
  95. package/dist/core/settings-manager.js.map +1 -1
  96. package/dist/core/settings-types.d.ts +5 -0
  97. package/dist/core/settings-types.d.ts.map +1 -1
  98. package/dist/core/settings-types.js.map +1 -1
  99. package/dist/core/slash-commands.d.ts.map +1 -1
  100. package/dist/core/slash-commands.js +4 -5
  101. package/dist/core/slash-commands.js.map +1 -1
  102. package/dist/core/tasks/agent-adapter.d.ts +5 -2
  103. package/dist/core/tasks/agent-adapter.d.ts.map +1 -1
  104. package/dist/core/tasks/agent-adapter.js +10 -3
  105. package/dist/core/tasks/agent-adapter.js.map +1 -1
  106. package/dist/core/tasks/completion.d.ts.map +1 -1
  107. package/dist/core/tasks/completion.js +5 -1
  108. package/dist/core/tasks/completion.js.map +1 -1
  109. package/dist/core/tools/bash-pty-native.d.ts +16 -2
  110. package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
  111. package/dist/core/tools/bash-pty-native.js +82 -0
  112. package/dist/core/tools/bash-pty-native.js.map +1 -1
  113. package/dist/core/tools/bash.d.ts +13 -2
  114. package/dist/core/tools/bash.d.ts.map +1 -1
  115. package/dist/core/tools/bash.js +48 -3
  116. package/dist/core/tools/bash.js.map +1 -1
  117. package/dist/core/tools/index.d.ts +4 -1
  118. package/dist/core/tools/index.d.ts.map +1 -1
  119. package/dist/core/tools/index.js +12 -0
  120. package/dist/core/tools/index.js.map +1 -1
  121. package/dist/core/tools/kill.d.ts +17 -0
  122. package/dist/core/tools/kill.d.ts.map +1 -0
  123. package/dist/core/tools/kill.js +49 -0
  124. package/dist/core/tools/kill.js.map +1 -0
  125. package/dist/core/tools/powershell.d.ts +84 -28
  126. package/dist/core/tools/powershell.d.ts.map +1 -1
  127. package/dist/core/tools/powershell.js +10 -3
  128. package/dist/core/tools/powershell.js.map +1 -1
  129. package/dist/core/workflow-stage-admission.d.ts +14 -0
  130. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  131. package/dist/core/workflow-stage-admission.js +63 -3
  132. package/dist/core/workflow-stage-admission.js.map +1 -1
  133. package/dist/index.d.ts +2 -2
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -1
  136. package/dist/index.js.map +1 -1
  137. package/dist/modes/interactive/components/task-row.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/task-row.js +8 -1
  139. package/dist/modes/interactive/components/task-row.js.map +1 -1
  140. package/docs/background-tasks.md +24 -1
  141. package/docs/compaction.md +20 -0
  142. package/docs/computer-use.md +390 -0
  143. package/docs/docs.json +1 -0
  144. package/docs/extensions.md +11 -1
  145. package/docs/herdr.md +2 -0
  146. package/docs/index.md +1 -0
  147. package/docs/intercom.md +10 -2
  148. package/docs/providers.md +1 -1
  149. package/docs/quickstart.md +1 -1
  150. package/docs/sdk.md +11 -1
  151. package/docs/settings.md +20 -0
  152. package/docs/subagents.md +8 -6
  153. package/docs/tmux.md +2 -0
  154. package/docs/tools.md +7 -1
  155. package/docs/usage.md +1 -1
  156. package/docs/workflows/api-reference.md +4 -3
  157. package/docs/workflows/authoring.md +3 -3
  158. package/docs/workflows/builtins.md +9 -6
  159. package/docs/workflows/operations.md +36 -30
  160. package/docs/workflows/reliable-design.md +3 -3
  161. package/docs/workflows/verification.md +99 -40
  162. package/docs/workflows.md +4 -2
  163. package/npm-shrinkwrap.json +32 -32
  164. package/package.json +3 -3
package/docs/subagents.md CHANGED
@@ -83,7 +83,9 @@ In a parallel run, `intercom.ask`, `contact_supervisor({ reason: "need_decision"
83
83
 
84
84
  `intercom.send` and `contact_supervisor` progress updates return after delivery without waiting for a reply. An exact-child Intercom handshake can release the parallel call's foreground observations so the supervisor can handle the message. This is not execution cancellation: active siblings keep running, queued siblings start once capacity is available, and worktrees stay owned until their children exit. Background calls use the same communication path without needing to release an observation.
85
85
 
86
- Targeted `interrupt` still stops only the selected child. Explicit batch cancellation and session/workflow-stage lifetime closure still stop the intended owned children, including pending reply waits. A late or duplicate reply cannot revive a terminal child. Ordinary Intercom group restrictions and the authorized cross-group `contact_supervisor` route are unchanged.
86
+ To update a working child, use ordinary Intercom `send` or `ask` with its exact connected name or full session ID from `intercom list`. Foreground and background children treat it as a priority interrupt: the child's current model call or cancellable tool is cancelled and the message is handled next in the same child execution, without relaunching its task or repeating the original prompt. Tools that ignore cancellation finish first, and completed side effects are kept. Multiple updates stay in arrival order. A reply to an incoming ask uses ordinary `intercom.reply`; this does not change the single-child parent-targeted handoff described below.
87
+
88
+ Targeted `kill` stops only the selected child and cannot be resumed. Explicit batch cancellation and session/workflow-stage lifetime closure still stop the intended owned children, including pending reply waits. A late or duplicate reply cannot revive a terminal child. Ordinary Intercom group restrictions and the authorized cross-group `contact_supervisor` route are unchanged.
87
89
 
88
90
  Completed, failed, interrupted, and cancelled noninteractive children cannot answer new Intercom asks, even when their retained registration still says `idle`. Such asks fail immediately with an explicit terminal-child error; an admitted ask also fails if its child terminates before replying. Launch a fresh child with the required context for follow-up work. This does not restrict live interactive idle sessions or workflow-stage post-mortem conversations, and does not change `send` delivery semantics.
89
91
 
@@ -117,7 +119,7 @@ Atomic currently bundles these agents from `@bastani/subagents`:
117
119
  | `code-simplifier` | Simplify recently changed code under its behavior-preservation “doors” rubric. | Yes |
118
120
  | `worker` | Implement an approved task or handoff, validate the narrow change, and escalate product, architecture, or scope decisions to its supervisor. | Yes |
119
121
 
120
- All bundled agents except `debugger` default to `openai-codex/gpt-6-astra:low`; `debugger` uses `openai-codex/gpt-6-astra:xhigh`. Their fallback chains start with GitHub Copilot Astra, OpenAI Astra, Anthropic Fable 5.1, then GitHub Copilot Fable 5.1. Ordinary agents use Astra/Fable 5.1 at `low`, with Sol and GPT-5.5 fallbacks at `medium`, including the locator roles. Debugger keeps Astra and Sol at `xhigh` and Anthropic fallbacks at `high`. Later candidates retain provider-specific reasoning levels and identifiers; OpenRouter mirrors follow the direct-provider candidates. Each agent definition contains its complete ordered chain.
122
+ The bundled `debugger` defaults to `openai-codex/gpt-6-astra:medium`. Its Astra, Fable 5.1, and Fable 5 candidates use `medium`; Sol and Opus candidates use `high`. Other defaults are unchanged: the three locator roles use `openai-codex/gpt-5.6-luna:xhigh`, while the remaining agents use `openai-codex/gpt-6-astra:low`. Ordinary agents use Astra/Fable 5.1 at `low`, with Sol and GPT-5.5 fallbacks at `medium`, including the locator roles. Debugger's fallback chain starts with GitHub Copilot Astra, OpenAI Astra, Anthropic Fable 5.1, then GitHub Copilot Fable 5.1. Later candidates retain provider-specific reasoning levels and identifiers; OpenRouter mirrors follow the direct-provider candidates. Each agent definition contains its complete ordered chain.
121
123
 
122
124
  The bundled definitions keep their routing and model frontmatter but use compact, outcome-first bodies: role and goal, success criteria, constraints and tool routes, output contract, and stop rules where applicable. Report-producing agents ground progress claims in tool results and return concise evidence rather than narrating internal reasoning. Read-oriented agents inspect and report. `debugger`, `code-simplifier`, and `worker` can edit files, so give them an explicit scope and validation target. The debugger should finish an in-scope diagnosis by applying and validating the fix, not stop at a proposed patch.
123
125
 
@@ -163,16 +165,16 @@ Tool examples:
163
165
  subagent({ agent: "codebase-analyzer", task: "Trace the auth flow with file references.", wait: { kind: "foreground", budgetMs: 30000 } })
164
166
  ```
165
167
 
166
- Use `interrupt` to stop a live child. Interrupted children are terminal for continuation; launch a fresh child with an explicit context handoff for follow-up work.
168
+ Use `subagent({ action: "kill", id: "<task-or-run-id>" })` to terminally stop a live child. Killed children cannot be resumed; launch a fresh child with an explicit context handoff for follow-up work. The former `interrupt` action is no longer accepted. Replace subagent calls using `action: "interrupt"` with `action: "kill"`. Workflow controls use `pause`; host cancellation APIs are unchanged.
167
169
 
168
170
  If the parent turn is cancelled while a foreground in-process child is still running, the child stops through the existing abort/interrupted state. That outcome is terminal and non-retryable: it does not count as a failure, never looks completed, and preserves any fallback metadata already recorded before abort. Parent receipts, Intercom summaries, and progress present the child as cancelled; persisted metadata records `interrupted` with abort cause rather than a new public status. Atomic recovers bounded, clearly labelled partial findings in this order:
169
171
  1. A modified run-scoped `progress.md`
170
172
  2. The last assistant message that contains actual text
171
173
  3. A cancellation notice with session, progress, and output artifact references
172
174
 
173
- A thinking-only aborted final message is skipped so earlier text can still be recovered. Session, Progress, and Output paths are cited only when those files exist when the cancelled envelope or receipt is built. A parallel set shares one `progress.md`; recovery attributes that file to the first progress-enabled child so siblings are not each given a copy of the same findings. A mixed parallel set that contains both a user interrupt and a parent cancellation presents the cancellation summary rather than interrupt-specific follow-up guidance.
175
+ A thinking-only aborted final message is skipped so earlier text can still be recovered. Session, Progress, and Output paths are cited only when those files exist when the cancelled envelope or receipt is built. A parallel set shares one `progress.md`; recovery attributes that file to the first progress-enabled child so siblings are not each given a copy of the same findings. A mixed parallel set that contains both a killed child and a parent cancellation preserves the cancellation summary.
174
176
 
175
- For owner-bound task IDs, status and interrupt resolve the same task owner as launch and wait. Legacy run IDs use the live Rust registry and status watch; `list` and `get` remain read-only definition management actions. Neither identifier revives a completed execution. Owner-bound completions use persisted delivery identities; unbound callers retain their legacy result and artifact behavior.
177
+ For owner-bound task IDs, status and kill resolve the same task owner as launch and wait. Legacy run IDs use the live registry and status watch; `list` and `get` remain read-only definition management actions. Neither identifier revives a completed execution. Owner-bound completions use persisted delivery identities; unbound callers retain their legacy result and artifact behavior.
176
178
 
177
179
  In-process status results use compact rows such as `∀ debugger_1 · Running`, matching the other subagent tool cards. The collapsed card shows up to six children and an omitted count; expanding the tool result shows every child, full paths, parent, task, depth, loaded/cold residency, and any recorded termination cause or session file. Multiple runs have separate labels. The configured tool-expansion shortcut appears below the compact rows. This is a status snapshot, not an animated live monitor; inspection does not start or resume work. Model-facing status text and canonical identifiers remain unchanged.
178
180
 
@@ -248,7 +250,7 @@ Child-safety boundaries are enforced by typed admission policy and the bundled s
248
250
  - In-process child sessions load bundled extensions through normal discovery. The `subagent` tool may therefore be registered when the child's active tool selection permits it, including the default no-allowlist case; an explicit allowlist may omit it. Tool presence does not grant fanout. The bundled subagents skill remains parent-only and is stripped from child prompts, including fanout-authorized children.
249
251
  - Child context is filtered to remove parent orchestration artifacts, old control/status messages, and prior parent `subagent` tool calls/results.
250
252
  - Children are instructed that they are not the parent orchestrator and must complete their assigned task directly rather than delegating.
251
- - Delegation is exactly one level deep and is not configurable. A session admitted as a subagent child is refused every launch and `interrupt`; only `list`, `get`, and `status` stay available. A management-restricted child is also refused `create`, `update`, and `delete`.
253
+ - Delegation is exactly one level deep and is not configurable. A session admitted as a subagent child is refused every launch and `kill`; only `list`, `get`, and `status` stay available. A management-restricted child is also refused `create`, `update`, and `delete`.
252
254
  - The rule is enforced twice: the subagent executor refuses a child before any run starts, and the Rust admission door refuses a child deeper than the single permitted level. Admitted depth is typed admission state, never inherited from process environment state.
253
255
 
254
256
  This keeps the parent session responsible for orchestration.
package/docs/tmux.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Atomic works inside tmux, but tmux strips modifier information from certain keys by default. Without configuration, `SHIFT+Enter` and `CTRL+Enter` are usually indistinguishable from plain `Enter`.
4
4
 
5
+ For terminal automation, prefer Herdr when eligible, with tmux or native Windows psmux as fallbacks. See [Computer use](/computer-use#terminal-automation-with-herdr) for tool selection and safe session control. This page covers Atomic's keyboard setup inside tmux.
6
+
5
7
  ## Recommended Configuration
6
8
 
7
9
  Add to `~/.tmux.conf`:
package/docs/tools.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Built-in tools
2
2
 
3
- Atomic enables these coding tools in normal sessions by default: `read`, `write`, `edit`, `bash`, `find`, and `search`.
3
+ Atomic enables these coding tools in normal sessions by default: `read`, `write`, `edit`, `bash`, `kill`, `find`, and `search`.
4
4
 
5
5
  ## Hashline editing anchors
6
6
 
@@ -42,6 +42,12 @@ Configured command prefixes and SDK `spawnHook` rewrites remain executable shell
42
42
  }
43
43
  ```
44
44
 
45
+ ## `kill`
46
+
47
+ `kill({ id: taskId })` stops an owned background shell task launched by `bash` or `powershell`, including a command that automatically yielded. Pass its returned task ID verbatim, not a PID. The tool is owner-scoped in main and workflow-stage chat and does not cancel subagents or another owner's work.
48
+
49
+ The result reports the cancellation decision and current execution and cleanup states. A request is not confirmation of termination. Repeated requests preserve the original cancellation decision; already-completed work retains its outcome. Cleanup failures are reported explicitly. See [Background tasks](/background-tasks#stop-a-shell-task-from-a-tool-call) for states, retained output, and `/tasks` controls.
50
+
45
51
  ## `find` and `search`
46
52
 
47
53
  `find` finds filesystem paths by glob; use `search` when you need content matches instead of path matches. `find.paths` is required and accepts file, directory (including a filesystem root), internal URL, or glob paths; copied quoted paths are normalized, exact filesystem paths with spaces/commas/semicolons are preserved before delimiter expansion, comma/semicolon-joined paths are split when at least one part resolves, whitespace-joined paths are split only when every part resolves, hidden files are included by default, `.gitignore` is respected by default (including nested `.gitignore` files outside a Git checkout), broad scans keep `node_modules`/`.git` pruned even with `gitignore:false` unless `node_modules` is explicitly present in the requested path or glob, results are capped at 200 by default, and timeout defaults to 5 seconds. Local `find` prefers the bundled Rust native `glob` implementation derived from oh-my-pi, falling back to the packaged `fd` helper only when native bindings are unavailable. Results include `scopePath`, `fileCount`, `files`, truncation/missing-path metadata, and streamed `onUpdate` snapshots during long scans.
package/docs/usage.md CHANGED
@@ -51,7 +51,7 @@ Type `/` in the editor to open command completion. Extensions can register custo
51
51
  | `/model` | Switch models and automatically save the startup default |
52
52
  | `/thinking` | Switch thinking level and automatically save the startup default |
53
53
  | `/scoped-models` | Enable/disable models for CTRL+P cycling |
54
- | `/workflow` | List/run workflows; manage runs (connect/inspect/pause/interrupt/quit/resume); reload workflow resources |
54
+ | `/workflow` | List/run workflows; manage runs (connect/inspect/pause/quit/resume); reload workflow resources |
55
55
  | `/settings` | Theme, message delivery, transport, and other preferences |
56
56
  | `/resume` | Pick from previous sessions |
57
57
  | `/new` | Start a new session |
@@ -405,7 +405,7 @@ ctx.tool<TValue extends WorkflowSerializableValue>(
405
405
 
406
406
  Runs arbitrary TypeScript code as a tracked, non-attachable durable workflow graph node and caches its serializable result by call order plus the content hash of `name` and `args`. The node is created before `fn` runs and may appear before, between, after, or without model stages. A completed call replays without rerunning `fn`, so use this primitive for workflow-owned durable side effects; keep pure computation as ordinary TypeScript.
407
407
 
408
- **Cancellation and deadlines.** Every callback receives a `WorkflowToolContext` whose `signal` aborts when the run is cancelled, when the run is gracefully quit, or when this single node is aborted with `workflow({ action: "quit"|"interrupt", runId, stageId: "<tool node id or name>" })`. Forward it to `fetch`, a child process, or any client that accepts an `AbortSignal` so a stuck call can be stopped:
408
+ **Cancellation and deadlines.** Every callback receives a `WorkflowToolContext` whose `signal` aborts when the run is cancelled, when the run is gracefully quit, or when this single node is aborted with `workflow({ action: "quit"|"pause", runId, stageId: "<tool node id or name>" })`. Forward it to `fetch`, a child process, or any client that accepts an `AbortSignal` so a stuck call can be stopped:
409
409
 
410
410
  ```ts
411
411
  await ctx.tool(
@@ -571,7 +571,7 @@ readonly excludedTools?: readonly string[];
571
571
 
572
572
  `tools` is an allowlist across built-in and bundled extension tools. `excludedTools` and `noTools: "all"` still win for every tool except mandatory ordinary `intercom`, which remains registered and active.
573
573
 
574
- The bundled `subagent` tool is available by default on the same terms as main chat. A workflow stage is a top-level session, so it may delegate once; the children it launches may not delegate or control another child. Delegation is exactly one level deep and nothing configures it there is no config option, agent frontmatter field, or tool parameter for the level. The in-process admission door carries each child's issued depth in its typed child policy, the executor refuses any launch or `interrupt` from a session that was itself admitted as a child, and the Rust `SubagentControl` admission door refuses a child deeper than the single permitted level. That depth is never carried through process environment. Bundled subagent definitions from `@bastani/subagents` are available to that tool. Explicitly list tools such as `subagent`, `web_search`, `fetch_content`, or `intercom` when using an allowlist; in-process child sessions load the bundled resources while suppressing the workflow extension lifecycle.
574
+ The bundled `subagent` tool is available by default on the same terms as main chat. A workflow stage is a top-level session, so it may delegate once; the children it launches may not delegate or control another child. Delegation is exactly one level deep and nothing configures it: there is no config option, agent frontmatter field, or tool parameter for the level. The in-process admission door carries each child's issued depth in its typed child policy, the executor refuses any launch or `kill` from a session that was itself admitted as a child, and the Rust `SubagentControl` admission door refuses a child deeper than the single permitted level. That depth is never carried through process environment. Bundled subagent definitions from `@bastani/subagents` are available to that tool. Explicitly list tools such as `subagent`, `web_search`, `fetch_content`, or `intercom` when using an allowlist; in-process child sessions load the bundled resources while suppressing the workflow extension lifecycle.
575
575
 
576
576
  Workflow stages use the same upstream-compatible `bash` tool as normal Atomic sessions. Enabled commands run through the configured shell with the stage process permissions. There is no command-text allow/deny option: expose or hide shell access with these tool fields, prefer narrow custom tools for repeatable operations, and use a container, VM, or other sandbox for stronger isolation.
577
577
 
@@ -955,6 +955,7 @@ interface WorkflowTaskResult extends WorkflowTaskContext {
955
955
  readonly sessionFile?: string;
956
956
  readonly artifacts?: readonly WorkflowArtifact[];
957
957
  readonly model?: string;
958
+ readonly thinkingLevel?: string;
958
959
  readonly attemptedModels?: readonly string[];
959
960
  readonly modelAttempts?: readonly WorkflowModelAttempt[];
960
961
  readonly warnings?: readonly string[];
@@ -988,7 +989,7 @@ When a stage explicitly configures `model` or `fallbackModels`, each recorded at
988
989
  ```typescript
989
990
  interface WorkflowDetails extends WorkflowSerializableObject {
990
991
  readonly mode: "named" | "single" | "parallel" | "chain" | "inspection" | "control";
991
- readonly action?: "list" | "get" | "inputs" | "run" | "status" | "interrupt" | "resume";
992
+ readonly action?: "list" | "get" | "inputs" | "run" | "status" | "pause" | "resume";
992
993
  readonly runId?: string;
993
994
  readonly status: "accepted" | "running" | WorkflowExitStatus | "failed" | "killed" | "noop";
994
995
  readonly context?: "fresh" | "fork";
@@ -409,7 +409,7 @@ The two streaming modes have distinct, deterministic timing:
409
409
  - **`steer`** is delivered at the next steering boundary: after the current assistant response has finished executing its whole tool batch, and before the next model request. It is not injected between two tool calls emitted by the same assistant response.
410
410
  - **`followUp`** is delivered only when the agent would otherwise stop — no further tool-driven turns and no steering messages left.
411
411
 
412
- Each queue is FIFO in admission order. There is no global FIFO *across* the two queues: steering keeps its semantic priority even when a follow-up was submitted earlier. A controlled pause or interrupt hold delays eligibility but preserves both the queue class and the order within it. An abort, kill, or fatal provider failure ends the turn without consuming what is still queued.
412
+ Each queue is FIFO in admission order. There is no global FIFO *across* the two queues: steering keeps its semantic priority even when a follow-up was submitted earlier. A controlled pause hold delays eligibility but preserves both the queue class and the order within it. An abort, kill, or fatal provider failure ends the turn without consuming what is still queued.
413
413
 
414
414
  A message you type into an attached stage chat and submit with Enter defaults to `steer`, matching normal (non-workflow) session steering, so a mid-run correction lands at the next steering boundary rather than at the end of the turn. Ctrl+F queues a follow-up instead. This is a property of the interactive surface, not of the API: an authored `stage.sendUserMessage()` call that names no `deliverAs` still defaults to follow-up while the stage is streaming.
415
415
 
@@ -474,7 +474,7 @@ export default workflow({
474
474
 
475
475
  `ctx.exit()` accepts `status: "completed" | "skipped" | "cancelled" | "blocked" | "failed"`; `status` defaults to `"completed"`. Choose `completed` when the objective was met and declared outputs are complete and trustworthy; `skipped` when a precondition made the run a valid no-op; `cancelled` when the work is no longer wanted, which is a decision rather than a defect; `blocked` when valid progress needs a changed condition or a later decision; and `failed` when required work was attempted and definitively could not complete. A bounded reviewer or repair loop that does not converge is `blocked`, not `failed`.
476
476
 
477
- `reason` from a valid author exit is persisted and shown in status surfaces and lifecycle notices, including the default `/workflow status` list and `/workflow status <runId>` detail, so do not put secrets in it. An exit rejected during validation is finalized as an ordinary failed run rather than an accepted author exit. `outputs` may contain a partial subset of declared outputs; provided keys still must be declared in the workflow's `outputs` object, match their TypeBox schema, and be JSON-serializable. `failed` exits default to `resumable: false`; set `resumable: true` only when a later durable retry is intended. `resumable` is valid only with `status: "failed"`; supplying it for another status records a non-resumable authoring failure. A durable retry keeps the failed handle in the resume catalog and re-dispatches the workflow with completed checkpoints replayed. The low-level `resumeRun()` helper only inspects terminal runs; it reports the durable retry path instead of silently claiming that it resumed. The other exit statuses keep their existing non-resumable author-exit behavior. Public `pause`, `interrupt`, and `quit`, plus internal destructive cancellation, keep their distinct existing behavior.
477
+ `reason` from a valid author exit is persisted and shown in status surfaces and lifecycle notices, including the default `/workflow status` list and `/workflow status <runId>` detail, so do not put secrets in it. An exit rejected during validation is finalized as an ordinary failed run rather than an accepted author exit. `outputs` may contain a partial subset of declared outputs; provided keys still must be declared in the workflow's `outputs` object, match their TypeBox schema, and be JSON-serializable. `failed` exits default to `resumable: false`; set `resumable: true` only when a later durable retry is intended. `resumable` is valid only with `status: "failed"`; supplying it for another status records a non-resumable authoring failure. A durable retry keeps the failed handle in the resume catalog and re-dispatches the workflow with completed checkpoints replayed. The low-level `resumeRun()` helper only inspects terminal runs; it reports the durable retry path instead of silently claiming that it resumed. The other exit statuses keep their existing non-resumable author-exit behavior. Public `pause` and `quit`, plus internal destructive cancellation, keep their distinct behavior.
478
478
 
479
479
  An author-initiated failed exit returns to a parent as `{ exited: true, status: "failed" }` with its reason and partial outputs; it does not throw. An unintentional child failure still throws, so check `child.exited === true` before reading required child outputs and use the discriminator to branch. The lifecycle terminal notice uses the same steer/trigger-turn delivery path and references partial outputs so the launching agent does not need a separate status call.
480
480
 
@@ -631,7 +631,7 @@ export default workflow({
631
631
 
632
632
  Passing a definition directly to `ctx.workflow(...)` uses the child definition's normalized name for replay metadata and the default boundary label.
633
633
 
634
- `ctx.workflow(workflowDefinition)` starts a nested workflow behind a parent boundary stage named `workflow:<workflow-name>` by default. User-facing status and graph views flatten a valid child graph into the parent run recursively, so composition behaves like inlining the child workflow code: child stages, HIL prompt nodes, and deeper imported workflows appear in one expanded graph. When Atomic hides a valid import boundary, every boundary parent connects to every child root, and every child terminal connects to each downstream dependent of the boundary. Every visible child node keeps a distinct virtual graph ID and its exact `{ runId, stageId }` control target, even when sibling or repeated child workflows reuse local stage IDs or names. Attach, send, pause, interrupt, resume, stage selection, and post-mortem chat therefore route to the nested run and stage that actually own the node. Implementation-owned child runs are not shown as separate top-level `/workflow status` entries. The returned child result has:
634
+ `ctx.workflow(workflowDefinition)` starts a nested workflow behind a parent boundary stage named `workflow:<workflow-name>` by default. User-facing status and graph views flatten a valid child graph into the parent run recursively, so composition behaves like inlining the child workflow code: child stages, HIL prompt nodes, and deeper imported workflows appear in one expanded graph. When Atomic hides a valid import boundary, every boundary parent connects to every child root, and every child terminal connects to each downstream dependent of the boundary. Every visible child node keeps a distinct virtual graph ID and its exact `{ runId, stageId }` control target, even when sibling or repeated child workflows reuse local stage IDs or names. Attach, send, pause, resume, stage selection, and post-mortem chat therefore route to the nested run and stage that actually own the node. Implementation-owned child runs are not shown as separate top-level `/workflow status` entries. The returned child result has:
635
635
 
636
636
  | Field | Meaning |
637
637
  |---|---|
@@ -102,14 +102,17 @@ The current `goal`, `ralph`, and `open-claude-design` defaults are:
102
102
 
103
103
  | Role | Primary model |
104
104
  |---|---|
105
- | Goal and Ralph orchestrators | `openai-codex/gpt-6-astra:high` |
106
- | Goal reviewers | `openai-codex/gpt-6-astra:xhigh` |
107
- | Ralph prompt engineer and research | `openai-codex/gpt-6-astra:high` |
105
+ | Goal and Ralph orchestrators | `openai-codex/gpt-6-astra:medium` |
106
+ | Goal reviewers | `openai-codex/gpt-6-astra:high` |
107
+ | Ralph prompt engineer | `openai-codex/gpt-6-astra:high` |
108
+ | Ralph research | `openai-codex/gpt-6-astra:medium` |
108
109
  | Ralph reviewer A | `anthropic/claude-fable-5-1:high` |
109
- | Ralph reviewer B | `openai-codex/gpt-6-astra:xhigh` |
110
- | Open Claude Design model stages | `openai-codex/gpt-6-astra:high` |
110
+ | Ralph reviewer B | `openai-codex/gpt-6-astra:high` |
111
+ | Open Claude Design model stages | `anthropic/claude-fable-5-1:medium` |
111
112
 
112
- Astra-led chains try GitHub Copilot Astra, OpenAI Astra, Anthropic Fable 5.1, then GitHub Copilot Fable 5.1 before older models. Ralph reviewer A starts with GitHub Copilot Fable 5.1, then Codex, Copilot, and OpenAI Astra at `xhigh`. Later fallback order is role-specific: Ralph research puts Fable 5 before Sol, while the orchestrators put Sol before Fable 5. Reviewer A puts Kimi before Sol; Goal reviewers and Ralph reviewer B put Sol before Kimi. OpenRouter mirrors follow direct-provider candidates. These are configured preferences, not guarantees of provider or account availability.
113
+ Astra-led chains try GitHub Copilot Astra, OpenAI Astra, Anthropic Fable 5.1, then GitHub Copilot Fable 5.1 before older models. Ralph reviewer A starts with GitHub Copilot Fable 5.1, then Codex, Copilot, and OpenAI Astra at `high`. Open Claude Design starts with Anthropic and Copilot Fable 5.1, then Codex, Copilot, and OpenAI Astra, all at `medium`; its OpenRouter group also puts Fable 5.1 before Astra.
114
+
115
+ Goal and Ralph orchestration, Ralph research, and design use Fable 5.1/Fable 5 at `medium` and Sol at `high` in their fallbacks. Goal and Ralph reviewers use Astra/Sol at `high`. Ralph prompt refinement keeps its Fable `high` and Sol `xhigh` fallbacks. Later fallback order remains role-specific: Ralph research puts Fable 5 before Sol, while the orchestrators put Sol before Fable 5. Reviewer A puts Kimi before Sol; Goal reviewers and Ralph reviewer B put Sol before Kimi. OpenRouter mirrors follow direct-provider candidates. These are configured preferences, not guarantees of provider or account availability.
113
116
 
114
117
  ### Six composable pattern builtins
115
118
 
@@ -61,7 +61,7 @@ The workflow tool action surface is:
61
61
  - discovery: `list`, `get`, `inputs`, plus `models` for the configured model catalog
62
62
  - execution: named `run` with validated `workflow` and `inputs`
63
63
  - inspection: `status`, `stages`, `stage`, `transcript`
64
- - prompt response: `answer`; run control: `pause`, `interrupt`, `quit`, `resume`; free-form stage communication: ordinary Intercom `send`/live `ask` to `workflow:<rootRunId>/<segment>[/<segment>...]` path targets, including `*` and `**` globs
64
+ - prompt response: `answer`; run control: `pause`, `quit`, `resume`; free-form stage communication: ordinary Intercom `send`/live `ask` to `workflow:<rootRunId>/<segment>[/<segment>...]` path targets, including `*` and `**` globs
65
65
  - rediscovery: `reload`
66
66
 
67
67
  Every registered `workflow` tool call has one hard two-minute wall-clock deadline at the shared public tool boundary. The deadline covers request handling through the returned result; for background `run` and `resume`, it therefore covers startup/resume admission and acknowledgement only, not the workflow execution that continues after acknowledgement. A deadline returns one structured result:
@@ -77,13 +77,13 @@ Every registered `workflow` tool call has one hard two-minute wall-clock deadlin
77
77
  }
78
78
  ```
79
79
 
80
- Expiry aborts the request operation signal so work that supports cancellation can stop, discards any later success or error, and never retries the action. The interactive engine remains available for the next command. For mutating actions (`reload`, `run`, `answer`, `pause`, `resume`, `interrupt`, and `quit`), the error additionally says that the outcome is unknown and instructs you to inspect workflow status before retrying; a timeout never claims that a mutation succeeded. When a timed-out `run` has already allocated its detached run, the structured result includes that exact full `runId`; inspect `status` with that id before any retry. A timeout before run allocation has no `runId`. Read-only actions (`models`, `list`, `get`, `inputs`, `status`, `stages`, `stage`, and `transcript`) omit that unknown-state guidance.
80
+ Expiry aborts the request operation signal so work that supports cancellation can stop, discards any later success or error, and never retries the action. The interactive engine remains available for the next command. For mutating actions (`reload`, `run`, `answer`, `pause`, `resume`, and `quit`), the error additionally says that the outcome is unknown and instructs you to inspect workflow status before retrying; a timeout never claims that a mutation succeeded. When a timed-out `run` has already allocated its detached run, the structured result includes that exact full `runId`; inspect `status` with that id before any retry. A timeout before run allocation has no `runId`. Read-only actions (`models`, `list`, `get`, `inputs`, `status`, `stages`, `stage`, and `transcript`) omit that unknown-state guidance.
81
81
 
82
82
  Explicit user interruption is different from the request deadline. Before startup acknowledgement, interrupting a `run` tool call cancels its initialization owner, records an allocated root as locally `killed`, and prevents delayed setup from starting workflow code. An already-aborted request starts no action. Interrupted resume preparation releases a claim it has acquired instead of dispatching later. After startup acknowledgement, the workflow is detached: aborting the original caller does not stop it; use run-level controls.
83
83
 
84
84
  Cancellation does not undo mutations that already happened. A non-cancellable database write may still finish, with the final cancellation record queued after it. Inspect status and external effects before retrying an interrupted mutation. Request timeout retains its unknown-outcome behavior and does not cancel an accepted detached launch.
85
85
 
86
- From interactive chat, named workflow launches run in the background so the parent chat stays available. Run `/workflow connect <run>` to see agents working and chat with and steer each stage. Inspection, prompt-response, and control calls (`status`, `stages`, `stage`, `transcript`, `answer`, `pause`, `resume`, `interrupt`, `quit`) remain available while work runs.
86
+ From interactive chat, named workflow launches run in the background so the parent chat stays available. Run `/workflow connect <run>` to see agents working and chat with and steer each stage. Inspection, prompt-response, and control calls (`status`, `stages`, `stage`, `transcript`, `answer`, `pause`, `resume`, `quit`) remain available while work runs.
87
87
 
88
88
  The no-`runId` status listing includes bounded pending-stage rows after each run summary. Each row gives the display name, canonical stage ID, literal `pending` lifecycle, `pendingStageDeliveryAvailable`, and either the exact usable Intercom target or `unavailable`. Interactive status cards and run detail show the same identity/availability distinction within their width budgets. Status cards wrap exact targets onto continuation rows instead of rendering a partially truncated address; bounded omissions retain an explicit remaining-stage count.
89
89
 
@@ -122,6 +122,8 @@ If you copy a HIL workflow example into a headless session, it can pass dispatch
122
122
 
123
123
  <p align="center"><img src="../images/workflow-input-picker.png" alt="Workflow Input Picker" width="600" /></p>
124
124
 
125
+ Graph node cards show each model stage's effective model and thinking level beneath its status, including after fallback and durable resume. Long model names are truncated first, preserving the complete thinking level and a canonical `-fast` model suffix. This suffix is model identity, not a separate fast-mode switch or proof of service tier. Thinking `off` is omitted; unresolved model identity shows `—`. Tool nodes retain their `durable tool` body, and the `BACKGROUND` summary is unchanged.
126
+
125
127
  ## Workflow Commands
126
128
 
127
129
  ```text
@@ -131,10 +133,9 @@ If you copy a HIL workflow example into a headless session, it can pass dispatch
131
133
  /workflow <name> [key=value ...]
132
134
  /workflow connect [run-id]
133
135
  /workflow attach [run-id] [stage-id-or-name]
134
- /workflow pause [run-id] [stage-id-or-name]
136
+ /workflow pause [run-id|--all]
135
137
  /workflow status [run-id]
136
138
  /workflow status --all
137
- /workflow interrupt <run-id|--all>
138
139
  /workflow quit <run-id|--all>
139
140
  /workflow resume <run-id> [stage-id-or-name] [message]
140
141
  /workflows [full-workflow-uuid]
@@ -147,7 +148,7 @@ Common controls:
147
148
  /workflow status # list retained active and terminal runs
148
149
  /workflow connect <run-id> # graph viewer, including terminal runs
149
150
  /workflow attach <run-id> <stage> # chat with a single stage
150
- /workflow interrupt <run-id> # pause resumably
151
+ /workflow pause <run-id> # pause resumably
151
152
  /workflow resume <run-id> [stage] msg # forward a steer message and resume
152
153
  /workflow quit <run-id> # pause gracefully and keep the run resumable
153
154
  /workflows [run-id] # retained alias for /workflow resume (history picker)
@@ -166,7 +167,7 @@ Surface behavior:
166
167
  - **Footer context** - An attached live stage chat shows its own current folder and Git branch and mirrors live extension status lines such as the MCP server indicator. Branch changes trigger a repaint through the host's cached footer provider. The compact `/tasks` picker retains this context and the stage's model and reasoning level even while its foreground turn streams; detail, transcript, input, and confirmation pages remain fullscreen.
167
168
  - **Working animation lifecycle** - Ordinary attached-stage work keeps the same exact one-cell `∀` visible while following the active workflow theme's dark → accent → bright/bold → accent → dark luminance ramp every 88ms. Every agent and SDK turn resets to the dark regular phase with a fresh lifecycle-relative cadence; turn, terminal, error, replacement, and disposal cleanup stop the active timer without stale repaint. In an eligible retained-stage chat, every accepted idle follow-up — including a workflow-authored `stage.sendUserMessage(...)` after a prior turn ended — shows Working on admission or attach, including while Atomic restores a saved retained conversation, and keeps it through prompt startup, pre-turn compaction, and agent handoff. Attaching or remounting mid-delivery paints immediately rather than waiting for the turn's first event. A message queued into a live turn with `followUp`/`steer` uses that turn's existing status instead of starting a new one. A no-turn result, prompt or restore error, or terminal completion removes it; once the last accepted post-terminal delivery settles, a leftover start cannot bring it back. An accepted manual retry clears stale status from the prior prompt before showing new pre-stream activity. `NO_COLOR` retains regular/bold activity without foreground-color escapes. Reduced motion uses a static regular accent `∀` without an animation timer; factual automatic retry, fallback, compaction, cancellation, and error copy retains precedence.
168
169
  - **Subagent statusline** - If a subagent is running while the fullscreen workflow graph is open, the graph statusline mirrors its summary so the run remains visible; hide the graph with `h`, leave it with `ctrl+x`, or reconnect later to return to the full below-editor widget.
169
- - **Run control** - Use `interrupt`, `pause`, and `resume` for resumable live work. Pause/interrupt holds a stage's queued steering and follow-up items in place without dequeuing them or starting continuation; `resume` releases those items once in their existing per-queue order, but queue release alone does not start a model turn. `resume` on a non-paused run reopens the saved snapshot or overlay. Use `quit` to pause a live run gracefully while preserving it for `/workflow resume`.
170
+ - **Run control** - Use `pause` and `resume` for resumable live work. Pause holds a stage's queued steering and follow-up items in place without dequeuing them or starting continuation; `resume` releases those items once in their existing per-queue order, but queue release alone does not start a model turn. `resume` on a non-paused run reopens the saved snapshot or overlay. Use `quit` to pause a live run gracefully while preserving it for `/workflow resume`. `/workflow pause` selects the active run by default, accepts a full run id or `--all`, and does not open a stage picker. Use the workflow tool's `stageId` for stage or tool-node targeting.
170
171
  - **Rediscovery** - Use `/workflow reload` after adding, editing, installing, or removing workflow resources or package manifest workflow entries and you want Atomic to rediscover them in-process ([Reloading workflow resources](#reloading-workflow-resources)).
171
172
  - **Status listing** - `/workflow status` lists all retained active and terminal top-level runs by default; implementation-owned nested child runs are flattened into their parent workflow rather than listed separately. `/workflow status --all` is retained as a compatibility alias.
172
173
 
@@ -222,9 +223,7 @@ workflow({ action: "resume", runId: "<full-run-uuid>", stageId: "review", messag
222
223
 
223
224
  workflow({ action: "pause", runId: "<full-run-uuid>" })
224
225
  workflow({ action: "pause", runId: "<full-run-uuid>", stageId: "review" })
225
-
226
- workflow({ action: "interrupt", runId: "<full-run-uuid>" })
227
- workflow({ action: "interrupt", all: true })
226
+ workflow({ action: "pause", all: true })
228
227
 
229
228
  workflow({ action: "resume", runId: "<full-run-uuid>" })
230
229
  workflow({ action: "resume", runId: "<full-run-uuid>", stageId: "review", message: "continue" })
@@ -234,7 +233,7 @@ workflow({ action: "quit", all: true })
234
233
 
235
234
  // Abort one in-flight ctx.tool node without pausing the run.
236
235
  workflow({ action: "quit", runId: "<full-run-uuid>", stageId: "tool:<argsHash>" })
237
- workflow({ action: "interrupt", runId: "<full-run-uuid>", stageId: "publish-artifact" })
236
+ workflow({ action: "pause", runId: "<full-run-uuid>", stageId: "publish-artifact" })
238
237
 
239
238
  workflow({ action: "reload", reason: "added team workflow" })
240
239
  ```
@@ -243,7 +242,7 @@ Control behavior:
243
242
 
244
243
  - `runId` requires the full 36-character run UUID for every lifecycle and inspection action, including `status`. User-facing status surfaces print that exact value, so pass it back verbatim; typed prefixes are rejected with a distinct `Run id must be a full 36-character UUID` diagnostic rather than resolved. Because ids are matched exactly and are unique, no run target is ambiguous. Status lists and run pickers show top-level user-launched workflows; nested child runs are implementation details of the expanded parent graph.
245
244
  - `status`, `stages`, `stage`, and `transcript` with an explicit full `runId` first use the current session store, then perform one exact DBOS hydration when that id is absent locally. This is inspection only: Atomic does not claim ownership, change status, run workflow code, or resume the workflow. A stale durable `running` root is shown as `crashed` with its resumability and an explicit `/workflow resume <id>` hint; fresh work owned by another Atomic process remains `running`, offers read-only status guidance, and stays protected from local control or resume. Deleted/tombstoned, absent, malformed, cyclic, orphaned, nonreciprocal, out-of-scope, and duplicate-node records report distinct failures instead of inventing a partial graph. `status` without `runId` remains current-session-only and never scans durable history.
246
- - `status` without `runId` lists every top-level run in the session with a concise per-run summary: the full run id, workflow name, run status, started/ended timing with pause-adjusted elapsed time, currently active stages, and awaiting-input details (count plus the stage, prompt id, kind, and message for each pending human prompt). In-flight runs are listed first. The summaries carry the exact identifiers that `answer`, `pause`, `resume`, `interrupt`, and `quit` accept, so an orchestrating agent can list runs and act on them directly.
245
+ - `status` without `runId` lists every top-level run in the session with a concise per-run summary: the full run id, workflow name, run status, started/ended timing with pause-adjusted elapsed time, currently active stages, and awaiting-input details (count plus the stage, prompt id, kind, and message for each pending human prompt). In-flight runs are listed first. The summaries carry the exact identifiers that `answer`, `pause`, `resume`, and `quit` accept, so an orchestrating agent can list runs and act on them directly.
247
246
  - `statusFilter` narrows the `status` run listing: run statuses (`pending`, `running`, `paused`, `blocked`, `completed`, `failed`, `skipped`, `cancelled`, `killed`) match runs directly, `awaiting_input` selects runs with at least one stage awaiting input or pending human prompt, and `all` (the default) includes everything.
248
247
  - `format: "json"` on data-bearing inspection actions (`status`, `stages`, `stage`, `transcript`) returns the full structured result; the default text output for `status` is the concise per-run summary list.
249
248
  - `status` / `status <runId>` show terminal `ctx.exit(...)` statuses (`completed`, `skipped`, `cancelled`, or `blocked`) and the optional exit reason when one was supplied.
@@ -254,21 +253,20 @@ Control behavior:
254
253
  - `transcript` is reference-first with a small preview by default: it returns metadata, transcript paths, and up to 5 recent entries. For targeted lookup, quote the exact `sessionFile`/`transcriptPath` value without changing platform separators (preserve Windows backslashes), search it with `rg` or `grep`, then read only small surrounding ranges. Text results include JSON-escaped `sessionFileJson`/`transcriptPathJson` lines for copy-safe path literals. Pass explicit `tail` or `limit` to override the 5-entry preview; `tail` overrides `limit`; `includeToolOutput` includes captured snapshot tool output in snapshot transcript results.
255
254
  - `answer` responds only to a pending primitive or structured human-input prompt. It accepts `promptId` plus `response`, `text`, or `message`, preserves prompt-kind validation, and never sends stage chat, steers, resumes, or starts a model turn.
256
255
  - Send free-form updates through ordinary Intercom to `workflow:<rootRunId>/<segment>[/<segment>...]`; `*` matches one segment and `**` any depth. Use `intercom list` inside the invocation group to see live, pending, and possible future targets. Atomic delivers immediately to live stages and queues matching future stages, delivering them before their first model turn. `workflow:<rootRunId>/**` remains sticky for every future descendant until root termination; narrower name and pattern sends reach every future match. Valid paths outside the known set queue with a `notInKnownSet` warning and settle undeliverable at terminal only if never delivered. Use `ask` once the target has a reply-capable live session. Use `workflow resume` only for paused workflow control.
257
- - `pause`, `interrupt`, and `quit` can target one top-level run or `all: true`; `stageId` cannot be combined with `all: true`. Stage-scoped `pause` and `interrupt` controls can target a visible nested child stage from the expanded graph. Atomic routes stage controls to the owning nested run internally.
258
- - `interrupt` and `quit` can also name one in-flight `ctx.tool` node with `stageId`, by expanded node id, local `tool:<argsHash>` id, or tool name. Both mean the same thing for a tool: abort that single call now. Tool nodes stay non-attachable — this is an abort control, not a chat target. Identifiers resolve exactly first and then uniquely; a name shared by two tool nodes (or by a stage and a tool) returns the same ambiguity diagnostic stages get, listing each match as `<name> (tool)`.
256
+ - `pause` and `quit` can target one top-level run or `all: true`; `stageId` cannot be combined with `all: true`. Stage-scoped `pause` controls can target a visible nested child stage from the expanded graph. Atomic routes stage controls to the owning nested run internally.
257
+ - `pause` and `quit` can also name one in-flight `ctx.tool` node with `stageId`, by expanded node id, local `tool:<argsHash>` id, or tool name. Both mean the same thing for a tool: abort that single call now. Tool nodes stay non-attachable — this is an abort control, not a chat target. Identifiers resolve exactly first and then uniquely; a name shared by two tool nodes (or by a stage and a tool) returns the same ambiguity diagnostic stages get, listing each match as `<name> (tool)`.
259
258
  - Aborting one tool node leaves every sibling stage and sibling tool node running and does not pause the run. The node becomes `cancelled`, writes no replayable checkpoint, and re-runs on a later resume. Whether the run itself survives is ordinary author control flow: an awaited `ctx.tool` that is aborted rejects, exactly as it would for any other failure, unless the workflow catches it. A node that has already settled reports that it is not running rather than silently succeeding.
260
259
  - If targeted cancellation escapes workflow code, the failed run records `failedToolNodeId`, not a fabricated `failedStageId`. Resume the run without a stage override to retry that unfinished tool and replay completed work. The tool remains a non-attachable `cancelled` tool node.
261
260
  - Whole-run `quit` stays authoritative even if workflow code catches the tool rejection. A catch may run cleanup, but its returned outputs do not convert the quit into a completed run: the executor suspends and quit's paused/resumable record stands. To abort one call and intentionally keep the workflow going, target that node instead of quitting the run.
262
261
  - A targeted tool abort reports the node outcome and the run separately: `status: "cancelled"` for the node it cancelled, `stageId` for that node, `abandoned` when the callback ignored its signal, and `workflowStatus` for the run status *observed* when the action returned. It never reports `paused`, and it never predicts what the run does next.
263
- - `pause` never accepts a tool node: `ctx.tool` has no turn boundary to stop at, so Atomic rejects it with `Tool nodes cannot be paused; ... Use interrupt or quit to abort it.` instead of a silent no-op.
264
- - `interrupt` and `pause` preserve resumable live work. With no active stage/tool handle (including initialization and between-node waits), they keep the same executor paused rather than quitting it. Run-level `resume` on that live process releases the barrier exactly once; later tracked steps, cached replay, and run completion wait for that explicit resume. An already-started untracked JavaScript await or non-cancellable I/O may still finish; pause cannot physically freeze arbitrary author code or roll back effects. A stage declared during this pause defers registration until its asynchronous method runs after resume; synchronous session access requires resume first.
262
+ - `pause` preserves resumable live work. With no active stage/tool handle (including initialization and between-node waits), it keeps the same executor paused rather than quitting it. Run-level `resume` on that live process releases the barrier exactly once; later tracked steps, cached replay, and run completion wait for that explicit resume. An already-started untracked JavaScript await or non-cancellable I/O may still finish; pause cannot physically freeze arbitrary author code or roll back effects. A stage declared during this pause defers registration until its asynchronous method runs after resume; synchronous session access requires resume first.
265
263
  - A stage pause cancels that generation's owned task executions, not its queued messages or future workflow stages. User and Intercom messages remain held until resume; resume allows fresh tasks and never restarts the cancelled executions. Cancellation and cleanup failures reject the pause rather than reporting a successful stop.
266
264
  - A whole-run executor-only pause also holds already-live owned child workflows, including children with no tracked node yet. Resume persists the root's running transition before releasing those child executors; a child-scoped control leaves sibling workflows alone.
267
265
  - Live executor resume does not require a checkpoint. Cross-process resume still requires durable checkpoint or pending-prompt progress: losing a zero-progress live owner cannot reconstruct its JavaScript continuation. Cancelling the original startup request before acknowledgement remains terminal cancellation, not this live pause.
268
266
  - `resume` can target a stage with `stageId`; the target may be an exact stage id or an exact stage name. `message` is forwarded to paused work. For a live interrupted streaming prompt, Atomic preserves the existing prompt loop without duplicating the user message and injects `Continue where you left off. If you believe you are finished with your original task (or a redefined task if the user told you), stop.` when required before normal readiness-gate completion. For a paused stage that was idle waiting for a new stage-chat turn, a non-empty message resumes the stage and starts exactly one fresh prompt containing that message; an empty resume releases the pause without creating a prompt.
269
267
  - An explicit workflow-tool `resume` target that is absent from the current session store triggers targeted DBOS discovery before Atomic returns `Run not found`. The target must be a full run UUID; an eligible exact ID resumes under the original workflow ID, and a malformed target is rejected before any durable lookup happens. Resource-loading and durable-backend failures remain visible. Ordinary workflow-tool `status` listing stays session-local and does not eagerly hydrate durable history.
270
268
  - Exact-id durable inspection is separate from resume. `status`, `stages`, `stage`, and `transcript` may hydrate one missing-local root for read-only inspection, but they never claim it or execute replay. Only an explicit `resume` action enters the claim-and-dispatch path.
271
- - Run-level `quit` gracefully pauses in-flight work and preserves eligible checkpointed runs for `/workflow resume`. Runs waiting during initialization or between nodes are also controllable, even with no active stage or tool. Unlike live pause/interrupt, an executor-only quit retires the owner (including one already paused); without usable durable progress it is nonresumable and offers no resume hint.
269
+ - Run-level `quit` gracefully pauses in-flight work and preserves eligible checkpointed runs for `/workflow resume`. Runs waiting during initialization or between nodes are also controllable, even with no active stage or tool. An executor-only quit retires the owner (including one already paused); without usable durable progress it is nonresumable and offers no resume hint.
272
270
  - `reload` refreshes discovered workflow resources in-process; the optional `reason` is echoed in the result.
273
271
 
274
272
  Use slash commands for graph connect and stage attach because those are interactive TUI surfaces. When a run needs user input or attention, tell the user instead of polling silently.
@@ -277,7 +275,7 @@ Use slash commands for graph connect and stage attach because those are interact
277
275
 
278
276
  Graceful quit is idempotent for an already-paused resumable run. If a run is waiting on `ctx.ui`, quit preserves its current DBOS prompt reservation. Answers cannot advance paused workflow code until explicit resume; checkpointing the answer releases exactly that reservation generation. Concurrent and nested prompts use composed scopes and independent DBOS reservation tokens.
279
277
 
280
- When no stage or tool owns the pending await, whole-run `pause`, `interrupt`, and `quit` suspend the executor itself. The local root stops reporting `running` while the durable pause is pending. Late workflow completion, cached-stage replay, and later `ctx.*` calls cannot revive that executor or modify a replacement run. This does not forcibly stop arbitrary JavaScript outside `ctx.*`; already-started untracked code and non-cancellable I/O may finish later. Control results disclose this limit. A stop before any checkpoint progress is retained as paused/nonresumable; start a new run only after reconciling any external effects.
278
+ When no stage or tool owns the pending await, whole-run `pause` holds the live executor until resume; `quit` retires it at a durability boundary. Neither forcibly stops arbitrary JavaScript outside `ctx.*`; already-started untracked code and non-cancellable I/O may finish later. Control results disclose this limit. An executor-only quit before any checkpoint progress is retained as paused/nonresumable; start a new run only after reconciling any external effects.
281
279
 
282
280
  **Quit closes `ctx.tool` admission before it becomes a durability boundary.** A run-level quit pauses controllable stages and waits for their acknowledgements, then closes the root-shared tool-admission boundary shared by the root run and every nested run. Closing is what makes the following scan final: a call admitted while the stage pauses were still being acknowledged is included, and no call can start afterwards — not even while the durable write is in flight. Quit then aborts that complete set, waits a bounded interval for the callbacks to settle, and only then records the durable paused transition and marks the run resumable.
283
281
 
@@ -374,13 +372,13 @@ Ended recoverable blocks, including reviewer execution failures, use this same i
374
372
 
375
373
  Completed top-level `ctx.tool` nodes also replay into the fresh run. See [`ctx.tool` — durable cached tool execution](#ctxtool--durable-cached-tool-execution). A fail-closed topology mismatch ends that continuation; the durable source stays blocked and resumable, and the same session can retry after the continuation settles.
376
374
 
377
- Deliberate control actions on a top-level run report themselves too. `/workflow <name>` emits a `WORKFLOW STARTED` notice (`▶`), `/workflow pause` a `WORKFLOW PAUSED` notice (`⏸`, warning tone), `/workflow quit` a `WORKFLOW QUIT` notice (`⏹`, warning tone, carrying a `resumable` field), and `/workflow resume` a `WORKFLOW RESUMED` notice (`▶`). All four travel the same steer delivery, capped-backoff retry, and notice-card path as the failure notice. The paused and quit text states that the stop was deliberate and user-requested and tells the model not to resume the run or take the work over unless asked, with `/workflow resume <run-id>` as the card hint; the resumed text does not, because the run is progressing again.
375
+ Attributed control actions on a top-level run report themselves too. `/workflow <name>` emits a `WORKFLOW STARTED` notice (`▶`), `/workflow quit` a `WORKFLOW QUIT` notice (`⏹`, warning tone, carrying a `resumable` field), and `/workflow resume` a `WORKFLOW RESUMED` notice (`▶`). These travel the same steer delivery, capped-backoff retry, and notice-card path as the failure notice. The quit text states that the stop was deliberate and user-requested and tells the model not to resume the run or take the work over unless asked, with `/workflow resume <run-id>` as the card hint; the resumed text does not, because the run is progressing again.
378
376
 
379
- **Only user actions notify.** The equivalent `workflow({ action: "run" | "pause" | "quit" | "resume" })` tool calls stay silent: the tool result already tells the agent what it just did, and a second steer would spend a turn repeating it. `/workflow interrupt` raises no notice at all. Engine-internal transitions are silent for the same reason a notice must name an actor to exist — answering a human-in-the-loop prompt resumes the run internally, and reporting that would both flood the chat and defeat the deliberate decision that `awaiting_input` never wakes the model.
377
+ **Only attributed user actions notify.** The equivalent `workflow({ action: "run" | "pause" | "quit" | "resume" })` tool calls stay silent: the tool result already tells the agent what it just did. `/workflow pause` does not attribute an actor or raise a main-chat control notice. Engine-internal transitions are silent too — answering a human-in-the-loop prompt resumes the run internally without waking the model. Workflow activity and lifecycle observation report pause requests independently of chat notices.
380
378
 
381
- **Two attributions.** *Origin* is who launched the run and renders on every kind as "which you started" or "which the user started"; it is set once at dispatch, persisted through session restore and durable resume, and inherited by a continuation from the run it continues. *Actor* is who performed this one event and renders as "The user paused" or "You paused". They differ routinely — the agent starts a run and the user quits it. A run with no recorded origin, including a legacy or restored snapshot, omits the clause entirely rather than guessing.
379
+ **Two attributions.** *Origin* is who launched the run and renders on every kind as "which you started" or "which the user started"; it is set once at dispatch, persisted through session restore and durable resume, and inherited by a continuation from the run it continues. *Actor* is who performed this one event and renders as "The user resumed" or "You resumed". They differ routinely — the agent starts a run and the user quits it. A run with no recorded origin, including a restored snapshot, omits the clause entirely rather than guessing.
382
380
 
383
- **One notice per request.** A whole-run pause or resume reports at run scope. A stage-scoped `/workflow pause <run> <stage>` that leaves other stages running reports at stage scope, and one that stops the last active stage reports the run instead — never a stage card and a run card for the same request. A quit reports only the quit, never the pause it publishes on the way. Because control actions are reversible, these notices are deduplicated by run id *and* the occurrence timestamp, so pause → resume → pause → resume emits four notices while repeated snapshot invalidations at one unchanged state emit one. Resuming reports a resume and never a start, whoever asked for it — a resumed run re-enters the dispatch path, so keying that on the resume rather than on the requester is what stops an agent-requested resume of a user-started run from being announced as a fresh launch. Resuming a failed or blocked run launches a continuation under a fresh run id, and its notice names both ("run 4d7e, continuing run 8c31"); resuming a quit run reuses the original workflow id so durable checkpoints replay, so that notice names the one id. A run that is already started, paused, or quit when notifications install — restore, replay, `/reload`, or a session-preserving reinstall — is seeded as delivered and stays silent, and nested `ctx.workflow(...)` child runs never notify at top level.
381
+ **One notice per attributed request.** A whole-run resume reports at run scope; a stage-scoped resume reports at stage scope when siblings remain paused. A quit reports only the quit, never the pause it publishes on the way. Notices are deduplicated by run id and occurrence timestamp, so repeated snapshot invalidations at one unchanged state emit one. Resuming reports a resume and never a start, whoever asked for it. Resuming a failed or blocked run launches a continuation under a fresh run id, and its notice names both; resuming a quit run reuses the original workflow id so durable checkpoints replay. A run already started, paused, or quit when notifications install — restore, replay, `/reload`, or a session-preserving reinstall — is seeded as delivered and stays silent, and nested child runs never notify at top level.
384
382
 
385
383
  Configure lifecycle behavior with `workflowNotifications.enabled` (default `true`) and `workflowNotifications.notifyOn` (default `["started", "completed", "failed", "blocked", "budget_warning", "awaiting_input", "paused", "quit", "resumed"]`). A config that pins `notifyOn` explicitly keeps exactly the kinds it lists, so `notifyOn: ["failed"]` suppresses every control notice. `budget_warning` is delivered once per run and dimension through the same lifecycle-notice renderer.
386
384
 
@@ -424,6 +422,12 @@ Atomic workflows use **DBOS/Postgres as their sole persistent workflow backend**
424
422
 
425
423
  **Running as root (Linux).** PostgreSQL refuses to run as UID 0, so a root Atomic process (containers, CI sandboxes, eval harnesses) resolves an unprivileged system account (`postgres`, `nobody`, or `daemon`) and keeps the cluster under `/var/lib/atomic-postgres` instead (a root home directory is untraversable for that account). Before any owner command runs, Atomic probes that candidate runner itself and accepts it only when it proves the account's exact UID, exact primary GID, membership in that primary group, and no root group; legitimate additional nonroot groups remain valid. The retained native direct-Postgres spawn also clears inherited supplementary groups before setting the primary GID and UID. When the embedded binaries themselves sit under an untraversable prefix (for example a root-owned `~/.nvm` global install), Atomic publishes and reuses one exact package-content runtime generation under a root-owned cache. Published runtime files remain readable/executable but not writable by the Postgres account. Runtime reuse and publication re-snapshot the current source, publication validates the deterministic path after rename, and source mutation, corrupt content, or setup-lease displacement fails closed without unbounded repair copies.
426
424
 
425
+ **Administrator accounts (Windows).** Atomic can start embedded Postgres from an elevated terminal or an administrative account without changing your account or system permissions. The server runs with reduced privileges, as it does under PostgreSQL's own launcher. Regular Windows accounts remain supported. On either path, server output goes to `~/.atomic/postgres/v18.log`, not your terminal; unrelated commands starting concurrently do not keep the server's log file open. If Postgres exits during startup, Atomic reports the recent log output instead of waiting for a generic readiness timeout. Check that log for configuration or cluster errors; do not delete the cluster while a server may still be using it.
426
+
427
+ If another service takes port `5439` while the embedded server is starting, a reachable port does not override a detected server exit. Atomic also reports process-status query failures instead of hiding them behind a readiness timeout. Inspect the reported error and server log before retrying.
428
+
429
+ Custom Windows launchers can still use executable names resolved through `PATH` or relative executable paths. `.cmd` and `.bat` launchers accept arguments on administrative accounts, including when the launcher path contains spaces. Explicit `cmd.exe` invocations retain their usual argument handling. Working directories with a `\\?\` prefix are supported when removing the prefix preserves the exact path; if `cmd.exe` reports an unsupported UNC working directory, use an ordinary local directory path. If startup reports that batch file arguments are invalid, remove carriage returns and line breaks from those arguments. If it reports that a string contains NUL characters, check the supplied paths, arguments, and environment entries. Remove the embedded NUL rather than retrying with a truncated value; no server is started for that request.
430
+
427
431
  If embedded provisioning fails without leaving retained-process cleanup pending, Atomic tries DBOS's reusable `dbos-db` Docker container. If DBOS still cannot become ready, workflows **degrade to a process-local in-memory backend with a loud warning** instead of refusing to run: the run executes normally, but its state does not survive the process and `/workflow resume` after exit has nothing to restore. Fix the configured database or set `DBOS_SYSTEM_DATABASE_URL` to a working Postgres to restore durability.
428
432
 
429
433
  **Multiple concurrent Atomic sessions.** Every Atomic process launches DBOS with a unique executor id, and running root workflows carry owner/heartbeat metadata. Once an active model stage has a session path, Atomic records that identity after the stage-start record and awaits the checkpoint before the first model use, then runs serialized, unref'd liveness checkpoints on a bounded 30-second cadence for the root and nested scoped workflows. Each accepted checkpoint refreshes root metadata; timers stop on every stage exit and cannot keep Atomic alive. A persistent checkpoint fault fails the active stage instead of disappearing in a detached timer. A stage that is shutting down drains the checkpoint still in flight rather than abandoning it, so a failure that lands after the model turn finished is reported instead of discarded, and a stage whose final durability checkpoint fails is recorded as `failed` rather than `completed` — its caller receives the error and its concurrency slot is released either way. **Running workflows are never resume targets**: a running row with a fresh heartbeat is hidden from every session's picker and refused by direct `/workflow resume <id>` — resuming a workflow that is executing elsewhere would double-dispatch it. Once the heartbeat goes stale (about two minutes after a crash), an exact inspection or the resume picker reports the workflow as `crashed`.
@@ -452,11 +456,11 @@ Durable `/workflow resume` preserves completed stage metadata, active-stage elap
452
456
 
453
457
  Each new Atomic process that reopens unfinished work starts from the latest saved baseline, so repeated process-boundary resumes keep stable boundary/child ids, status, graph, and lifecycle duration cumulative without double-counting pauses. A stage paused at ten seconds resumes at ten seconds, and the main-chat dashboard reports prior-session elapsed plus current-session elapsed. Completed inspection uses that same accumulated run timing rather than DBOS record wall-clock age.
454
458
 
455
- Repeated, sibling, sequential, parallel, and multi-level child calls keep independent composed scopes and stable boundary order. The expanded graph routes attach, send, pause, interrupt, and resume through each stage's ordinary owning `{ runId, stageId}`. Resolution is exact: an expanded id, a local stage id, or a name must match whole, and colliding names return an ambiguity diagnostic rather than selecting the first match silently.
459
+ Repeated, sibling, sequential, parallel, and multi-level child calls keep independent composed scopes and stable boundary order. The expanded graph routes attach, send, pause, and resume through each stage's ordinary owning `{ runId, stageId}`. Resolution is exact: an expanded id, a local stage id, or a name must match whole, and colliding names return an ambiguity diagnostic rather than selecting the first match silently.
456
460
 
457
461
  ### `ctx.tool` — durable cached tool execution
458
462
 
459
- The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state — status, timing, and dependency rows keep their own rows, and the card does not preview the result or error. In the graph viewer, focusing the node and pressing Enter, clicking it, or choosing it from the switcher opens a read-only host-style operator card from the snapshot: a status-tinted shaded rectangle with the same inner padding and header/body gap as the main-chat tool block, inset from the orchestrator header and footer bars, a `$ <tool-name>` call header, an optional short argument summary, and the result or error as its body. Running and completed call headers have no status marker; pending, failed, cached, and cancelled calls retain their quiet markers. It is collapsed by default and wraps the fully bounded result or error before showing its last visual rows, with `... (N earlier lines, ctrl+o Expand)` above the tail when the action is bound; the configured `app.tools.expand` action (`ctrl+o` by default) toggles the full bounded result or error and then a muted callback-source block when source exists. The graph statusline advertises the resolved expand key with `expand` or `collapse` alongside return-to-graph and scroll hints, including remapped keys, and omits that segment entirely when the action is unbound. The footer says `Took` for settled calls or `Elapsed` for running calls, using the same second-resolution duration as the main-chat tool block, with cached/replayed markers kept as a quiet suffix. The operator surface has no ARGS/RESULT/SOURCE/TIMING/MARKERS debug table and does not expose raw clock fields. Source capture uses `fn.toString()` at registration without re-executing the callback or reading a file. `↑`/`↓`, `PageUp`/`PageDown`, `Home`/`End`, the wheel, and the scrollbar all scroll the block, so a long payload stays readable on a keyboard-only session or a terminal without mouse reporting; Escape or `ctrl+x` returns to the graph. The message block is read-only and never offers chat attachment, steering, interrupt, or resume. Bounded payloads remain width-safe and mark truncation explicitly with `… [truncated]`; source tabs expand and control bytes become `\xNN`, while cyclic payloads, throwing `toJSON`, or throwing property getters render `<cycle>`, `<unserializable>`, or `<unreadable>` instead of crashing the view. The same cap applies to what the live run snapshot retains for a tool node, while durable checkpoints keep the exact output, raw-args `argsHash`, and replay behavior unchanged.
463
+ The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state — status, timing, and dependency rows keep their own rows, and the card does not preview the result or error. In the graph viewer, focusing the node and pressing Enter, clicking it, or choosing it from the switcher opens a read-only host-style operator card from the snapshot: a status-tinted shaded rectangle with the same inner padding and header/body gap as the main-chat tool block, inset from the orchestrator header and footer bars, a `$ <tool-name>` call header, an optional short argument summary, and the result or error as its body. Running and completed call headers have no status marker; pending, failed, cached, and cancelled calls retain their quiet markers. It is collapsed by default and wraps the fully bounded result or error before showing its last visual rows, with `... (N earlier lines, ctrl+o Expand)` above the tail when the action is bound; the configured `app.tools.expand` action (`ctrl+o` by default) toggles the full bounded result or error and then a muted callback-source block when source exists. The graph statusline advertises the resolved expand key with `expand` or `collapse` alongside return-to-graph and scroll hints, including remapped keys, and omits that segment entirely when the action is unbound. The footer says `Took` for settled calls or `Elapsed` for running calls, using the same second-resolution duration as the main-chat tool block, with cached/replayed markers kept as a quiet suffix. The operator surface has no ARGS/RESULT/SOURCE/TIMING/MARKERS debug table and does not expose raw clock fields. Source capture uses `fn.toString()` at registration without re-executing the callback or reading a file. `↑`/`↓`, `PageUp`/`PageDown`, `Home`/`End`, the wheel, and the scrollbar all scroll the block, so a long payload stays readable on a keyboard-only session or a terminal without mouse reporting; Escape or `ctrl+x` returns to the graph. The message block is read-only and never offers chat attachment, steering, pause, or resume. Bounded payloads remain width-safe and mark truncation explicitly with `… [truncated]`; source tabs expand and control bytes become `\xNN`, while cyclic payloads, throwing `toJSON`, or throwing property getters render `<cycle>`, `<unserializable>`, or `<unreadable>` instead of crashing the view. The same cap applies to what the live run snapshot retains for a tool node, while durable checkpoints keep the exact output, raw-args `argsHash`, and replay behavior unchanged.
460
464
 
461
465
  When the workflow body fulfills but one or more admitted tool calls failed, Atomic promotes the first observed failure to the terminal run failure, regardless of admission order, and persists that selected tool-node identity for status inspection and lifecycle output. A direct uncaught `await ctx.tool(...)` rejection keeps the original error and persists its failed-node link through session and durable restore. First-event arbitration also preserves the selected node when concurrent failures throw the same object or primitive; unrelated later stage or body errors do not inherit a caught tool's origin. Tool admission remains open while author code can catch a failure and continue. Once the body settles and failure has won before any real cancellation, Atomic closes admission, cancels remaining non-failed tool nodes, waits for observed failed nodes to finish publication, and publishes the failed root without waiting for callbacks that ignore cancellation.
462
466
 
@@ -472,7 +476,7 @@ Recoverable output is explicit data flow. Atomic does not add a failed tool outc
472
476
 
473
477
  Cancellation, closed tool admission, and durable-storage faults still throw. They never become ordinary `{ ok: false }` callback outcomes. Omitting `failureMode: "return"` also keeps the existing behavior: an exhausted callback error rejects `ctx.tool` and fails the workflow unless author code catches it. Atomic persists that failed node and the root's selected tool link for later inspection, but excludes the failure record from the replay cache, so a resume or rerun calls the function again. Command failures that expose `exitCode`, `stdout`, or `stderr` remain failures even when a wrapper also uses cancellation-like text or codes; only a real run cancellation that wins the terminal race produces a killed/cancelled root.
474
478
 
475
- **Per-node cancellation and per-attempt deadlines.** Each logical `ctx.tool` call runs under its own `AbortController`, combined with the run's signal and handed to the callback as `{ signal }`. A run abort cascades to every live node; `workflow({ action: "quit"|"interrupt", runId, stageId })` naming one tool node aborts exactly that node and leaves its siblings alone. Without `timeoutMs`, retries share that logical call signal. With `timeoutMs`, every attempt gets a fresh signal and deadline; expiry aborts that attempt and becomes an ordinary attempt failure, while run cancellation and operator abort remain cancellation.
479
+ **Per-node cancellation and per-attempt deadlines.** Each logical `ctx.tool` call runs under its own `AbortController`, combined with the run's signal and handed to the callback as `{ signal }`. A run abort cascades to every live node; `workflow({ action: "quit"|"pause", runId, stageId })` naming one tool node aborts exactly that node and leaves its siblings alone. Without `timeoutMs`, retries share that logical call signal. With `timeoutMs`, every attempt gets a fresh signal and deadline; expiry aborts that attempt and becomes an ordinary attempt failure, while run cancellation and operator abort remain cancellation.
476
480
 
477
481
  A cancelled call is recorded as `cancelled`, not `failed`, and is never a run failure by itself: it writes no replayable `tool:` checkpoint and no `return_failure` outcome even under `failureMode: "return"`, so a cancellation can never replay as data. Targeted node aborts in either failure mode, and all return-mode cancellations, keep one inspection-only `tool-failure:` record carrying the cancellation message and a `cancelled: true` marker. This record preserves the unfinished node identity and parent edges across DBOS hydration, but is excluded from replay lookup. A callback that ignores its signal and returns late is caught before persistence, so its value cannot become a successful checkpoint either. Resume recomputes the same ordinal and `argsHash` from authored order; a tool-frontier continuation rejects changed identity or incompatible parents rather than running a different unfinished callback.
478
482
 
@@ -527,6 +531,8 @@ Changing `iteration` makes each loop pass a distinct durable call. Reusing the s
527
531
 
528
532
  The `/workflow resume` command mirrors `/resume` ergonomics and `/workflows` is its alias. With no id, it builds one newest-first picker from live runs that satisfy the shared resumability predicate and current DBOS resumable/completed records. DBOS is the authoritative catalog; selected records are hydrated and revalidated before resume or inspection. Running workflows never appear: fresh-heartbeat rows are excluded in every session to prevent double dispatch, and stale ones surface as `crashed`. A row whose durable checkpoint or referenced artifact is missing is not resumable and is omitted rather than offered and rejected later. Naming such an id explicitly still produces the existing clear no-checkpoint/not-resumable error.
529
533
 
534
+ If resume reports a checkpoint decoding error or an unavailable serializer, retain the full run ID and original diagnostic when reporting the problem. Do not delete saved progress or launch a fresh copy just to bypass the error: that can repeat completed side effects.
535
+
530
536
  The resume picker lists only runs the resume path would actually accept. One shared predicate (`isWorkflowRunResumable` in `packages/workflows/src/durable/resume-eligibility.ts`) backs both the picker and the `resume` command, so a row can never be offered and then refused. A run stops being resumable when it reaches a terminal state without a durable checkpoint or pending prompt progress, when its durable entry is explicitly deleted with Ctrl+D, or when its referenced artifacts are gone. The broader `connect`/`attach` pickers and `/workflow status` keep listing terminal runs for inspection; only `resume` is filtered.
531
537
 
532
538
  Rows carry semantic colors — completed green, paused yellow, failed/blocked/crashed red — and show checkpoint progress without the redundant pending-prompt count. The open picker live-updates on local run changes plus a bounded cross-session poll, so state transitions appear (and freshly running workflows disappear) without reopening it.
@@ -564,7 +570,7 @@ Validation uses the final retained transcript for a repeated stage replay key, s
564
570
 
565
571
  ### Recovering an uncaught tool abort
566
572
 
567
- After `workflow({ action: "interrupt", runId, stageId: "tool:<argsHash>" })`, inspect the run. If the author did not catch cancellation, its terminal status is `failed`, its selected frontier is `failedToolNodeId`, and the tool node is `cancelled`. Use `/workflow resume <full-run-uuid>` or `workflow({ action: "resume", runId: "<full-run-uuid>" })` **without `stageId`**. Completed model and tool checkpoints replay without invoking their callbacks; only the unfinished tool and subsequent work execute. This does not roll back external work a cancelled callback performed before abort, so verify that retrying that unfinished operation is safe.
573
+ After `workflow({ action: "pause", runId, stageId: "tool:<argsHash>" })`, inspect the run. If the author did not catch cancellation, its terminal status is `failed`, its selected frontier is `failedToolNodeId`, and the tool node is `cancelled`. Use `/workflow resume <full-run-uuid>` or `workflow({ action: "resume", runId: "<full-run-uuid>" })` **without `stageId`**. Completed model and tool checkpoints replay without invoking their callbacks; only the unfinished tool and subsequent work execute. This does not roll back external work a cancelled callback performed before abort, so verify that retrying that unfinished operation is safe.
568
574
 
569
575
  Fresh processes hydrate the same typed DBOS checkpoints before resume. Older records without `failedToolNodeId` are accepted only when the retained graph and inspection-only tool failure record identify one unfinished node matching the terminal targeted-abort error, with completed predecessor checkpoints intact. Missing checkpoints, multiple unfinished frontiers, corrupt identities, and cyclic or missing parent edges fail closed with `insufficient_state`. Error text alone, model/chat transcript text, and generic status tool-result snapshots are not executable recovery state.
570
576
 
@@ -589,7 +595,7 @@ The bounded, offline loading/recovery checks are:
589
595
  npm run test:integration -- test/integration/workflow-tool-abort-resume.test.ts test/integration/workflow-tool-frontier-consumption.test.ts test/integration/workflow-tool-node-quit-cli.test.ts
590
596
  ```
591
597
 
592
- These cover public interrupt/resume, typed legacy recovery and rejection, a fresh DBOS adapter, and the built Node CLI with a fixture provider. The CLI fixture uses an isolated in-memory backend, not the production database or a release workflow.
598
+ These cover public pause/resume, typed recovery and rejection, a fresh DBOS adapter, and the built Node CLI with a fixture provider. The CLI fixture uses an isolated in-memory backend, not the production database or a release workflow.
593
599
 
594
600
  **Historical release incident.** Run `877a91c7-ed68-4ace-9895-cf2555d9b154` (`publish-release`) stopped at `wait-required-ci`, node `tool:h09e55b6c3ff8718bdf7066c90323715f`, after `prepare-changelog-branch` and `validate-commit-push-open-pr`. Read-only investigation found completed checkpoints and the targeted-abort terminal error, but no typed unfinished-tool checkpoint for that node. The patch therefore cannot safely resume this exact retained record automatically; it does not import its chat/status snapshots or edit the database.
595
601
 
@@ -605,7 +611,7 @@ The supported explicit recovery route is operator reconciliation followed, if au
605
611
  | **Process crash** | Workflow remains `running` in durable state. Exact-id status/inspection reconstructs its retained checkpoint DAG as `crashed` once the owner heartbeat is stale and shows whether explicit resume is available. `/workflow resume <id>` is still required to claim the root and continue from the last completed checkpoint. |
606
612
  | **`ctx.tool` retry/default failure** | When `retriesAllowed: true`, the tool function is retried with exponential backoff. Cancellation is checked before each attempt, during retry backoff, and through the callback's own `signal`. Without `failureMode: "return"`, an exhausted callback error propagates and the workflow fails. |
607
613
  | **Recoverable `ctx.tool` failure** | With `failureMode: "return"`, exhausted callback failures are durably returned after retries. The tool node remains failed, downstream handoff is explicit, and replay returns the same outcome with `cached: true`. Cancellation and storage faults still throw. |
608
- | **`ctx.tool` node quit/interrupt** | `quit`/`interrupt` with a tool node id or name aborts that call's signal, marks the node `cancelled`, and leaves sibling stages and tools running. The action returns `status: "cancelled"` with the separately observed `workflowStatus`; it never reports the run as paused. No replayable `tool:` checkpoint and no `return_failure` outcome are written — return mode writes only inspection metadata — so resume re-runs exactly that call at the same ordinal and node id. |
614
+ | **`ctx.tool` node quit/pause** | `quit`/`pause` with a tool node id or name aborts that call's signal, marks the node `cancelled`, and leaves sibling stages and tools running. The action returns `status: "cancelled"` with the separately observed `workflowStatus`; it never reports the run as paused. No replayable `tool:` checkpoint and no `return_failure` outcome are written — return mode writes only inspection metadata — so resume re-runs exactly that call at the same ordinal and node id. |
609
615
  | **Run quit with in-flight tools** | Quit closes tool admission after stage pauses acknowledge, rescans every root/nested node, aborts that set, and waits a bounded interval before recording the durable paused/resumable transition, so the run is not declared quiesced while a callback still runs and no late call can slip in. A tool-only run pauses as resumable instead of reporting no controllable stages. A call attempted after the close is refused with the graceful-quit signal. Catching the cancellation in workflow code cannot turn the quit into a completed run. |
610
616
  | **Abandoned `ctx.tool` callback** | A callback that ignores its abort signal is abandoned after the bounded wait: quit proceeds, the node is published as `cancelled`, each abandoned call is reported as an owning `{runId, nodeId}` identity, and the stale background job is detached so resume relaunches a fresh executor under the same workflow id. A late return from that callback is discarded before persistence, cannot become a checkpoint, and cannot mutate or unregister the replacement run. |
611
617
  | **`ctx.ui` pending prompt** | If a UI prompt was not answered before interruption, resume leaves off on that prompt — the user must answer it to continue. |
@@ -724,7 +730,7 @@ export default workflow({
724
730
 
725
731
  `maxDurationMs` and `maxTokens` must be non-negative finite integers. `maxCost` and `warnAtPercent` must be non-negative finite numbers. Invalid config produces `CONFIG_INVALID`; invalid authored or direct-run declarations throw a `TypeError` before the workflow body runs. Nested `ctx.workflow(child)` calls use the child's own declared budget and remain subject to the root run's duration scope; a root exhaustion wins simultaneous child exhaustion, while a child-only exhaustion soft-lands that child run and returns to the parent.
726
732
 
727
- `maxDurationMs` is enforced at stage and durable-tool boundaries, and again immediately after a completed `ctx.task` persists its result checkpoint. Elapsed run time excludes paused time and resumed runs carry prior elapsed time. If that post-task boundary finds the duration ceiling already exhausted, the root becomes a resumable `budget_exceeded` block instead of remaining raw `running` with no active or control node. When every authored task in `ctx.parallel(..., { failFast: false })` reaches that boundary together after persisting a complete result, Atomic waits for the tasks to settle and deterministically uses the earliest authored exhausted task as the resume frontier rather than replacing the budget stop with a generic parallel failure. A raised-budget resume then replays those completed task results without rerunning their models and continues after the parallel barrier. If any authored parallel task was still pending at exhaustion, or any task instead has an ordinary failure, the existing aggregate-failure behavior wins. A root still awaiting that task-result persist remains quit/interrupt-controllable and is not diagnosed as a stranded root; aborting it after the persist has already started does not leave an unobserved checkpoint rejection or block durable finalization behind the original write. Catching that rejection in workflow code and returning outputs does not overwrite the requested pause. Exact-id quit or interrupt of a nested task tail quits the aggregate root so the parent snapshot and durable handle do not stay raw `running`. A later resume reuses a completed task result or, if only the terminal stage checkpoint exists, that complete `WorkflowTaskResult` including the exact persisted text (schema-backed `maxOutput` truncation included), primitive structured values such as `null`, generated worktree artifacts, warnings, session, and model metadata, without rerunning the model. Exact `workflow status <id>` uses the live tool-control registry and reports the same stranded-root diagnostic as the listing. Same-process DBOS hydration rejects unknown checkpoint history instead of skipping it. `maxTokens` counts uncached input plus output tokens across the complete run tree, including nested children and stage retries; cache reads and writes remain reported counters and `maxCost` charges the summed `usage.cost`. A `budget_warning` lifecycle notice is emitted once per run and dimension at `warnAtPercent` (default `80`); exhaustion gives an already-live frontier stage one current-turn wrap-up, then records a resumable `budget_exceeded` blocked result with its reading, ceiling, frontier, wrap-up summary, and the wrap-up turn's own `wrapUpUsage` when model usage is available. No new stage is created just to host a wrap-up; when no stage turn is live at the exhausting boundary, the run stops with no wrap-up summary and leaves the once-per-run delivery allowance unused. A resumed run carries prior duration, token, and cost spend without double-charging replayed completions; pass a raised resume budget to continue with the prior spend. Nested child budgets meter only their subtree while the root meter still includes child spend, and a child-only exhaustion returns to the parent as a blocked child result while the parent continues.
733
+ `maxDurationMs` is enforced at stage and durable-tool boundaries, and again immediately after a completed `ctx.task` persists its result checkpoint. Elapsed run time excludes paused time and resumed runs carry prior elapsed time. If that post-task boundary finds the duration ceiling already exhausted, the root becomes a resumable `budget_exceeded` block instead of remaining raw `running` with no active or control node. When every authored task in `ctx.parallel(..., { failFast: false })` reaches that boundary together after persisting a complete result, Atomic waits for the tasks to settle and deterministically uses the earliest authored exhausted task as the resume frontier rather than replacing the budget stop with a generic parallel failure. A raised-budget resume then replays those completed task results without rerunning their models and continues after the parallel barrier. If any authored parallel task was still pending at exhaustion, or any task instead has an ordinary failure, the existing aggregate-failure behavior wins. A root still awaiting that task-result persist remains quit/pause-controllable and is not diagnosed as a stranded root; aborting it after the persist has already started does not leave an unobserved checkpoint rejection or block durable finalization behind the original write. Catching that rejection in workflow code and returning outputs does not overwrite the requested pause. Exact-id quit or pause of a nested task tail suspends the aggregate root so the parent snapshot and durable handle do not stay raw `running`. A later resume reuses a completed task result or, if only the terminal stage checkpoint exists, that complete `WorkflowTaskResult` including the exact persisted text (schema-backed `maxOutput` truncation included), primitive structured values such as `null`, generated worktree artifacts, warnings, session, and model metadata, without rerunning the model. Exact `workflow status <id>` uses the live tool-control registry and reports the same stranded-root diagnostic as the listing. Same-process DBOS hydration rejects unknown checkpoint history instead of skipping it. `maxTokens` counts uncached input plus output tokens across the complete run tree, including nested children and stage retries; cache reads and writes remain reported counters and `maxCost` charges the summed `usage.cost`. A `budget_warning` lifecycle notice is emitted once per run and dimension at `warnAtPercent` (default `80`); exhaustion gives an already-live frontier stage one current-turn wrap-up, then records a resumable `budget_exceeded` blocked result with its reading, ceiling, frontier, wrap-up summary, and the wrap-up turn's own `wrapUpUsage` when model usage is available. No new stage is created just to host a wrap-up; when no stage turn is live at the exhausting boundary, the run stops with no wrap-up summary and leaves the once-per-run delivery allowance unused. A resumed run carries prior duration, token, and cost spend without double-charging replayed completions; pass a raised resume budget to continue with the prior spend. Nested child budgets meter only their subtree while the root meter still includes child spend, and a child-only exhaustion returns to the parent as a blocked child result while the parent continues.
728
734
 
729
735
  ## Workflow Configuration
730
736
 
@@ -777,7 +783,7 @@ Runtime config defaults:
777
783
  | `statusFile` | `false` | Write a derived status file; defaults under `.atomic/workflows/status.json` when enabled |
778
784
  | `resumeInFlight` | `"ask"` | Behavior when discovering resumable in-flight work |
779
785
  | `workflowNotifications.enabled` | `true` | Emit workflow lifecycle notices into the active main chat |
780
- | `workflowNotifications.notifyOn` | `["started", "completed", "failed", "blocked", "budget_warning", "awaiting_input", "paused", "quit", "resumed"]` | Lifecycle states to track; terminal `completed`/`failed`/`blocked` outcomes, active recoverable blocks, duration budget warnings, and the user-initiated `started`/`paused`/`quit`/`resumed` control actions on a top-level run create main-chat notices, while `awaiting_input` is tracked for dedupe/restore without waking the main agent |
786
+ | `workflowNotifications.notifyOn` | `["started", "completed", "failed", "blocked", "budget_warning", "awaiting_input", "paused", "quit", "resumed"]` | Lifecycle states to track; terminal `completed`/`failed`/`blocked` outcomes, active recoverable blocks, duration budget warnings, and attributed user `started`/`quit`/`resumed` actions on a top-level run create main-chat notices. `pause` does not attribute an actor; `awaiting_input` is tracked for dedupe/restore without waking the main agent. |
781
787
  | `worktree.symlinkDirectories` | `["node_modules"]` | Main-root directories symlinked into each runner-managed temporary worktree during post-creation setup |
782
788
 
783
789
  Invalid JSON or invalid shapes produce `CONFIG_INVALID` diagnostics. Missing config files are ignored.
@@ -1168,7 +1168,7 @@ Good workflows are information-flow systems, not just prompt sequences. Keep sta
1168
1168
  - Do not rely on undeclared child outputs; returning a key that is not declared in `outputs` fails the run. Declare every child-workflow field you expose in `outputs` — including `result` — and return values matching those schemas from `run` (see [Outputs](/workflows/authoring#outputs)).
1169
1169
  - Do not expect to select or rename child outputs at the call site; parent workflows receive the child's declared output contract as `child.outputs` after checking `child.exited === false`, and a partial declared-output map when `child.exited === true`.
1170
1170
  - Do not expect named workflow runs to block the chat turn; they are background tasks.
1171
- - Use `interrupt` or `pause` when the user asks to pause specific live work resumably; use `quit` for a graceful run-level process boundary.
1171
+ - Use `pause` when the user asks to pause specific live work resumably; use `quit` for a graceful run-level process boundary.
1172
1172
  - Keep stage names readable because they appear in workflow status and UI.
1173
1173
  - Do not ask a stage to reason from workflow or stage names that are only orchestration labels. Model stages see their local prompt, artifacts, tools, and reads; describe the concrete action and evidence instead of referring to an implementation-specific nickname.
1174
1174
  - Do not write stage prompts that depend on hidden workflow-wide awareness; make each model stage locally scoped and self-described ([Locally Scoped Stage Prompts](#locally-scoped-stage-prompts)).
@@ -1348,7 +1348,7 @@ A failed test becomes the next objective.
1348
1348
  Validation failed on `[command]`. Treat that as the source of truth. Fix the root cause only, rerun the failing check, then report the result.
1349
1349
  ```
1350
1350
 
1351
- #### 8. Interrupt stale or wrong work
1351
+ #### 8. Pause stale or wrong work
1352
1352
 
1353
1353
  If a run is solving the wrong problem, based on outdated assumptions, or duplicating another run, stop it. Continuing usually creates more cleanup.
1354
1354
 
@@ -2093,7 +2093,7 @@ These anti-patterns target run prompts; [Common Mistakes](#common-mistakes) cove
2093
2093
  | `Fix this.` | `Fix [specific failure]; done means [test command] passes.` |
2094
2094
  | No validation step | Require tests, smoke checks, typecheck, or explicit manual verification. |
2095
2095
  | Broad refactors | Constrain the run to the files needed for the objective. |
2096
- | Letting a wrong stage continue | Redirect or interrupt as soon as the agent follows the wrong signal. |
2096
+ | Letting a wrong stage continue | Redirect or pause as soon as the agent follows the wrong signal. |
2097
2097
  | Accepting unverified summaries | Ask for changed files, commands run, results, and remaining risks. |
2098
2098
  | Mixing investigation and implementation too early | Ask for root cause and proposed fix before code changes. |
2099
2099
  | Ignoring blocked stages | Answer directly with one decision and any constraints. |