@bastani/atomic 0.8.21 → 0.8.22-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/CHANGELOG.md +40 -9
  2. package/dist/builtin/intercom/broker/broker.ts +3 -3
  3. package/dist/builtin/intercom/config.ts +3 -3
  4. package/dist/builtin/intercom/index.ts +1 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/intercom/ui/compose.ts +2 -2
  7. package/dist/builtin/mcp/host-html-template.ts +0 -3
  8. package/dist/builtin/mcp/package.json +1 -1
  9. package/dist/builtin/subagents/CHANGELOG.md +13 -4
  10. package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -9
  11. package/dist/builtin/subagents/agents/debugger.md +6 -6
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +1 -1
  14. package/dist/builtin/subagents/skills/browser-use/SKILL.md +234 -0
  15. package/dist/builtin/subagents/skills/browser-use/references/cdp-python.md +76 -0
  16. package/dist/builtin/subagents/skills/browser-use/references/multi-session.md +92 -0
  17. package/dist/builtin/subagents/skills/subagent/SKILL.md +4 -4
  18. package/dist/builtin/subagents/src/agents/skills.ts +19 -1
  19. package/dist/builtin/subagents/src/extension/index.ts +24 -22
  20. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +7 -1
  21. package/dist/builtin/subagents/src/runs/background/async-execution.ts +23 -7
  22. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +98 -3
  23. package/dist/builtin/subagents/src/runs/background/async-status.ts +3 -1
  24. package/dist/builtin/subagents/src/runs/background/run-status.ts +1 -1
  25. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +3 -0
  26. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +37 -12
  27. package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +15 -15
  28. package/dist/builtin/subagents/src/runs/foreground/execution.ts +26 -2
  29. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +1 -1
  30. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +7 -0
  31. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +28 -1
  32. package/dist/builtin/subagents/src/shared/fast-mode.ts +80 -0
  33. package/dist/builtin/subagents/src/shared/formatters.ts +4 -2
  34. package/dist/builtin/subagents/src/shared/types.ts +4 -2
  35. package/dist/builtin/subagents/src/shared/utils.ts +3 -61
  36. package/dist/builtin/subagents/src/tui/render.ts +303 -157
  37. package/dist/builtin/web-access/package.json +1 -1
  38. package/dist/builtin/workflows/CHANGELOG.md +95 -35
  39. package/dist/builtin/workflows/README.md +228 -41
  40. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +535 -541
  41. package/dist/builtin/workflows/builtin/goal.ts +39 -25
  42. package/dist/builtin/workflows/builtin/open-claude-design.ts +66 -69
  43. package/dist/builtin/workflows/builtin/ralph.ts +21 -21
  44. package/dist/builtin/workflows/package.json +6 -5
  45. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
  46. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +2 -2
  47. package/dist/builtin/workflows/src/extension/discovery.ts +25 -146
  48. package/dist/builtin/workflows/src/extension/dispatcher.ts +72 -24
  49. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +363 -0
  50. package/dist/builtin/workflows/src/extension/index.ts +690 -352
  51. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +99 -62
  52. package/dist/builtin/workflows/src/extension/render-call.ts +2 -1
  53. package/dist/builtin/workflows/src/extension/render-result.ts +9 -3
  54. package/dist/builtin/workflows/src/extension/renderers.ts +5 -3
  55. package/dist/builtin/workflows/src/extension/runtime.ts +68 -33
  56. package/dist/builtin/workflows/src/extension/status-writer.ts +1 -1
  57. package/dist/builtin/workflows/src/extension/wiring.ts +34 -13
  58. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +142 -0
  59. package/dist/builtin/workflows/src/extension/workflow-schema.ts +4 -4
  60. package/dist/builtin/workflows/src/index.ts +2 -0
  61. package/dist/builtin/workflows/src/intercom/result-intercom.ts +1 -1
  62. package/dist/builtin/workflows/src/runs/background/runner.ts +6 -4
  63. package/dist/builtin/workflows/src/runs/background/status.ts +45 -21
  64. package/dist/builtin/workflows/src/runs/foreground/executor.ts +624 -52
  65. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +1 -1
  66. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +80 -24
  67. package/dist/builtin/workflows/src/runs/shared/validate-inputs.ts +61 -24
  68. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +32 -10
  69. package/dist/builtin/workflows/src/sdk-surface.ts +6 -0
  70. package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +178 -0
  71. package/dist/builtin/workflows/src/shared/persistence-restore.ts +92 -12
  72. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +21 -3
  73. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +1 -2
  74. package/dist/builtin/workflows/src/shared/run-visibility.ts +9 -0
  75. package/dist/builtin/workflows/src/shared/schema-introspection.ts +121 -0
  76. package/dist/builtin/workflows/src/shared/serializable.ts +132 -0
  77. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +91 -9
  78. package/dist/builtin/workflows/src/shared/store-types.ts +31 -3
  79. package/dist/builtin/workflows/src/shared/store.ts +58 -14
  80. package/dist/builtin/workflows/src/shared/types.ts +105 -40
  81. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +129 -13
  82. package/dist/builtin/workflows/src/tui/chat-surface.ts +6 -1
  83. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +3 -2
  84. package/dist/builtin/workflows/src/tui/graph-canvas.ts +1 -1
  85. package/dist/builtin/workflows/src/tui/graph-view.ts +91 -65
  86. package/dist/builtin/workflows/src/tui/inline-form-card.ts +1 -1
  87. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +3 -2
  88. package/dist/builtin/workflows/src/tui/inputs-overlay.ts +3 -2
  89. package/dist/builtin/workflows/src/tui/inputs-picker.ts +8 -7
  90. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +2 -0
  91. package/dist/builtin/workflows/src/tui/node-card.ts +34 -8
  92. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -11
  93. package/dist/builtin/workflows/src/tui/prompt-card.ts +98 -50
  94. package/dist/builtin/workflows/src/tui/session-list.ts +7 -2
  95. package/dist/builtin/workflows/src/tui/session-picker.ts +2 -0
  96. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +226 -55
  97. package/dist/builtin/workflows/src/tui/status-helpers.ts +2 -0
  98. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +37 -158
  99. package/dist/builtin/workflows/src/tui/toast.ts +2 -2
  100. package/dist/builtin/workflows/src/tui/widget.ts +53 -12
  101. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +270 -19
  102. package/dist/builtin/workflows/src/tui/workflow-notice-card.ts +184 -0
  103. package/dist/builtin/workflows/src/workflows/define-workflow.ts +138 -43
  104. package/dist/config.d.ts +9 -0
  105. package/dist/config.d.ts.map +1 -1
  106. package/dist/config.js +45 -0
  107. package/dist/config.js.map +1 -1
  108. package/dist/core/agent-session.d.ts +27 -9
  109. package/dist/core/agent-session.d.ts.map +1 -1
  110. package/dist/core/agent-session.js +196 -17
  111. package/dist/core/agent-session.js.map +1 -1
  112. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  113. package/dist/core/atomic-guide-command.js +2 -2
  114. package/dist/core/atomic-guide-command.js.map +1 -1
  115. package/dist/core/codex-fast-mode.d.ts +36 -0
  116. package/dist/core/codex-fast-mode.d.ts.map +1 -0
  117. package/dist/core/codex-fast-mode.js +117 -0
  118. package/dist/core/codex-fast-mode.js.map +1 -0
  119. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  120. package/dist/core/compaction/branch-summarization.js +1 -1
  121. package/dist/core/compaction/branch-summarization.js.map +1 -1
  122. package/dist/core/compaction/compaction.d.ts.map +1 -1
  123. package/dist/core/compaction/compaction.js +1 -1
  124. package/dist/core/compaction/compaction.js.map +1 -1
  125. package/dist/core/extensions/index.d.ts +4 -1
  126. package/dist/core/extensions/index.d.ts.map +1 -1
  127. package/dist/core/extensions/index.js +1 -0
  128. package/dist/core/extensions/index.js.map +1 -1
  129. package/dist/core/extensions/loader.d.ts +7 -2
  130. package/dist/core/extensions/loader.d.ts.map +1 -1
  131. package/dist/core/extensions/loader.js +23 -8
  132. package/dist/core/extensions/loader.js.map +1 -1
  133. package/dist/core/extensions/reactive-widget.d.ts +58 -0
  134. package/dist/core/extensions/reactive-widget.d.ts.map +1 -0
  135. package/dist/core/extensions/reactive-widget.js +182 -0
  136. package/dist/core/extensions/reactive-widget.js.map +1 -0
  137. package/dist/core/extensions/runner.d.ts.map +1 -1
  138. package/dist/core/extensions/runner.js +1 -0
  139. package/dist/core/extensions/runner.js.map +1 -1
  140. package/dist/core/extensions/types.d.ts +26 -12
  141. package/dist/core/extensions/types.d.ts.map +1 -1
  142. package/dist/core/extensions/types.js.map +1 -1
  143. package/dist/core/messages.d.ts +1 -1
  144. package/dist/core/messages.d.ts.map +1 -1
  145. package/dist/core/messages.js +8 -2
  146. package/dist/core/messages.js.map +1 -1
  147. package/dist/core/model-registry.d.ts +4 -0
  148. package/dist/core/model-registry.d.ts.map +1 -1
  149. package/dist/core/model-registry.js +11 -0
  150. package/dist/core/model-registry.js.map +1 -1
  151. package/dist/core/resource-loader.d.ts +9 -1
  152. package/dist/core/resource-loader.d.ts.map +1 -1
  153. package/dist/core/resource-loader.js +49 -21
  154. package/dist/core/resource-loader.js.map +1 -1
  155. package/dist/core/sdk.d.ts.map +1 -1
  156. package/dist/core/sdk.js +22 -13
  157. package/dist/core/sdk.js.map +1 -1
  158. package/dist/core/session-manager.d.ts +7 -5
  159. package/dist/core/session-manager.d.ts.map +1 -1
  160. package/dist/core/session-manager.js +5 -3
  161. package/dist/core/session-manager.js.map +1 -1
  162. package/dist/core/settings-manager.d.ts +16 -0
  163. package/dist/core/settings-manager.d.ts.map +1 -1
  164. package/dist/core/settings-manager.js +64 -5
  165. package/dist/core/settings-manager.js.map +1 -1
  166. package/dist/core/slash-commands.d.ts.map +1 -1
  167. package/dist/core/slash-commands.js +1 -0
  168. package/dist/core/slash-commands.js.map +1 -1
  169. package/dist/core/system-prompt.d.ts.map +1 -1
  170. package/dist/core/system-prompt.js +7 -4
  171. package/dist/core/system-prompt.js.map +1 -1
  172. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  173. package/dist/core/tools/ask-user-question/ask-user-question.js +2 -2
  174. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  175. package/dist/index.d.ts +4 -3
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +3 -2
  178. package/dist/index.js.map +1 -1
  179. package/dist/main.d.ts +3 -0
  180. package/dist/main.d.ts.map +1 -1
  181. package/dist/main.js +12 -0
  182. package/dist/main.js.map +1 -1
  183. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
  184. package/dist/modes/interactive/chat-input-actions.js.map +1 -1
  185. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  186. package/dist/modes/interactive/components/diff.js +0 -1
  187. package/dist/modes/interactive/components/diff.js.map +1 -1
  188. package/dist/modes/interactive/components/fast-mode-selector.d.ts +27 -0
  189. package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +1 -0
  190. package/dist/modes/interactive/components/fast-mode-selector.js +105 -0
  191. package/dist/modes/interactive/components/fast-mode-selector.js.map +1 -0
  192. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  193. package/dist/modes/interactive/components/footer.js +7 -12
  194. package/dist/modes/interactive/components/footer.js.map +1 -1
  195. package/dist/modes/interactive/components/index.d.ts +1 -0
  196. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  197. package/dist/modes/interactive/components/index.js +1 -0
  198. package/dist/modes/interactive/components/index.js.map +1 -1
  199. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  200. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  201. package/dist/modes/interactive/interactive-mode.js +132 -30
  202. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  203. package/dist/modes/print-mode.d.ts.map +1 -1
  204. package/dist/modes/print-mode.js +53 -6
  205. package/dist/modes/print-mode.js.map +1 -1
  206. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  207. package/dist/modes/rpc/rpc-mode.js +3 -0
  208. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  209. package/docs/compaction.md +1 -1
  210. package/docs/custom-provider.md +2 -2
  211. package/docs/development.md +2 -2
  212. package/docs/docs.json +2 -2
  213. package/docs/extensions.md +18 -13
  214. package/docs/providers.md +5 -1
  215. package/docs/quickstart.md +5 -3
  216. package/docs/rpc.md +5 -5
  217. package/docs/sdk.md +12 -12
  218. package/docs/settings.md +18 -0
  219. package/docs/themes.md +6 -6
  220. package/docs/tui.md +20 -18
  221. package/docs/usage.md +2 -0
  222. package/docs/workflows.md +403 -39
  223. package/examples/extensions/qna.ts +2 -2
  224. package/package.json +4 -4
  225. package/dist/builtin/subagents/skills/playwright-cli/SKILL.md +0 -392
  226. package/dist/builtin/subagents/skills/playwright-cli/references/element-attributes.md +0 -23
  227. package/dist/builtin/subagents/skills/playwright-cli/references/playwright-tests.md +0 -39
  228. package/dist/builtin/subagents/skills/playwright-cli/references/request-mocking.md +0 -87
  229. package/dist/builtin/subagents/skills/playwright-cli/references/running-code.md +0 -241
  230. package/dist/builtin/subagents/skills/playwright-cli/references/session-management.md +0 -225
  231. package/dist/builtin/subagents/skills/playwright-cli/references/spec-driven-testing.md +0 -305
  232. package/dist/builtin/subagents/skills/playwright-cli/references/storage-state.md +0 -275
  233. package/dist/builtin/subagents/skills/playwright-cli/references/test-generation.md +0 -134
  234. package/dist/builtin/subagents/skills/playwright-cli/references/tracing.md +0 -139
  235. package/dist/builtin/subagents/skills/playwright-cli/references/video-recording.md +0 -143
package/docs/workflows.md CHANGED
@@ -38,7 +38,9 @@ Use a workflow when a task should be repeatable, inspectable, resumable, or spli
38
38
  - [Running Workflows](#running-workflows)
39
39
  - [Workflow Commands](#workflow-commands)
40
40
  - [Monitor and Control Runs](#monitor-and-control-runs)
41
+ - [Lifecycle Notices and Human Input](#lifecycle-notices-and-human-input)
41
42
  - [Direct One-Off Runs](#direct-one-off-runs)
43
+ - [Codex Fast Mode for Workflow Stages](#codex-fast-mode-for-workflow-stages)
42
44
  - [Writing a Workflow](#writing-a-workflow)
43
45
  - [Workflow Primitives](#workflow-primitives)
44
46
  - [Task and Stage Options](#task-and-stage-options)
@@ -85,7 +87,9 @@ Atomic will:
85
87
  - ask clarifying questions when stage purpose, inputs, models, or handoffs are ambiguous,
86
88
  - write a `.atomic/workflows/<name>.ts` file using `defineWorkflow(...).input(...).run(...).compile()`,
87
89
  - pick `ctx.task` / `ctx.chain` / `ctx.parallel` / `ctx.ui` per the [primitives](#workflow-primitives) and [task options](#task-and-stage-options) reference, and
88
- - reload discovery so you can run it immediately.
90
+ - run `/workflow reload` so Atomic rediscovers the workflow resource and you can launch it immediately.
91
+
92
+ Atomic does not use the long-running `/goal` workflow by default for first-time workflow creation. If you explicitly choose `/goal` for reviewer-gated implementation, keep the objective tightly scoped with concrete done criteria and validation steps, and monitor the run with workflow status/connect controls rather than manual sleep-and-poll loops.
89
93
 
90
94
  The same plain-chat approach works for editing or hardening an existing workflow — ask Atomic to add a stage, switch a model, save artifacts, or wire in a human approval gate.
91
95
 
@@ -104,15 +108,17 @@ Named workflow runs are background-oriented. After launch, expect a run id and m
104
108
  Workflow files are plain TypeScript modules. Create `.atomic/workflows/explain-file.ts`:
105
109
 
106
110
  ```ts
107
- import { defineWorkflow } from "@bastani/workflows";
111
+ import { defineWorkflow, Type } from "@bastani/workflows";
108
112
 
109
113
  export default defineWorkflow("explain-file")
110
114
  .description("Explain a file with tracked workflow stages.")
111
- .input("path", {
112
- type: "text",
113
- required: true,
114
- description: "File path to explain.",
115
- })
115
+ .input("path", Type.String({ description: "File path to explain." }))
116
+ .output(
117
+ "explanation",
118
+ Type.String({
119
+ description: "Explanation of the file's purpose, risks, and key symbols.",
120
+ }),
121
+ )
116
122
  .run(async (ctx) => {
117
123
  const explanation = await ctx.task("explain", {
118
124
  prompt: `Read ${String(ctx.inputs.path)} and explain purpose, risks, and key symbols.`,
@@ -124,7 +130,7 @@ export default defineWorkflow("explain-file")
124
130
  .compile();
125
131
  ```
126
132
 
127
- Restart Atomic or run `/reload`, then list and run it:
133
+ Run `/workflow reload` or restart Atomic, then list and run it:
128
134
 
129
135
  ```text
130
136
  /workflow list
@@ -138,12 +144,16 @@ See [Writing a Workflow](#writing-a-workflow) for the full builder API and [Work
138
144
 
139
145
  Atomic bundles four workflows that cover the most common multi-stage jobs. They are available in every session — no install step required. Use `/workflow list` to confirm they are loaded, and `/workflow inputs <name>` to see the exact inputs in your environment.
140
146
 
147
+ These same builtin workflows are also available to workflow authors as compiled definitions. Import them from `@bastani/workflows/builtin` and pass the definition directly to `ctx.workflow(...)` when one workflow should call `deep-research-codebase`, `goal`, `ralph`, `open-claude-design`, or another builtin as a nested child workflow. See [Workflow Composition](#workflow-composition) for full examples alongside user-defined child workflows.
148
+
149
+ For the builtin result tables below, `deep-research-codebase`, `goal`, and `ralph` explicitly declare `.output("result", Type.String(...))` and return a `result` key from `.run()`, so `result` is part of their declared output contract. Every output a workflow exposes — including `result` — must be both declared with `.output(...)` and returned from `.run()`; Atomic no longer adds any automatic `result` output.
150
+
141
151
  | Workflow | What it does | When to use |
142
152
  |---|---|---|
143
153
  | `deep-research-codebase` | Scout + research-history chain → parallel specialist waves → aggregator. Indexes the whole repo and synthesizes findings. | Broad or cross-cutting research before you decide what to change. Prefer `/skill:research-codebase` for one subsystem. |
144
154
  | `goal` | Persisted goal ledger → bounded worker turns → receipts → three-reviewer gate → deterministic reducer → final report. | Small-to-medium scope changes when you can identify the work surface, state the exact outcome, and name the validation that proves it is done — for example tests, lint/typecheck, docs builds, or observable behavior. |
145
155
  | `ralph` | RFC planning → sub-agent orchestration → simplification → infrastructure discovery → parallel review → PR handoff. | Larger migrations, broad refactors, multi-package changes, and spec-to-PR work where you want Atomic to plan the approach, delegate implementation through sub-agents, simplify, review, iterate, and prepare a pull-request report. |
146
- | `open-claude-design` | Design-system onboarding → reference import → HTML generation → impeccable-driven refinement → quality gate → rich HTML handoff. Renders a live `preview.html` you can iterate against (opens through `playwright-cli` when available). | UI, page, component, theme, or design-token work that benefits from generation + critique loops. |
156
+ | `open-claude-design` | Design-system onboarding → reference import → HTML generation → impeccable-driven refinement → quality gate → rich HTML handoff. Renders a live `preview.html` you can iterate against (opens through `browser-use` when available). | UI, page, component, theme, or design-token work that benefits from generation + critique loops. |
147
157
 
148
158
  ### `deep-research-codebase`
149
159
 
@@ -153,7 +163,7 @@ Inputs:
153
163
  |---|---|---|---|---|
154
164
  | `prompt` | text | yes | — | Research question or investigation focus. |
155
165
  | `max_partitions` | number | no | `100` | Maximum codebase partitions explored in parallel. Actual partitions scale by one per 10K LoC, capped by this value. |
156
- | `max_concurrency` | number | no | `4` | Maximum workflow stages running concurrently during deep research. |
166
+ | `max_concurrency` | number | no | `100` | Maximum workflow stages running concurrently during deep research. |
157
167
 
158
168
  Run examples:
159
169
 
@@ -176,6 +186,7 @@ Output locations and result fields:
176
186
 
177
187
  | Field | Meaning |
178
188
  |---|---|
189
+ | `result` | Final Markdown research report text, matching `findings`. |
179
190
  | `findings` | Final Markdown research report text. |
180
191
  | `research_doc_path` | Public report path under `research/<date>-<topic>.md`. If a file already exists, the workflow writes a suffixed filename. |
181
192
  | `artifact_dir` | Hidden per-run handoff directory under `research/.deep-research-<run-id>/`. |
@@ -280,6 +291,26 @@ Inputs:
280
291
  | `design_system` | text | no | — | Path(s) or description of an existing design system (e.g. `DESIGN.md`, `PRODUCT.md`). Skips onboarding when provided. |
281
292
  | `max_refinements` | number | no | `3` | Maximum critique/apply refinement iterations. |
282
293
 
294
+ Result fields:
295
+
296
+ | Field | Meaning |
297
+ |---|---|
298
+ | `output_type` | Kind of design artifact produced. |
299
+ | `design_system` | Design system source used for generation: supplied input or project-derived design system. |
300
+ | `artifact` | Latest final design summary from the approved preview artifact. |
301
+ | `handoff` | Final rich HTML spec and implementation handoff summary. |
302
+ | `approved_for_export` | Whether refinement completed before the final export gate. |
303
+ | `refinements_completed` | Number of refinement iterations completed. |
304
+ | `import_context` | Reference-import context used during generation. |
305
+ | `run_id` | Per-run design workflow artifact identifier. |
306
+ | `artifact_dir` | Directory containing preview and spec artifacts. |
307
+ | `preview_path` | Absolute path to the generated `preview.html` file. |
308
+ | `preview_file_url` | `file://` URL for the generated `preview.html` file. |
309
+ | `spec_path` | Absolute path to the generated `spec.html` file. |
310
+ | `spec_file_url` | `file://` URL for the generated `spec.html` file. |
311
+
312
+ `open-claude-design` has no `result` output; it exposes only the declared fields listed above. Use the declared `artifact` and `handoff` fields for generated content.
313
+
283
314
  Run examples:
284
315
 
285
316
  ```text
@@ -411,7 +442,11 @@ Example config:
411
442
  "maxDepth": 4,
412
443
  "persistRuns": true,
413
444
  "statusFile": false,
414
- "resumeInFlight": "ask"
445
+ "resumeInFlight": "ask",
446
+ "workflowNotifications": {
447
+ "enabled": true,
448
+ "notifyOn": ["completed", "failed", "awaiting_input"]
449
+ }
415
450
  }
416
451
  ```
417
452
 
@@ -424,6 +459,8 @@ Runtime config defaults:
424
459
  | `persistRuns` | `true` | Persist run metadata for status/resume/history |
425
460
  | `statusFile` | `false` | Write a derived status file; defaults under `.atomic/workflows/status.json` when enabled |
426
461
  | `resumeInFlight` | `"ask"` | Behavior when discovering resumable in-flight work |
462
+ | `workflowNotifications.enabled` | `true` | Emit terminal workflow lifecycle notices into the active main chat |
463
+ | `workflowNotifications.notifyOn` | `["completed", "failed", "awaiting_input"]` | Lifecycle states to track; terminal `completed`/`failed` states create main-chat notices, while `awaiting_input` is tracked for dedupe/restore without waking the main agent |
427
464
 
428
465
  Invalid JSON or invalid shapes produce `CONFIG_INVALID` diagnostics. Missing config files are ignored.
429
466
 
@@ -522,6 +559,14 @@ workflow({ action: "get", workflow: "deep-research-codebase" })
522
559
  workflow({ action: "inputs", workflow: "deep-research-codebase" })
523
560
  ```
524
561
 
562
+ The workflow tool action surface is:
563
+
564
+ - discovery: `list`, `get`, `inputs`
565
+ - execution: named `run`, plus direct one-off `task`, `tasks`, and `chain` modes
566
+ - inspection: `status`, `stages`, `stage`, `transcript`
567
+ - messaging and run control: `send`, `pause`, `interrupt`, `kill`, `resume`
568
+ - rediscovery: `reload`
569
+
525
570
  Run a named workflow with inputs:
526
571
 
527
572
  ```ts
@@ -540,10 +585,12 @@ Slash equivalent:
540
585
 
541
586
  <p align="center"><img src="images/workflow-command.png" alt="Running a Workflow Command" width="600" /></p>
542
587
 
543
- Input overrides are bare `key=value` tokens. Values are JSON-parsed when possible, so `count=3`, `flag=true`, and `prompt="multi word value"` preserve useful types. A whole input object can also be passed as one JSON token.
588
+ Input overrides are bare `key=value` tokens. Values are JSON-parsed when possible, so `count=3`, `flag=true`, and `prompt="multi word value"` preserve useful types. A whole input object can also be passed as one JSON token. Runtime validation is strict: unknown input keys, missing required values, type mismatches, and invalid `select` choices fail before a named workflow run starts or before a child workflow starts.
544
589
 
545
590
  In the TUI, `/workflow <name>` opens an input picker when the workflow declares inputs and either no arguments were supplied or required inputs are missing. Supplied values seed the picker. Pass `--no-picker` to skip that interactive flow.
546
591
 
592
+ In non-interactive (`-p`, `--print`, or `--mode json`) sessions, named workflow dispatch waits for the terminal run snapshot and skips pickers. Because human input is runtime-only and workflows no longer carry a declaration-time HIL marker, headless dispatch does not reject a workflow just because its source contains `ctx.ui.*`. If you copy a HIL workflow example into a headless session, it can pass dispatch and then fail when execution reaches the prompt with an error such as `atomic-workflows: HIL ctx.ui.confirm is unavailable because Atomic runtime did not provide a UI adapter` (the primitive name varies). Run those workflows interactively, or guard/remove runtime `ctx.ui.*` calls before using headless mode.
593
+
547
594
  <p align="center"><img src="images/workflow-input-picker.png" alt="Workflow Input Picker" width="600" /></p>
548
595
 
549
596
  ## Workflow Commands
@@ -564,11 +611,11 @@ In the TUI, `/workflow <name>` opens an input picker when the workflow declares
564
611
  /workflow reload
565
612
  ```
566
613
 
567
- Use `connect` for the workflow graph. Use `attach` when you want a chat pane for a specific stage. Use `interrupt`, `pause`, and `resume` for resumable live work; `resume` on a non-paused run reopens the saved snapshot or overlay. Use `kill` only when the run should be terminated; killed runs are retained in live history/status for read-only inspection. Use `/workflow reload` after adding, editing, installing, or removing workflow resources and you want Atomic to rediscover them in-process. `/workflow status` lists all retained active and terminal runs by default; `/workflow status --all` is retained as a compatibility alias.
614
+ Use `connect` for the workflow graph. Use `attach` when you want a chat pane for a specific stage. Use `interrupt`, `pause`, and `resume` for resumable live work; `resume` on a non-paused run reopens the saved snapshot or overlay. Use `kill` only when the run should be terminated; killed runs are retained in live history/status for read-only inspection. 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. `/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.
568
615
 
569
616
  <p align="center"><img src="images/workflow-graph.png" alt="Workflow Graph Viewer" width="600" /></p>
570
617
 
571
- Human-in-the-loop prompts from `ctx.ui.input`, `ctx.ui.confirm`, `ctx.ui.select`, and `ctx.ui.editor` appear as awaiting-input nodes in the workflow UI/graph viewer, not as ordinary chat modals.
618
+ Human-in-the-loop prompts from `ctx.ui.input`, `ctx.ui.confirm`, `ctx.ui.select`, and `ctx.ui.editor` appear as awaiting-input nodes in the workflow UI/graph viewer, not as ordinary chat modals. Workflows do not declare HIL up front; prompt nodes are created when the runtime `ctx.ui.*` call executes. If the prompt lives inside an imported child workflow, it still appears in the same expanded parent graph so the user can focus and answer it without switching to a separate child status entry.
572
619
 
573
620
  ## Monitor and Control Runs
574
621
 
@@ -580,11 +627,15 @@ workflow({ action: "status", runId: "<id-or-prefix>" })
580
627
 
581
628
  workflow({ action: "stages", runId: "<id-or-prefix>", statusFilter: "all" })
582
629
  workflow({ action: "stage", runId: "<id-or-prefix>", stageId: "review" })
630
+ // Prefer sessionFile/transcriptPath from stages/stage; quote the exact path, preserve Windows separators, then search/read small ranges.
631
+ workflow({ action: "transcript", runId: "<id-or-prefix>", stageId: "review" })
632
+ // Omit tail/limit for the default 5-entry preview; pass them for quick recent-context checks.
583
633
  workflow({ action: "transcript", runId: "<id-or-prefix>", stageId: "review", tail: 40 })
584
- workflow({ action: "transcript", runId: "<id-or-prefix>", stageId: "review", includeToolOutput: true })
634
+ workflow({ action: "transcript", runId: "<id-or-prefix>", stageId: "review", limit: 20, includeToolOutput: true })
585
635
 
586
636
  workflow({ action: "send", runId: "<id-or-prefix>", stageId: "review", text: "please focus on tests" })
587
- workflow({ action: "send", runId: "<id-or-prefix>", stageId: "approval", response: true, delivery: "answer" })
637
+ workflow({ action: "send", runId: "<id-or-prefix>", stageId: "approval", promptId: "prompt-1", response: true, delivery: "answer" })
638
+ workflow({ action: "send", runId: "<id-or-prefix>", stageId: "review", message: "continue with tests", delivery: "resume" })
588
639
 
589
640
  workflow({ action: "pause", runId: "<id-or-prefix>" })
590
641
  workflow({ action: "pause", runId: "<id-or-prefix>", stageId: "review" })
@@ -603,12 +654,13 @@ workflow({ action: "reload", reason: "added team workflow" })
603
654
 
604
655
  Control behavior:
605
656
 
606
- - `runId` accepts full run ids or unique prefixes for lifecycle and inspection actions.
607
- - `stages` lists stage summaries. Use `statusFilter: "all"` to include completed, failed, skipped, and pending stages.
608
- - `stage` returns details for one stage by stage id, unique prefix, or stage name.
609
- - `transcript` reads recent messages for a stage. `tail` overrides `limit`; `includeToolOutput` includes captured snapshot tool output when available.
610
- - `send` can answer pending prompts, steer streaming stages, queue follow-ups, or resume paused work. `delivery: "auto"` chooses in that order; use `delivery: "answer"` with `promptId` or `response` for explicit prompt answers.
611
- - `pause`, `interrupt`, and `kill` can target one run or `all: true`; `stageId` cannot be combined with `all: true`.
657
+ - `runId` accepts full run ids or unique prefixes for lifecycle and inspection actions. Status lists and run pickers show top-level user-launched workflows; nested child runs are implementation details of the expanded parent graph.
658
+ - `stages` lists stage summaries, including flattened stages from nested `ctx.workflow(...)` imports and `sessionFile`/`transcriptPath` when a stage has a persisted session. Use `statusFilter: "all"` to include completed, failed, skipped, and pending stages.
659
+ - `stage` returns details for one stage by stage id, unique prefix, or stage name, including nested child stages shown in the expanded graph and the persisted `sessionFile` when available.
660
+ - `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.
661
+ - `send` delivery modes are `auto`, `answer`, `prompt`, `steer`, `followUp`, and `resume`. Prompt answers can include `promptId` and can carry answer content in `response`, `text`, or `message`; structured UI prompts usually prefer `response`.
662
+ - `delivery: "auto"` first answers a pending prompt, then resumes paused work, then steers a streaming stage, then queues a follow-up.
663
+ - `pause`, `interrupt`, and `kill` can target one top-level run or `all: true`; `stageId` cannot be combined with `all: true`. Stage-scoped controls can target a visible nested child stage from the expanded graph; Atomic routes the operation to the owning nested run internally.
612
664
  - `interrupt` is resumable: it pauses live work when pausable stages exist and keeps the run in live history/status.
613
665
  - `pause` is useful for pausing a live run or a single live stage without treating it as a destructive abort.
614
666
  - `resume` can target a stage with `stageId`; the target may be a stage id, unique prefix, or stage name. `message` is forwarded to paused work.
@@ -617,6 +669,23 @@ Control behavior:
617
669
 
618
670
  Use slash commands for graph connect and stage attach because those are interactive TUI surfaces. When a run needs user input or attention, surface that to the user instead of polling silently.
619
671
 
672
+ ## Lifecycle Notices and Human Input
673
+
674
+ Atomic emits deduplicated main-chat notices when top-level workflow runs complete or fail. Nested child workflow completion/failure is reflected inside the expanded parent graph instead of producing separate top-level completion cards. These terminal notices are queued into the active main chat as steering/context messages (`triggerTurn: true`, `deliverAs: "steer"`) so the model can react without the user manually polling status. Awaiting-input workflow states are tracked for dedupe/restore, but they do not enqueue main-chat connect cards or wake the model; prompt state remains visible through workflow status/connect surfaces. Configure lifecycle behavior with `workflowNotifications.enabled` (default `true`) and `workflowNotifications.notifyOn` (default `["completed", "failed", "awaiting_input"]`).
675
+
676
+ Human input is runtime-only: call `ctx.ui.input`, `ctx.ui.confirm`, `ctx.ui.select`, or `ctx.ui.editor` at the point where the workflow actually needs a decision. No builder-level declaration is required or supported.
677
+
678
+ When a workflow needs human input, answer in the graph viewer or attached stage chat when possible:
679
+
680
+ ```text
681
+ /workflow connect <run-id>
682
+ /workflow attach <run-id> <stage-id-or-name>
683
+ ```
684
+
685
+ Agents can answer pending prompts programmatically with `workflow({ action: "send", delivery: "answer", ... })`; use `promptId` when it is present in the stage details, and provide answer content with `response`, `text`, or `message`.
686
+
687
+ If the user answers a human-in-the-loop prompt in the workflow UI or stage UI broker, the stage receives the answer directly and the active main chat receives a display-only notice (`triggerTurn: false`, `excludeFromContext: true`) containing a concise answer summary. The notice is rendered for the user and persisted for audit, but it does not wake the model, enter LLM context, or authorize answering any other workflow prompt. Prompt answers sent by the main-chat `workflow` tool are suppressed from this notice because the tool result already informs the current turn.
688
+
620
689
  ## Direct One-Off Runs
621
690
 
622
691
  Use direct workflow-native orchestration for one-off tracked work that does not need a reusable workflow file.
@@ -688,20 +757,26 @@ Direct mode supports top-level/default options and per-task options such as `con
688
757
 
689
758
  For large fan-outs, prefer `outputMode: "file-only"` so the parent result contains compact file references instead of full output. Treat intercom payloads from async direct runs as user-visible workflow output.
690
759
 
760
+ ## Codex Fast Mode for Workflow Stages
761
+
762
+ Use `/fast` to manage Codex fast mode separately for normal chat and workflow-stage sessions. The settings are `codexFastMode.chat` and `codexFastMode.workflow`; workflow stages use the workflow scope, not the chat scope.
763
+
764
+ Fast mode is eligible only for supported `openai/*` and `openai-codex/*` providers. It does not apply to `github-copilot/*`, Azure OpenAI, OpenRouter, or custom OpenAI-compatible providers. When applied, workflow stage displays keep the raw model id and expose `fast` as a separate marker/stage metadata indicator.
765
+
766
+ Enable workflow fast mode deliberately for broad workflows: parallel fan-out and fallback attempts can multiply priority-tier requests and cost.
767
+
691
768
  ## Writing a Workflow
692
769
 
693
770
  Workflow files are TypeScript modules that export a compiled definition:
694
771
 
695
772
  ```ts
696
- import { defineWorkflow } from "@bastani/workflows";
773
+ import { defineWorkflow, Type } from "@bastani/workflows";
697
774
 
698
775
  export default defineWorkflow("my-workflow")
699
776
  .description("Short description shown in workflow listings.")
700
- .input("prompt", {
701
- type: "text",
702
- required: true,
703
- description: "Task or question for the workflow.",
704
- })
777
+ .input("prompt", Type.String({ description: "Task or question for the workflow." }))
778
+ .output("summary", Type.String({ description: "Synthesized findings and recommended next steps." }))
779
+ .output("reviewer_count", Type.Number({ description: "Number of parallel reviewers that ran." }))
705
780
  .run(async (ctx) => {
706
781
  const prompt = String(ctx.inputs.prompt);
707
782
 
@@ -735,23 +810,305 @@ Builder basics:
735
810
  - `.description(text)` sets the listing text.
736
811
  - `.input(key, schema)` declares typed user inputs.
737
812
  - `.worktreeFromInputs({ gitWorktreeDir, baseBranch })` optionally maps input names to workflow-wide reusable Git worktree defaults.
813
+ - `.output(key, schema?)` declares typed outputs that parent workflows receive from `ctx.workflow(childWorkflow, ...)`.
738
814
  - `.run(async (ctx) => { ... })` defines the workflow body.
739
815
  - `.compile()` returns the workflow definition for discovery.
740
816
 
741
817
  `prompt` and `task` are aliases for task text. Prefer `prompt` inside authored workflow files because it mirrors lower-level `stage.prompt(...)`; `task` remains useful in direct tool calls and chain examples.
742
818
 
743
- A valid workflow must create at least one tracked stage by calling `ctx.task()`, `ctx.chain()`, `ctx.parallel()`, or `ctx.stage()` in its run body. A no-stage workflow is skipped during discovery because it has no graph node to inspect, attach to, interrupt, resume, or render.
819
+ A valid workflow must create at least one tracked stage by calling `ctx.task()`, `ctx.chain()`, `ctx.parallel()`, `ctx.stage()`, or `ctx.workflow()` in its run body. A no-stage workflow is skipped during discovery because it has no graph node to inspect, attach to, interrupt, resume, or render.
744
820
 
745
821
  ### Inputs
746
822
 
747
- Supported input schema types are:
823
+ Inputs are declared with TypeBox `Type.*` schemas passed to `.input(key, schema)`. `Type` is re-exported from `@bastani/workflows` (along with the `Static` and `TSchema` type helpers), so you can author and type schemas without adding a separate `typebox` dependency. Common input schemas map to picker kinds and accepted runtime values:
824
+
825
+ | TypeBox schema | Picker kind | Accepted runtime value |
826
+ |---|---|---|
827
+ | `Type.String({ default? })` | text | string |
828
+ | `Type.Number({ default? })` | number | number |
829
+ | `Type.Integer({ default? })` | integer | integer (whole number) |
830
+ | `Type.Boolean({ default? })` | boolean | boolean |
831
+ | `Type.Union([Type.Literal("a"), Type.Literal("b")], { default? })` | select | one of the literal strings |
832
+
833
+ A `Type.Union([Type.Literal(...)])` of string literals is how a 'select' is expressed: the input picker renders those literals as the selectable choices, and runtime validation rejects any value outside them. Put `description` and `default` in the schema options object, e.g. `Type.String({ description: "…", default: "…" })`. An input is required when its schema is **not** wrapped in `Type.Optional(...)` and declares no `default`; wrap optional inputs in `Type.Optional(...)`. A `default` does not make an input optional — a defaulted input is always present after defaults are applied.
834
+
835
+ Prefer explicit descriptions because `/workflow inputs <name>`, `/workflow <name> --help`, and the input picker show them to the user. Runtime validation uses TypeBox `Value` and is strict for both top-level named runs and `ctx.workflow(...)` child calls: Atomic rejects unknown keys, missing required values, type mismatches, non-JSON-serializable values, and union/literal values outside the declared choices before the workflow body starts. It does not coerce strings like `"3"` to numbers; pass `count=3` or JSON numbers when a schema declares `Type.Number()`.
836
+
837
+ In TypeScript workflow files, `.input(...)` also narrows `ctx.inputs` for better intellisense: required/defaulted `Type.String()` inputs are `string`, `Type.Number()` is `number`, `Type.Boolean()` is `boolean`, a `Type.Union([Type.Literal(...)])` select is the literal string union, and `Type.Optional(...)` inputs include `undefined`. Use `Static<typeof schema>` when you need the inferred TypeScript type of a schema directly.
838
+
839
+ ### Outputs
840
+
841
+ Workflow outputs are runtime contracts for completed workflow runs and for parent workflows that call a child with `ctx.workflow(childWorkflow, ...)`. A workflow returns a JSON-serializable object from `.run()`, and `.output(key, schema?)` documents, validates, and exposes keys from that returned object. Primitives, arrays, `null`, functions, symbols, `undefined` properties, `NaN`, and infinite numbers fail validation.
842
+
843
+ **Return convention:** outputs are return-object keys. Atomic never infers child workflow outputs from stage names, stage order, or the final assistant message. If a parent should read `child.outputs.foo`, the child workflow's `.run()` must both declare `.output("foo", schema)` and return `{ foo: value }`. `result` is not special and is never added for you: to expose `result`, declare `.output("result", schema)` and return `{ result }` exactly like any other output. Returning a key that is not declared with `.output(...)` fails the run with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it with .output("<key>", Type....) or remove it from the .run() return`.
844
+
845
+ `.output(...)` is a schema contract, not an automatic stage selector. To expose values from any stage, capture the stage/task/child result in normal TypeScript and return it from `.run()` under the desired key:
846
+
847
+ ```ts
848
+ export default defineWorkflow("review-with-summary")
849
+ .output("research_summary", Type.String())
850
+ .output("review", Type.String())
851
+ .run(async (ctx) => {
852
+ const research = await ctx.task("research", { prompt: "Research the target." });
853
+ const review = await ctx.task("review", {
854
+ prompt: "Review using this research:\n\n{previous}",
855
+ previous: research,
856
+ });
857
+
858
+ return {
859
+ research_summary: research.text,
860
+ review: review.text,
861
+ };
862
+ })
863
+ .compile();
864
+ ```
865
+
866
+ There is no automatic `result` output. A workflow exposes exactly the keys it declares with `.output(...)` and returns from `.run()` — nothing more. To expose `result`, declare `.output("result", schema)` and return `{ result }` like any other output. If `.run()` returns a key that was never declared with `.output(...)`, the run fails with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it with .output("<key>", Type....) or remove it from the .run() return` (the child-call variant reports `... child "<alias>" returned undeclared output "<key>" from "<childName>"`).
867
+
868
+ Outputs are declared with TypeBox `Type.*` schemas passed to `.output(key, schema)`. **Prefer precise schemas.** A precise schema gives a precise `Static<>` type for the `.run()` return and for any parent reading `child.outputs`, and it makes runtime validation enforce the real shape instead of waving values through. Reach for `Type.Unknown()`, `Type.Any()`, `Type.Array(Type.Unknown())`, or `Type.Object({}, { additionalProperties: true })` only for genuinely dynamic data whose shape you cannot know ahead of time.
869
+
870
+ | TypeBox schema | Static type | Accepted runtime value |
871
+ |---|---|---|
872
+ | `Type.String({ ... })` | `string` | string |
873
+ | `Type.Number({ ... })` | `number` | finite number |
874
+ | `Type.Integer({ ... })` | `number` | integer |
875
+ | `Type.Boolean({ ... })` | `boolean` | boolean |
876
+ | `Type.Union([Type.Literal("a"), Type.Literal("b")], { ... })` | `"a" \| "b"` | one of the literal strings |
877
+ | `Type.Array(Type.String())` | `string[]` | array of strings |
878
+ | `Type.Object({ topic: Type.String(), score: Type.Number() })` | `{ topic: string; score: number }` | object matching that shape |
879
+ | `Type.Unsafe<MyInterface>(runtimeSchema)` | `MyInterface` | whatever `runtimeSchema` accepts (escape hatch) |
880
+ | `Type.Array(Type.Unknown())` | `unknown[]` | any JSON array (last resort, dynamic only) |
881
+ | `Type.Object({}, { additionalProperties: true })` | `Record<string, unknown>` | any JSON object (last resort, dynamic only) |
882
+ | `Type.Unknown()` / `Type.Any()` | `unknown` / `any` | any JSON-serializable value (last resort) |
883
+
884
+ Output schemas carry `description` in their options object. A declared output is required when its schema is **not** wrapped in `Type.Optional(...)`; wrap outputs that may be absent in `Type.Optional(...)`. A required output means the workflow `.run()` return object must contain that output before the run can complete; a missing required output fails with `missing output "<key>"`, and a declared value whose runtime type does not match the schema fails with `output "<key>" expected <type>, got <actual>`. For child workflow calls, the parent boundary fails before the parent continues. Declared outputs are validated against the declared schema with TypeBox `Value` on completion, and every returned/exposed value is recursively validated as JSON-serializable. Child output replay still performs a structured-clone safety check after JSON validation so continuation can restore completed child workflow boundaries.
885
+
886
+ #### Prefer precise schemas
887
+
888
+ A loose output like `Type.Unknown()` or `Type.Object({}, { additionalProperties: true })` types the `.run()` return and `child.outputs.x` as `unknown`/`Record<string, unknown>`, so every consumer must cast or guard before using the value, and runtime validation only checks "is this JSON?" instead of the real shape. Declaring the shape fixes both at once:
889
+
890
+ ```ts
891
+ // ❌ Loose: child.outputs.report is `unknown`; nothing checks the shape at runtime.
892
+ .output("report", Type.Unknown())
893
+
894
+ // ✅ Precise: child.outputs.report is `{ topic: string; score: number; tags: string[] }`,
895
+ // and TypeBox rejects a returned value missing `score` or with a non-number `score`.
896
+ .output(
897
+ "report",
898
+ Type.Object({
899
+ topic: Type.String(),
900
+ score: Type.Number(),
901
+ tags: Type.Array(Type.String()),
902
+ }),
903
+ )
904
+ ```
905
+
906
+ The same rule applies to inputs: `.input("counts", Type.Array(Type.Number()))` makes `ctx.inputs.counts` a `number[]`, while `Type.Array(Type.Unknown())` only gives you `unknown[]`.
907
+
908
+ #### `Type.Unsafe<T>()` escape hatch for deeply-nested values
909
+
910
+ When you already have a precise TypeScript interface for a deeply-nested serializable value and don't want to hand-write the equivalent TypeBox schema, wrap a permissive runtime schema with `Type.Unsafe<MyInterface>(...)`. The **static** type becomes exactly `MyInterface` (so `ctx.inputs`, the `.run()` return, and `child.outputs` stay precise), while the **runtime** check stays as lenient as the wrapped schema:
911
+
912
+ ```ts
913
+ import { defineWorkflow, Type } from "@bastani/workflows";
914
+
915
+ interface ResearchPacket {
916
+ readonly topic: string;
917
+ readonly score: number;
918
+ readonly sections: readonly { readonly heading: string; readonly body: string }[];
919
+ }
920
+
921
+ export default defineWorkflow("research-packet")
922
+ .input("topic", Type.String())
923
+ // Static type = ResearchPacket; runtime only checks "is a JSON object".
924
+ .output("packet", Type.Unsafe<ResearchPacket>(Type.Object({}, { additionalProperties: true })))
925
+ .run(async (ctx) => {
926
+ const packet: ResearchPacket = {
927
+ topic: ctx.inputs.topic,
928
+ score: 1,
929
+ sections: [{ heading: "overview", body: "…" }],
930
+ };
931
+ return { packet }; // statically checked against ResearchPacket
932
+ })
933
+ .compile();
934
+ ```
935
+
936
+ Tradeoff: `Type.Unsafe<T>()` does not deeply validate at runtime — it trusts that the produced value matches `T`. Use it when the producing code already guarantees the shape (the `contract-complex-leaf` contract workflow does exactly this, wrapping `Type.Unsafe<ComplexPacket>(...)` and `Type.Unsafe<readonly ComplexRecord[]>(...)` around permissive runtime schemas). When you can express the shape directly, prefer a real `Type.Object(...)`/`Type.Array(...)` so runtime validation also catches drift. Keep bare `Type.Unknown()` and `Type.Object({}, { additionalProperties: true })` for the rare cases where the value is genuinely dynamic.
937
+
938
+ #### How types flow
939
+
940
+ - `ctx.inputs.x` is `Static<inputSchema>` for the input you declared with `.input("x", schema)` — required and defaulted schemas are always present, and `Type.Optional(...)` adds `| undefined`.
941
+ - The `.run()` return is checked against your declared outputs at **compile time** (a missing required output or a wrong value type is a TypeScript error) and at **runtime** via TypeBox `Value` (undeclared keys are rejected and the declared shape is enforced recursively).
942
+ - `ctx.workflow(child).outputs` is typed from the child's declared `.output(...)` contract, so a parent reads precisely-typed child outputs without casting.
943
+
944
+ Use `Static<typeof schema>` (both `Static` and `TSchema` are re-exported from `@bastani/workflows`) when you need the inferred TypeScript type of a schema directly — for example to type a helper that builds an output value.
945
+
946
+ ### Workflow Composition
947
+
948
+ Use workflow composition when one workflow should call another reusable workflow and consume its outputs as a tracked boundary stage. The child can be a user-defined workflow from your project/package or a bundled builtin workflow. In both cases, use normal TypeScript imports: import the compiled child workflow definition, then pass that definition directly to `ctx.workflow(workflowDefinition, options)`. Registry names, path objects, and string aliases are not accepted by `ctx.workflow(...)`.
949
+
950
+ For workflows intended to be called by parent workflows, declare `.output(...)` for every field a parent should rely on, including `result`. No output exists without declaration: a child exposes exactly its declared outputs, and returning an undeclared key fails the child call.
951
+
952
+ #### Compose with a user-defined workflow
953
+
954
+ User-defined workflows are ordinary TypeScript modules. Import the compiled definition with a relative module specifier and call it directly from the parent workflow:
955
+
956
+ ```ts
957
+ // .atomic/workflows/shared-research.ts
958
+ import { defineWorkflow, Type } from "@bastani/workflows";
959
+
960
+ export default defineWorkflow("shared-research")
961
+ .input("topic", Type.String())
962
+ .output("summary", Type.String({ description: "Research summary markdown." }))
963
+ // Precise element type: child.outputs.sources is `string[] | undefined`, not `unknown[]`.
964
+ .output("sources", Type.Optional(Type.Array(Type.String(), { description: "Source URLs and file references." })))
965
+ .run(async (ctx) => {
966
+ const result = await ctx.task("research", { prompt: `Research ${String(ctx.inputs.topic)}` });
967
+ return { summary: result.text, sources: [] };
968
+ })
969
+ .compile();
970
+
971
+ // .atomic/workflows/research-and-synthesize.ts
972
+ import { defineWorkflow, Type } from "@bastani/workflows";
973
+ import sharedResearch from "./shared-research.js";
974
+
975
+ export default defineWorkflow("research-and-synthesize")
976
+ .input("topic", Type.String())
977
+ .output("final", Type.String({ description: "Synthesis built from the child research summary." }))
978
+ .output("child_run_id", Type.String({ description: "Run id of the nested shared-research child." }))
979
+ .run(async (ctx) => {
980
+ const child = await ctx.workflow(sharedResearch, {
981
+ inputs: { topic: ctx.inputs.topic },
982
+ stageName: "run shared research",
983
+ });
984
+
985
+ const final = await ctx.task("synthesize", {
986
+ prompt: `Synthesize:\n\n${String(child.outputs.summary)}`,
987
+ });
988
+ return { final: final.text, child_run_id: child.runId };
989
+ })
990
+ .compile();
991
+ ```
992
+
993
+ #### Compose with builtin workflows
994
+
995
+ Builtin workflows are also exported as compiled workflow definitions, so parent workflows can call them exactly like user-defined workflows. Use the barrel export when you want several builtins:
996
+
997
+ ```ts
998
+ import { deepResearchCodebase, goal, openClaudeDesign, ralph } from "@bastani/workflows/builtin";
999
+ ```
1000
+
1001
+ Or import one builtin from its individual module path:
1002
+
1003
+ ```ts
1004
+ import deepResearchCodebase from "@bastani/workflows/builtin/deep-research-codebase";
1005
+ import goal from "@bastani/workflows/builtin/goal";
1006
+ import openClaudeDesign from "@bastani/workflows/builtin/open-claude-design";
1007
+ import ralph from "@bastani/workflows/builtin/ralph";
1008
+ ```
1009
+
1010
+ Common builtin import targets:
1011
+
1012
+ | Workflow name | TypeScript export | Individual module path | Typical use inside another workflow |
1013
+ |---|---|---|---|
1014
+ | `deep-research-codebase` | `deepResearchCodebase` | `@bastani/workflows/builtin/deep-research-codebase` | Gather broad repo research before planning, synthesis, or implementation. |
1015
+ | `goal` | `goal` | `@bastani/workflows/builtin/goal` | Run a bounded implementation/check loop with receipts and reviewer-gated completion. |
1016
+ | `ralph` | `ralph` | `@bastani/workflows/builtin/ralph` | Delegate a larger migration/refactor/spec-to-PR effort to Ralph's plan/orchestrate/review loop. |
1017
+ | `open-claude-design` | `openClaudeDesign` | `@bastani/workflows/builtin/open-claude-design` | Generate and refine a UI/design artifact and handoff spec. |
1018
+
1019
+ Example parent workflow that runs builtin deep research, then chooses either `goal` or `ralph` as the nested implementation runner:
1020
+
1021
+ ```ts
1022
+ import { defineWorkflow, Type } from "@bastani/workflows";
1023
+ import { deepResearchCodebase, goal, ralph } from "@bastani/workflows/builtin";
1024
+
1025
+ export default defineWorkflow("research-then-implement")
1026
+ .input("topic", Type.String())
1027
+ .input(
1028
+ "runner",
1029
+ Type.Union([Type.Literal("goal"), Type.Literal("ralph")], {
1030
+ default: "goal",
1031
+ description: "Use goal for bounded changes or Ralph for broad spec-to-PR work.",
1032
+ }),
1033
+ )
1034
+ .output("research_doc_path", Type.String({ description: "Path to the deep-research document used for implementation." }))
1035
+ .output("runner", Type.String({ description: "Which nested runner executed: \"goal\" or \"ralph\"." }))
1036
+ // Genuinely dynamic: the nested runner (goal vs ralph) is chosen at runtime and
1037
+ // each exposes a different declared output shape, so a loose object is appropriate here.
1038
+ // When a child's outputs are known and fixed, declare the precise shape instead.
1039
+ .output("implementation", Type.Object({}, { additionalProperties: true, description: "Declared outputs from the nested implementation workflow." }))
1040
+ .run(async (ctx) => {
1041
+ const topic = String(ctx.inputs.topic);
1042
+ const research = await ctx.workflow(deepResearchCodebase, {
1043
+ inputs: { prompt: topic, max_concurrency: 4 },
1044
+ stageName: "deep research",
1045
+ });
1046
+
1047
+ if (String(ctx.inputs.runner) === "ralph") {
1048
+ const implementation = await ctx.workflow(ralph, {
1049
+ inputs: {
1050
+ prompt: `Use the research document at ${String(research.outputs.research_doc_path)} to plan, implement, review, and prepare a PR for: ${topic}`,
1051
+ },
1052
+ stageName: "ralph implementation",
1053
+ });
1054
+
1055
+ return {
1056
+ research_doc_path: research.outputs.research_doc_path,
1057
+ runner: "ralph",
1058
+ implementation: implementation.outputs,
1059
+ };
1060
+ }
1061
+
1062
+ const implementation = await ctx.workflow(goal, {
1063
+ inputs: {
1064
+ objective: `Use the research document at ${String(research.outputs.research_doc_path)} to implement and validate: ${topic}`,
1065
+ max_turns: 3,
1066
+ },
1067
+ stageName: "goal implementation",
1068
+ });
1069
+
1070
+ return {
1071
+ research_doc_path: research.outputs.research_doc_path,
1072
+ runner: "goal",
1073
+ implementation: implementation.outputs,
1074
+ };
1075
+ })
1076
+ .compile();
1077
+ ```
1078
+
1079
+ Passing a compiled definition directly to `ctx.workflow(...)` uses the child workflow's normalized name for replay metadata and default boundary labels (`shared-research` for the user-defined example above, or builtin names such as `deep-research-codebase`, `goal`, and `ralph`).
1080
+
1081
+ `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 that child into the parent run, so composition behaves like inlining the child workflow code: child stages, HIL prompt nodes, and deeper imported workflows appear in one expanded graph. The nested run id remains available internally for routing attach/pause/interrupt/resume/kill to the correct live stage, but it is not shown as a separate top-level `/workflow status` entry. The returned child result has:
1082
+
1083
+ | Field | Meaning |
1084
+ |---|---|
1085
+ | `workflow` | Normalized child workflow name. |
1086
+ | `runId` | Nested child run id. |
1087
+ | `status` | `completed` when the child workflow succeeds. Failed or interrupted children make the parent child call fail. |
1088
+ | `outputs` | Declared child outputs. |
1089
+
1090
+ `ctx.workflow()` options:
1091
+
1092
+ | Option | Meaning |
1093
+ |---|---|
1094
+ | `inputs` | Values validated against the child workflow's `.input()` schema before the child starts. |
1095
+ | `stageName` | Parent boundary stage label. Defaults to `workflow:<workflow-name>`. |
1096
+
1097
+ Output exposure rules:
1098
+
1099
+ ```ts
1100
+ const child = await ctx.workflow(sharedResearch);
1101
+ child.outputs.summary; // declared by sharedResearch.output("summary", ...)
1102
+ child.outputs.sources; // declared by sharedResearch.output("sources", ...)
1103
+ ```
1104
+
1105
+ A child exposes exactly its declared outputs — the keys it declared with `.output(...)` and returned from `.run()`. There are no implicit outputs and no raw return-object passthrough. If `.run()` returns a key that was not declared with `.output(...)`, the child call fails with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it with .output("<key>", Type....) or remove it from the .run() return` (surfaced through the parent as `... child "<alias>" returned undeclared output "<key>" from "<childName>"`). A child with no declared outputs therefore exposes no outputs. Missing required outputs, schema type mismatches, and non-JSON-serializable returned values fail the child workflow call before the parent continues.
1106
+
1107
+ Only compiled workflow definitions can be passed to `ctx.workflow(...)`. Import reusable workflows with TypeScript `import` statements first; use `/workflow` names such as `goal` only for launching named runs, not as `ctx.workflow(...)` arguments. If a module is missing or does not export a compiled workflow definition, workflow discovery fails when loading that module. Nested child workflows count against `maxDepth` (default `4` total workflow levels).
748
1108
 
749
- - `text` / `string`: optional `default: string`
750
- - `number`: optional `default: number`
751
- - `boolean`: optional `default: boolean`
752
- - `select`: required `choices: string[]`, optional `default: string`
1109
+ The graph includes both the parent boundary node and the imported child workflow's own stages while the child is loading/running, so the user can observe progress and interrupt sub-workflows before they complete. Completed boundaries still retain the child workflow name, child run id prefix, and exposed output count for replay/debugging. Use `stageName` when the parent needs a more specific label, but keep it concise so the child summary remains readable in the graph.
753
1110
 
754
- All schemas support `description` and `required`. Prefer explicit descriptions because `/workflow inputs <name>`, `/workflow <name> --help`, and the input picker show them to the user. Runtime validation rejects unknown keys, missing required values, type mismatches, and select values outside `choices`; it does not coerce strings like `"3"` to numbers.
1111
+ Continuation replay treats the parent child-workflow boundary as the durable checkpoint: a previously completed child boundary replays with the original exposed outputs and without re-running the child, while a child that failed or was interrupted before completion starts again from the beginning on continuation.
755
1112
 
756
1113
  ## Workflow Primitives
757
1114
 
@@ -762,6 +1119,7 @@ Prefer high-level primitives because they create tracked graph nodes, provide co
762
1119
  | One LLM/session task with workflow tracking | `ctx.task(name, options)` |
763
1120
  | Dependent sequential tasks | `ctx.chain(steps, options?)` |
764
1121
  | Independent concurrent branches | `ctx.parallel(steps, options?)` |
1122
+ | Reusable child workflow | Call `ctx.workflow(workflowDefinition, options?)` |
765
1123
  | Human input during a workflow run | `ctx.ui.input/confirm/select/editor` |
766
1124
  | Pure deterministic computation, parsing, or file I/O | Plain TypeScript in `.run()` or helpers |
767
1125
  | Fine-grained session control | `ctx.stage(name, options?)` |
@@ -804,10 +1162,11 @@ To bind user inputs to a workflow-wide worktree default, use the builder method:
804
1162
 
805
1163
  ```ts
806
1164
  export default defineWorkflow("safe-implementation")
807
- .input("task", { type: "text", required: true })
808
- .input("git_worktree_dir", { type: "string", default: "" })
809
- .input("base_branch", { type: "string", default: "origin/main" })
1165
+ .input("task", Type.String())
1166
+ .input("git_worktree_dir", Type.String({ default: "" }))
1167
+ .input("base_branch", Type.String({ default: "origin/main" }))
810
1168
  .worktreeFromInputs({ gitWorktreeDir: "git_worktree_dir", baseBranch: "base_branch" })
1169
+ .output("result", Type.String({ description: "Implementation result text." }))
811
1170
  .run(async (ctx) => {
812
1171
  const result = await ctx.task("implement", { task: String(ctx.inputs.task) });
813
1172
  return { result: result.text };
@@ -853,9 +1212,10 @@ The programmatic definition object mirrors the workflow tool for named runs (`mo
853
1212
  Use `createRegistry()` when code needs to group definitions explicitly:
854
1213
 
855
1214
  ```ts
856
- import { createRegistry, defineWorkflow } from "@bastani/workflows";
1215
+ import { createRegistry, defineWorkflow, Type } from "@bastani/workflows";
857
1216
 
858
1217
  const alpha = defineWorkflow("alpha")
1218
+ .output("text", Type.String({ description: "Alpha task output text." }))
859
1219
  .run(async (ctx) => {
860
1220
  const result = await ctx.task("alpha", { prompt: "Run alpha." });
861
1221
  return { text: result.text };
@@ -990,6 +1350,7 @@ Before implementing or shipping a non-trivial workflow, answer these questions:
990
1350
  - **Inputs:** Which values should be declared as inputs? What is the narrowest schema type? Which defaults are safe?
991
1351
  - **Stage decomposition:** For each stage, what question does it answer, what context does it need, what output should it return, and what model/tool/MCP requirements does it have?
992
1352
  - **Information flow:** For every edge between stages, is `previous` enough, or should the handoff use structured returns, files, `reads`, `output`, or `outputMode`?
1353
+ - **Output contract:** Which outputs should be declared with `.output(...)`, which stage/task/child results should `.run()` return for those keys, and what runtime type must each value have? If another workflow may call this workflow as a child, which non-default outputs should the parent rely on?
993
1354
  - **Context size:** Can downstream stages succeed from the handoff alone? Should large transcripts, logs, or research bundles be summarized or saved as artifacts?
994
1355
  - **Control flow:** Should the workflow use `ctx.chain`, `ctx.parallel`, `ctx.ui`, bounded loops, `failFast`, or `fallbackModels`?
995
1356
  - **User experience:** Are stage names readable in status and graph views? Is the final output compact? Are important artifacts saved with stable paths?
@@ -1003,6 +1364,9 @@ Good workflows are information-flow systems, not just prompt sequences. Keep sta
1003
1364
  - Do not guess input keys; inspect with `inputs` or `get` first.
1004
1365
  - Do not call `create`, `update`, or `delete` on the workflow tool; definitions are code-authored.
1005
1366
  - Do not use legacy workflow tool fields like `agent`, `stage`, or run-control `name`.
1367
+ - Do not pass strings such as `"goal"` or path objects to `ctx.workflow(...)`; import the compiled workflow definition from `@bastani/workflows/builtin` or another TypeScript module first.
1368
+ - Do not rely on undeclared child outputs; returning a key that is not declared with `.output(...)` fails the run. Declare `.output(...)` for every child-workflow field you expose — including `result` — and return values matching those schemas from `.run()`.
1369
+ - 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`.
1006
1370
  - Do not expect named workflow runs to block the chat turn; they are background tasks.
1007
1371
  - Do not call `kill` when the user asks to interrupt or pause resumably.
1008
1372
  - Keep stage names readable because they appear in workflow status and UI.