@bastani/atomic 0.9.0-alpha.1 → 0.9.0-alpha.2

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 (211) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +2 -2
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +3 -3
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/package.json +4 -4
  10. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  11. package/dist/builtin/web-access/package.json +2 -2
  12. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  13. package/dist/builtin/workflows/README.md +189 -122
  14. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +30 -27
  15. package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
  16. package/dist/builtin/workflows/builtin/goal.ts +39 -44
  17. package/dist/builtin/workflows/builtin/index.d.ts +1 -0
  18. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +16 -17
  19. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +1 -0
  20. package/dist/builtin/workflows/builtin/open-claude-design.ts +42 -50
  21. package/dist/builtin/workflows/builtin/ralph.ts +44 -41
  22. package/dist/builtin/workflows/package.json +2 -2
  23. package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
  24. package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +217 -0
  25. package/dist/builtin/workflows/src/authoring/workflow.ts +184 -0
  26. package/dist/builtin/workflows/src/authoring.d.ts +14 -66
  27. package/dist/builtin/workflows/src/engine/graph-inference.ts +100 -0
  28. package/dist/builtin/workflows/src/engine/options.ts +40 -0
  29. package/dist/builtin/workflows/src/engine/primitives/chain.ts +29 -0
  30. package/dist/builtin/workflows/src/engine/primitives/exit.ts +2 -0
  31. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +47 -0
  32. package/dist/builtin/workflows/src/engine/primitives/task.ts +108 -0
  33. package/dist/builtin/workflows/src/engine/primitives/ui.ts +41 -0
  34. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +159 -0
  35. package/dist/builtin/workflows/src/engine/replay.ts +8 -0
  36. package/dist/builtin/workflows/src/engine/run.ts +356 -0
  37. package/dist/builtin/workflows/src/engine/runtime.ts +160 -0
  38. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +9 -3
  39. package/dist/builtin/workflows/src/extension/workflow-schema.ts +0 -18
  40. package/dist/builtin/workflows/src/index.ts +0 -2
  41. package/dist/builtin/workflows/src/runs/background/runner.ts +6 -3
  42. package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +3 -3
  43. package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +4 -4
  44. package/dist/builtin/workflows/src/runs/foreground/executor-child-workflow.ts +1 -158
  45. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +1 -1
  46. package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +2 -2
  47. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +1 -1
  48. package/dist/builtin/workflows/src/runs/foreground/executor-run.ts +1 -359
  49. package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +1 -1
  50. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +2 -5
  51. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +12 -4
  52. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +4 -3
  53. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +9 -2
  54. package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +2 -132
  55. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -2
  56. package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +2 -100
  57. package/dist/builtin/workflows/src/sdk-surface.ts +6 -9
  58. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +9 -3
  59. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +17 -3
  60. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +3 -33
  61. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +9 -81
  62. package/dist/builtin/workflows/src/shared/types.ts +25 -8
  63. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +49 -0
  64. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +84 -0
  65. package/dist/builtin/workflows/src/workflows/registry.ts +7 -3
  66. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  67. package/dist/core/agent-session-auto-compaction.js +6 -1
  68. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  69. package/dist/core/agent-session-bash.d.ts.map +1 -1
  70. package/dist/core/agent-session-bash.js +0 -5
  71. package/dist/core/agent-session-bash.js.map +1 -1
  72. package/dist/core/agent-session-methods.d.ts +0 -2
  73. package/dist/core/agent-session-methods.d.ts.map +1 -1
  74. package/dist/core/agent-session-methods.js.map +1 -1
  75. package/dist/core/agent-session-services.d.ts +0 -1
  76. package/dist/core/agent-session-services.d.ts.map +1 -1
  77. package/dist/core/agent-session-services.js +0 -1
  78. package/dist/core/agent-session-services.js.map +1 -1
  79. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  80. package/dist/core/agent-session-tool-registry.js +0 -2
  81. package/dist/core/agent-session-tool-registry.js.map +1 -1
  82. package/dist/core/agent-session-types.d.ts +0 -2
  83. package/dist/core/agent-session-types.d.ts.map +1 -1
  84. package/dist/core/agent-session-types.js.map +1 -1
  85. package/dist/core/agent-session.d.ts +0 -2
  86. package/dist/core/agent-session.d.ts.map +1 -1
  87. package/dist/core/agent-session.js +0 -1
  88. package/dist/core/agent-session.js.map +1 -1
  89. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  90. package/dist/core/atomic-guide-command.js +1 -1
  91. package/dist/core/atomic-guide-command.js.map +1 -1
  92. package/dist/core/extensions/loader-core.d.ts +1 -3
  93. package/dist/core/extensions/loader-core.d.ts.map +1 -1
  94. package/dist/core/extensions/loader-core.js +13 -6
  95. package/dist/core/extensions/loader-core.js.map +1 -1
  96. package/dist/core/extensions/loader-virtual-modules.d.ts +7 -1
  97. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  98. package/dist/core/extensions/loader-virtual-modules.js +34 -2
  99. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  100. package/dist/core/extensions/loader.d.ts +2 -1
  101. package/dist/core/extensions/loader.d.ts.map +1 -1
  102. package/dist/core/extensions/loader.js +2 -1
  103. package/dist/core/extensions/loader.js.map +1 -1
  104. package/dist/core/index.d.ts +0 -1
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js +0 -1
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/model-registry-builtins.d.ts.map +1 -1
  109. package/dist/core/model-registry-builtins.js +6 -0
  110. package/dist/core/model-registry-builtins.js.map +1 -1
  111. package/dist/core/model-registry-schemas.d.ts +65 -13
  112. package/dist/core/model-registry-schemas.d.ts.map +1 -1
  113. package/dist/core/model-registry-schemas.js +10 -0
  114. package/dist/core/model-registry-schemas.js.map +1 -1
  115. package/dist/core/resource-loader-core.d.ts +1 -0
  116. package/dist/core/resource-loader-core.d.ts.map +1 -1
  117. package/dist/core/resource-loader-core.js +2 -0
  118. package/dist/core/resource-loader-core.js.map +1 -1
  119. package/dist/core/resource-loader-extensions.d.ts.map +1 -1
  120. package/dist/core/resource-loader-extensions.js +3 -3
  121. package/dist/core/resource-loader-extensions.js.map +1 -1
  122. package/dist/core/resource-loader-internals.d.ts +1 -0
  123. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  124. package/dist/core/resource-loader-internals.js.map +1 -1
  125. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  126. package/dist/core/resource-loader-reload.js +6 -2
  127. package/dist/core/resource-loader-reload.js.map +1 -1
  128. package/dist/core/sdk-exports.d.ts +1 -1
  129. package/dist/core/sdk-exports.d.ts.map +1 -1
  130. package/dist/core/sdk-exports.js.map +1 -1
  131. package/dist/core/sdk-types.d.ts +0 -3
  132. package/dist/core/sdk-types.d.ts.map +1 -1
  133. package/dist/core/sdk-types.js.map +1 -1
  134. package/dist/core/sdk.d.ts.map +1 -1
  135. package/dist/core/sdk.js +0 -1
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-manager-history.d.ts.map +1 -1
  138. package/dist/core/session-manager-history.js +2 -1
  139. package/dist/core/session-manager-history.js.map +1 -1
  140. package/dist/core/tools/bash.d.ts +0 -5
  141. package/dist/core/tools/bash.d.ts.map +1 -1
  142. package/dist/core/tools/bash.js +10 -11
  143. package/dist/core/tools/bash.js.map +1 -1
  144. package/dist/core/tools/edit-diff-preserve.d.ts +18 -0
  145. package/dist/core/tools/edit-diff-preserve.d.ts.map +1 -0
  146. package/dist/core/tools/edit-diff-preserve.js +85 -0
  147. package/dist/core/tools/edit-diff-preserve.js.map +1 -0
  148. package/dist/core/tools/edit-diff.d.ts +3 -2
  149. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  150. package/dist/core/tools/edit-diff.js +15 -18
  151. package/dist/core/tools/edit-diff.js.map +1 -1
  152. package/dist/core/tools/index.d.ts +0 -1
  153. package/dist/core/tools/index.d.ts.map +1 -1
  154. package/dist/core/tools/index.js +0 -1
  155. package/dist/core/tools/index.js.map +1 -1
  156. package/dist/index.d.ts +2 -2
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +1 -1
  159. package/dist/index.js.map +1 -1
  160. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/model-selector.js +2 -2
  162. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  163. package/dist/modes/interactive/model-search.d.ts +5 -0
  164. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  165. package/dist/modes/interactive/model-search.js +9 -0
  166. package/dist/modes/interactive/model-search.js.map +1 -1
  167. package/dist/utils/shell.d.ts +1 -0
  168. package/dist/utils/shell.d.ts.map +1 -1
  169. package/dist/utils/shell.js +12 -5
  170. package/dist/utils/shell.js.map +1 -1
  171. package/docs/custom-provider.md +4 -3
  172. package/docs/models.md +3 -2
  173. package/docs/packages.md +2 -2
  174. package/docs/quickstart.md +1 -1
  175. package/docs/sdk.md +2 -40
  176. package/docs/security.md +1 -1
  177. package/docs/workflows.md +238 -173
  178. package/package.json +5 -5
  179. package/dist/builtin/workflows/src/workflows/define-workflow.ts +0 -277
  180. package/dist/core/tools/bash-policy-compile.d.ts +0 -5
  181. package/dist/core/tools/bash-policy-compile.d.ts.map +0 -1
  182. package/dist/core/tools/bash-policy-compile.js +0 -241
  183. package/dist/core/tools/bash-policy-compile.js.map +0 -1
  184. package/dist/core/tools/bash-policy-evaluate.d.ts +0 -3
  185. package/dist/core/tools/bash-policy-evaluate.d.ts.map +0 -1
  186. package/dist/core/tools/bash-policy-evaluate.js +0 -92
  187. package/dist/core/tools/bash-policy-evaluate.js.map +0 -1
  188. package/dist/core/tools/bash-policy-format.d.ts +0 -5
  189. package/dist/core/tools/bash-policy-format.d.ts.map +0 -1
  190. package/dist/core/tools/bash-policy-format.js +0 -49
  191. package/dist/core/tools/bash-policy-format.js.map +0 -1
  192. package/dist/core/tools/bash-policy-parser.d.ts +0 -4
  193. package/dist/core/tools/bash-policy-parser.d.ts.map +0 -1
  194. package/dist/core/tools/bash-policy-parser.js +0 -155
  195. package/dist/core/tools/bash-policy-parser.js.map +0 -1
  196. package/dist/core/tools/bash-policy-segment.d.ts +0 -3
  197. package/dist/core/tools/bash-policy-segment.d.ts.map +0 -1
  198. package/dist/core/tools/bash-policy-segment.js +0 -275
  199. package/dist/core/tools/bash-policy-segment.js.map +0 -1
  200. package/dist/core/tools/bash-policy-shell.d.ts +0 -11
  201. package/dist/core/tools/bash-policy-shell.d.ts.map +0 -1
  202. package/dist/core/tools/bash-policy-shell.js +0 -267
  203. package/dist/core/tools/bash-policy-shell.js.map +0 -1
  204. package/dist/core/tools/bash-policy-types.d.ts +0 -146
  205. package/dist/core/tools/bash-policy-types.d.ts.map +0 -1
  206. package/dist/core/tools/bash-policy-types.js +0 -2
  207. package/dist/core/tools/bash-policy-types.js.map +0 -1
  208. package/dist/core/tools/bash-policy.d.ts +0 -6
  209. package/dist/core/tools/bash-policy.d.ts.map +0 -1
  210. package/dist/core/tools/bash-policy.js +0 -5
  211. package/dist/core/tools/bash-policy.js.map +0 -1
package/docs/workflows.md CHANGED
@@ -86,7 +86,7 @@ Return structured output with `consolidated_review` and `decision` fields.
86
86
  Atomic will:
87
87
 
88
88
  - ask clarifying questions when stage purpose, inputs, models, or handoffs are ambiguous,
89
- - write a `.atomic/workflows/<name>.ts` file using `defineWorkflow(...).input(...).run(...).compile()`,
89
+ - write a `.atomic/workflows/<name>.ts` file using `workflow({...})`,
90
90
  - pick `ctx.task` / `ctx.chain` / `ctx.parallel` / `ctx.ui` per the [primitives](#workflow-primitives) and [task options](#task-and-stage-options) reference, and
91
91
  - run `/workflow reload` so Atomic rediscovers the workflow resource and you can launch it immediately.
92
92
 
@@ -109,26 +109,29 @@ Named workflow runs are background-oriented. After launch, expect a run id and m
109
109
  Workflow files are plain TypeScript modules. Create `.atomic/workflows/explain-file.ts`:
110
110
 
111
111
  ```ts
112
- import { defineWorkflow, Type } from "@bastani/workflows";
113
-
114
- export default defineWorkflow("explain-file")
115
- .description("Explain a file with tracked workflow stages.")
116
- .input("path", Type.String({ description: "File path to explain." }))
117
- .output(
118
- "explanation",
119
- Type.String({
112
+ import { workflow } from "@bastani/workflows";
113
+ import { Type } from "typebox";
114
+
115
+ export default workflow({
116
+ name: "explain-file",
117
+ description: "Explain a file with tracked workflow stages.",
118
+ inputs: {
119
+ path: Type.String({ description: "File path to explain." }),
120
+ },
121
+ outputs: {
122
+ explanation: Type.String({
120
123
  description: "Explanation of the file's purpose, risks, and key symbols.",
121
124
  }),
122
- )
123
- .run(async (ctx) => {
125
+ },
126
+ run: async (ctx) => {
124
127
  const explanation = await ctx.task("explain", {
125
128
  prompt: `Read ${String(ctx.inputs.path)} and explain purpose, risks, and key symbols.`,
126
129
  context: "fresh",
127
130
  });
128
131
 
129
132
  return { explanation: explanation.text };
130
- })
131
- .compile();
133
+ },
134
+ });
132
135
  ```
133
136
 
134
137
  Run `/workflow reload` or restart Atomic, then list and run it:
@@ -139,15 +142,15 @@ Run `/workflow reload` or restart Atomic, then list and run it:
139
142
  /workflow explain-file path="src/index.ts"
140
143
  ```
141
144
 
142
- See [Writing a Workflow](#writing-a-workflow) for the full builder API and [Workflow Primitives](#workflow-primitives) for `ctx.task` / `ctx.chain` / `ctx.parallel` / `ctx.stage` / `ctx.ui`.
145
+ See [Writing a Workflow](#writing-a-workflow) for the full `workflow({...})` API and [Workflow Primitives](#workflow-primitives) for `ctx.task` / `ctx.chain` / `ctx.parallel` / `ctx.stage` / `ctx.ui`.
143
146
 
144
147
  ## Built-in Workflows
145
148
 
146
149
  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.
147
150
 
148
- 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.
151
+ These same builtin workflows are also available to workflow authors as workflow 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.
149
152
 
150
- 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.
153
+ For the builtin result tables below, `deep-research-codebase`, `goal`, and `ralph` explicitly declare `outputs: { 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 declared in `outputs` and returned from `run` or supplied to `ctx.exit({ outputs })`; Atomic no longer adds any automatic `result` output.
151
154
 
152
155
  | Workflow | What it does | When to use |
153
156
  |---|---|---|
@@ -388,7 +391,7 @@ If the task is only deterministic TypeScript with no LLM/session stage, use a sc
388
391
  | Run, inspect, attach to, pause, interrupt, resume, or check status for an existing workflow | `/workflow ...` or `workflow({ action: ... })` |
389
392
  | Implement a small-to-medium scope change with an identifiable work surface, exact outcome, and named validation | `/workflow goal objective="..."` so Atomic keeps the run bounded, captures receipts in a goal ledger, gates completion through reviewers, and stops as `complete`, `blocked`, or `needs_human` |
390
393
  | Research and execute a larger migration, broad refactor, or multi-package change | `/workflow ralph prompt="..."` so Atomic can transform the prompt into a research question, research the codebase first, delegate implementation through sub-agents, review, and iterate; prompt text alone does not opt in to PR creation, so add `create_pr=true` only when you want the final `pull-request` stage and `pr_report` |
391
- | Create or edit reusable automation | a TypeScript workflow definition exported from `defineWorkflow(...).compile()` |
394
+ | Create or edit reusable automation | a TypeScript workflow definition exported from `workflow({...})` |
392
395
  | Track one-off work without saving a workflow file | direct `workflow({ task })`, `workflow({ tasks })`, or `workflow({ chain })` calls |
393
396
  | Make a workflow robust | design the stage graph, context handoffs, artifacts, validation gates, model fallbacks, and human approval points before coding |
394
397
 
@@ -594,20 +597,21 @@ Atomic discovers workflow definitions in this order:
594
597
 
595
598
  A workflow module may export one default workflow definition and/or named workflow definitions. Discovery checks the default export first, then named exports.
596
599
 
597
- Every runtime export of a discovered workflow file is validated as a workflow definition. A named export that is not a compiled definition — a widget factory, shared constant, or utility function — is rejected with an `INVALID_DEFINITION` discovery diagnostic (`export is not an object`), even when the module also has a valid default export (the valid workflow still loads; the diagnostic flags the extra export as skipped). Type-only exports (`export type` / `export interface`) are erased at runtime and never flagged.
600
+ Every runtime export of a discovered workflow file is validated as a workflow definition. A named export that is not a workflow definition — a widget factory, shared constant, or utility function — is rejected with an `INVALID_DEFINITION` discovery diagnostic (`export is not an object`), even when the module also has a valid default export (the valid workflow still loads; the diagnostic flags the extra export as skipped). Type-only exports (`export type` / `export interface`) are erased at runtime and never flagged.
598
601
 
599
602
  To co-locate reusable helpers with your workflows — for example a `ctx.ui.custom<T>` widget factory you want to import in tests without running the workflow — put them in a subdirectory and import them from the workflow file. Discovery scans only the top level of each workflow directory, so subdirectories such as `.atomic/workflows/lib/` are never treated as workflow modules:
600
603
 
601
604
  ```text
602
605
  .atomic/workflows/
603
- release-picker.ts # only runtime export: defineWorkflow(...).compile()
606
+ release-picker.ts # only runtime export: workflow({...})
604
607
  lib/
605
608
  table-selector.ts # widget factory + helpers; not scanned by discovery
606
609
  ```
607
610
 
608
611
  ```ts
609
612
  // .atomic/workflows/release-picker.ts
610
- import { defineWorkflow, Type } from "@bastani/workflows";
613
+ import { workflow } from "@bastani/workflows";
614
+ import { Type } from "typebox";
611
615
  import { tableSelectorFactory } from "./lib/table-selector.js";
612
616
  ```
613
617
 
@@ -973,7 +977,7 @@ workflow({
973
977
  })
974
978
  ```
975
979
 
976
- Direct mode supports top-level/default options and per-task options such as `context`, `forkFromSessionFile`, `model`, `fallbackModels`, `thinkingLevel`, `contextWindow`, `tools`, `noTools`, `customTools`, `bashPolicy`, `mcp`, `output`, `outputMode`, `reads`, `worktree`, `gitWorktreeDir`, `baseBranch`, `maxOutput`, `artifacts`, `sessionDir`, `cwd`, and `agentDir`. Direct chains also support `chainName`, `chainDir`, and `failFast`.
980
+ Direct mode supports top-level/default options and per-task options such as `context`, `forkFromSessionFile`, `model`, `fallbackModels`, `thinkingLevel`, `contextWindow`, `tools`, `noTools`, `customTools`, `mcp`, `output`, `outputMode`, `reads`, `worktree`, `gitWorktreeDir`, `baseBranch`, `maxOutput`, `artifacts`, `sessionDir`, `cwd`, and `agentDir`. Direct chains also support `chainName`, `chainDir`, and `failFast`.
977
981
 
978
982
  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.
979
983
 
@@ -991,17 +995,23 @@ Enable workflow fast mode deliberately for broad workflows: parallel fan-out and
991
995
 
992
996
  ## Writing a Workflow
993
997
 
994
- Workflow files are TypeScript modules that export a compiled definition:
998
+ Workflow files are TypeScript modules that export a workflow definition:
995
999
 
996
1000
  ```ts
997
- import { defineWorkflow, Type } from "@bastani/workflows";
998
-
999
- export default defineWorkflow("my-workflow")
1000
- .description("Short description shown in workflow listings.")
1001
- .input("prompt", Type.String({ description: "Task or question for the workflow." }))
1002
- .output("summary", Type.String({ description: "Synthesized findings and recommended next steps." }))
1003
- .output("reviewer_count", Type.Number({ description: "Number of parallel reviewers that ran." }))
1004
- .run(async (ctx) => {
1001
+ import { workflow } from "@bastani/workflows";
1002
+ import { Type } from "typebox";
1003
+
1004
+ export default workflow({
1005
+ name: "my-workflow",
1006
+ description: "Short description shown in workflow listings.",
1007
+ inputs: {
1008
+ prompt: Type.String({ description: "Task or question for the workflow." }),
1009
+ },
1010
+ outputs: {
1011
+ summary: Type.String({ description: "Synthesized findings and recommended next steps." }),
1012
+ reviewer_count: Type.Number({ description: "Number of parallel reviewers that ran." }),
1013
+ },
1014
+ run: async (ctx) => {
1005
1015
  const prompt = String(ctx.inputs.prompt);
1006
1016
 
1007
1017
  const scoutPath = ".atomic/workflows/runs/my-workflow/scout.md";
@@ -1047,20 +1057,41 @@ export default defineWorkflow("my-workflow")
1047
1057
  });
1048
1058
 
1049
1059
  return { summary: final.text, reviewer_count: reviews.length };
1050
- })
1051
- .compile();
1060
+ },
1061
+ });
1052
1062
  ```
1053
1063
 
1054
- Builder basics:
1064
+ Authoring basics:
1055
1065
 
1056
- - `defineWorkflow("name")` starts a builder; the name must be non-empty.
1066
+ - `workflow({ ... })` returns the workflow definition directly for discovery; there is no builder terminal step.
1057
1067
  - Workflow names normalize for lookup: trim, lowercase, convert whitespace/underscore to hyphen, remove other punctuation, and collapse hyphens.
1058
- - `.description(text)` sets the listing text.
1059
- - `.input(key, schema)` declares typed user inputs.
1060
- - `.worktreeFromInputs({ gitWorktreeDir, baseBranch })` optionally maps input names to workflow-wide reusable Git worktree defaults.
1061
- - `.output(key, schema)` declares typed outputs that parent workflows receive from `ctx.workflow(childWorkflow, ...)`.
1062
- - `.run(async (ctx) => { ... })` defines the workflow body.
1063
- - `.compile()` returns the workflow definition for discovery.
1068
+ - `description` sets the listing text.
1069
+ - `inputs` declares typed user inputs.
1070
+ - `worktreeFromInputs` optionally maps input names to workflow-wide reusable Git worktree defaults.
1071
+ - `outputs` declares typed outputs that parent workflows receive from `ctx.workflow(childWorkflow, ...)`.
1072
+ - `run: async (ctx) => { ... }` defines the workflow body.
1073
+
1074
+ Codemod-style migration from the removed builder API:
1075
+
1076
+ ```diff
1077
+ -import { defineWorkflow, Type } from "@bastani/workflows";
1078
+ +import { workflow } from "@bastani/workflows";
1079
+ +import { Type } from "typebox";
1080
+
1081
+ -export default defineWorkflow("review-changes")
1082
+ - .description("Run two reviewers and synthesize findings.")
1083
+ - .input("target", Type.String())
1084
+ - .output("decision", Type.String())
1085
+ - .run(async (ctx) => ({ decision: await review(ctx) }))
1086
+ - .compile();
1087
+ +export default workflow({
1088
+ + name: "review-changes",
1089
+ + description: "Run two reviewers and synthesize findings.",
1090
+ + inputs: { target: Type.String() },
1091
+ + outputs: { decision: Type.String() },
1092
+ + run: async (ctx) => ({ decision: await review(ctx) }),
1093
+ +});
1094
+ ```
1064
1095
 
1065
1096
  `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.
1066
1097
 
@@ -1068,12 +1099,17 @@ Author workflows to create at least one tracked stage by calling `ctx.task()`, `
1068
1099
 
1069
1100
  ### Early exit with `ctx.exit()`
1070
1101
 
1071
- Use `ctx.exit(options?)` when workflow code intentionally stops the current run from a helper, branch, loop, or precondition guard without classifying the run as failed. `ctx.exit()` throws an executor-owned control signal and is typed as `never`, so code after it is unreachable. In async `.run()` bodies, prefer `return ctx.exit(...)` when the exit is the only path so TypeScript can see the non-returning branch.
1102
+ Use `ctx.exit(options?)` when workflow code intentionally stops the current run from a helper, branch, loop, or precondition guard without classifying the run as failed. `ctx.exit()` throws an executor-owned control signal and is typed as `never`, so code after it is unreachable. In async `run` bodies, prefer `return ctx.exit(...)` when the exit is the only path so TypeScript can see the non-returning branch.
1072
1103
 
1073
1104
  ```ts
1074
- export default defineWorkflow("guarded-import")
1075
- .output("scanned", Type.Number())
1076
- .run(async (ctx) => {
1105
+ export default workflow({
1106
+ name: "guarded-import",
1107
+ description: "",
1108
+ inputs: {},
1109
+ outputs: {
1110
+ scanned: Type.Number(),
1111
+ },
1112
+ run: async (ctx) => {
1077
1113
  const files = await findCandidateFiles(ctx.cwd);
1078
1114
  if (files.length === 0) {
1079
1115
  return ctx.exit({
@@ -1085,11 +1121,11 @@ export default defineWorkflow("guarded-import")
1085
1121
 
1086
1122
  const review = await ctx.task("review", { prompt: `Review ${files.join(", ")}` });
1087
1123
  return { scanned: files.length };
1088
- })
1089
- .compile();
1124
+ },
1125
+ });
1090
1126
  ```
1091
1127
 
1092
- `ctx.exit()` accepts `status: "completed" | "skipped" | "cancelled" | "blocked"`; it never accepts `"failed"` or `"killed"` because thrown errors and external run-control keep those meanings. `status` defaults to `"completed"`. `reason` is persisted and shown in status surfaces, including the default `/workflow status` list and `/workflow status <runId>` detail, so do not put secrets in it. `outputs` may contain a partial subset of declared outputs; provided keys still must be declared with `.output(...)`, match their TypeBox schema, and be JSON-serializable. Missing required outputs are allowed only on the `ctx.exit(...)` path. Exited runs are terminal and not resumable; external `kill`, `pause`, and `interrupt` keep their existing behavior.
1128
+ `ctx.exit()` accepts `status: "completed" | "skipped" | "cancelled" | "blocked"`; it never accepts `"failed"` or `"killed"` because thrown errors and external run-control keep those meanings. `status` defaults to `"completed"`. `reason` is persisted and shown in status surfaces, including the default `/workflow status` list and `/workflow status <runId>` detail, so do not put secrets in it. `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. Missing required outputs are allowed only on the `ctx.exit(...)` path. Exited runs are terminal and not resumable; external `kill`, `pause`, and `interrupt` keep their existing behavior.
1093
1129
 
1094
1130
  The first selected `ctx.exit({ outputs })` snapshots its output payload synchronously by value before JavaScript `finally` blocks or cleanup callbacks can mutate the caller-owned object. The snapshot preserves undeclared keys and invalid values until post-cleanup validation, so deleting an undeclared key or changing an invalid value after `ctx.exit(...)` does not change the terminal validation result. If reading `status`, `reason`, or `outputs` options, or enumerating/copying the output snapshot itself, throws, Atomic still selects the exit signal, runs workflow-exit cleanup when feasible, and then records a terminal non-resumable authoring failure (`resumable: false`) if no external terminal control won first.
1095
1131
 
@@ -1122,7 +1158,7 @@ Workflow guidance should also cover the context passed between stages:
1122
1158
 
1123
1159
  ### Inputs
1124
1160
 
1125
- 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 do not import from `typebox` directly in workflow files. Workflow packages still declare `typebox` as a peer dependency so the SDK's shipped types resolve under `tsc` — see [Programmatic Usage](#programmatic-usage). Common input schemas map to picker kinds and accepted runtime values:
1161
+ Inputs are declared with TypeBox `Type.*` schemas in the `inputs` object. Import `Type` from `typebox` directly in workflow files. Workflow packages still declare `typebox` as a peer dependency so TypeBox schemas resolve under `tsc` — see [Programmatic Usage](#programmatic-usage). Common input schemas map to picker kinds and accepted runtime values:
1126
1162
 
1127
1163
  | TypeBox schema | Picker kind | Accepted runtime value |
1128
1164
  |---|---|---|
@@ -1136,21 +1172,26 @@ A `Type.Union([Type.Literal(...)])` of string literals is how a 'select' is expr
1136
1172
 
1137
1173
  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()`.
1138
1174
 
1139
- 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.
1175
+ In TypeScript workflow files, entries in `inputs` also narrow `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.
1140
1176
 
1141
1177
  ### Outputs
1142
1178
 
1143
- Workflow outputs are runtime contracts for completed workflow runs and for parent workflows that call a child with `ctx.workflow(childWorkflow, ...)`. A workflow normally returns a JSON-serializable object from `.run()`, and `.output(key, schema)` documents, validates, and exposes keys from that returned object. `ctx.exit({ outputs })` can expose a partial subset of the same declared output contract when the run intentionally stops early. Primitives, arrays, `null`, functions, symbols, `undefined` properties, `NaN`, and infinite numbers fail validation.
1179
+ Workflow outputs are runtime contracts for completed workflow runs and for parent workflows that call a child with `ctx.workflow(childWorkflow, ...)`. A workflow normally returns a JSON-serializable object from `run`, and entries in the `outputs` object document, validate, and expose keys from that returned object. `ctx.exit({ outputs })` can expose a partial subset of the same declared output contract when the run intentionally stops early. Primitives, arrays, `null`, functions, symbols, `undefined` properties, `NaN`, and infinite numbers fail validation.
1144
1180
 
1145
- **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`.
1181
+ **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 `outputs: { foo: schema }` and return `{ foo: value }`. `result` is not special and is never added for you: to expose `result`, declare it in `outputs` and return `{ result }` exactly like any other output. Returning a key that is not declared in `outputs` fails the run with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it in outputs or remove it from the run return`.
1146
1182
 
1147
- `.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:
1183
+ The `outputs` object 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:
1148
1184
 
1149
1185
  ```ts
1150
- export default defineWorkflow("review-with-summary")
1151
- .output("research_artifact", Type.String())
1152
- .output("review", Type.String())
1153
- .run(async (ctx) => {
1186
+ export default workflow({
1187
+ name: "review-with-summary",
1188
+ description: "Review with returned artifacts.",
1189
+ inputs: {},
1190
+ outputs: {
1191
+ research_artifact: Type.String(),
1192
+ review: Type.String(),
1193
+ },
1194
+ run: async (ctx) => {
1154
1195
  const researchPath = ".atomic/workflows/runs/review-with-summary/research.md";
1155
1196
  await ctx.task("research", {
1156
1197
  prompt: "Research the target.",
@@ -1166,13 +1207,13 @@ export default defineWorkflow("review-with-summary")
1166
1207
  research_artifact: researchPath,
1167
1208
  review: review.text,
1168
1209
  };
1169
- })
1170
- .compile();
1210
+ },
1211
+ });
1171
1212
  ```
1172
1213
 
1173
- 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` (for a child workflow call, `<name>` is the child's own name, and the parent surfaces the failure through the child-failure wrapper `atomic-workflows: child workflow "<childName>" (<displayName>) failed with status failed: ...`).
1214
+ There is no automatic `result` output. A workflow exposes exactly the keys it declares in `outputs` and returns from `run` — nothing more. To expose `result`, declare `outputs: { result: schema }` and return `{ result }` like any other output. If `run` returns a key that was never declared in `outputs`, the run fails with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it in outputs or remove it from the run return` (for a child workflow call, `<name>` is the child's own name, and the parent surfaces the failure through the child-failure wrapper `atomic-workflows: child workflow "<childName>" (<displayName>) failed with status failed: ...`).
1174
1215
 
1175
- 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.
1216
+ Outputs are declared with TypeBox `Type.*` schemas in the `outputs` object. **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.
1176
1217
 
1177
1218
  | TypeBox schema | Static type | Accepted runtime value |
1178
1219
  |---|---|---|
@@ -1188,36 +1229,38 @@ Outputs are declared with TypeBox `Type.*` schemas passed to `.output(key, schem
1188
1229
  | `Type.Object({}, { additionalProperties: true })` | `Record<string, unknown>` | any JSON object (last resort, dynamic only) |
1189
1230
  | `Type.Unknown()` / `Type.Any()` | `unknown` / `any` | any JSON-serializable value (last resort) |
1190
1231
 
1191
- 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.
1232
+ 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.
1192
1233
 
1193
1234
  #### Prefer precise schemas
1194
1235
 
1195
- 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:
1236
+ 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:
1196
1237
 
1197
1238
  ```ts
1198
1239
  // ❌ Loose: child.outputs.report is `unknown`; nothing checks the shape at runtime.
1199
- .output("report", Type.Unknown())
1240
+ outputs: {
1241
+ report: Type.Unknown(),
1242
+ }
1200
1243
 
1201
1244
  // ✅ Precise: child.outputs.report is `{ topic: string; score: number; tags: string[] }`,
1202
1245
  // and TypeBox rejects a returned value missing `score` or with a non-number `score`.
1203
- .output(
1204
- "report",
1205
- Type.Object({
1246
+ outputs: {
1247
+ report: Type.Object({
1206
1248
  topic: Type.String(),
1207
1249
  score: Type.Number(),
1208
1250
  tags: Type.Array(Type.String()),
1209
1251
  }),
1210
- )
1252
+ }
1211
1253
  ```
1212
1254
 
1213
- 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[]`.
1255
+ The same rule applies to inputs: `inputs: { counts: Type.Array(Type.Number()) }` makes `ctx.inputs.counts` a `number[]`, while `Type.Array(Type.Unknown())` only gives you `unknown[]`.
1214
1256
 
1215
1257
  #### `Type.Unsafe<T>()` escape hatch for deeply-nested values
1216
1258
 
1217
- When you already have a precise TypeScript type 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<MyType>(...)`. The **static** type becomes exactly `MyType` (so `ctx.inputs`, the `.run()` return, and `child.outputs` stay precise), while the **runtime** check stays as lenient as the wrapped schema. Use a `type` alias rather than an `interface` for the wrapped type — an `interface` has no implicit index signature, so it does not satisfy the serializable-output constraint:
1259
+ When you already have a precise TypeScript type 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<MyType>(...)`. The **static** type becomes exactly `MyType` (so `ctx.inputs`, the `run` return, and `child.outputs` stay precise), while the **runtime** check stays as lenient as the wrapped schema. Use a `type` alias rather than an `interface` for the wrapped type — an `interface` has no implicit index signature, so it does not satisfy the serializable-output constraint:
1218
1260
 
1219
1261
  ```ts
1220
- import { defineWorkflow, Type } from "@bastani/workflows";
1262
+ import { workflow } from "@bastani/workflows";
1263
+ import { Type } from "typebox";
1221
1264
 
1222
1265
  type ResearchPacket = {
1223
1266
  readonly topic: string;
@@ -1225,65 +1268,83 @@ type ResearchPacket = {
1225
1268
  readonly sections: readonly { readonly heading: string; readonly body: string }[];
1226
1269
  };
1227
1270
 
1228
- export default defineWorkflow("research-packet")
1229
- .input("topic", Type.String())
1230
- // Static type = ResearchPacket; runtime only checks "is a JSON object".
1231
- .output("packet", Type.Unsafe<ResearchPacket>(Type.Object({}, { additionalProperties: true })))
1232
- .run(async (ctx) => {
1271
+ export default workflow({
1272
+ name: "research-packet",
1273
+ description: "",
1274
+ inputs: {
1275
+ topic: Type.String(),
1276
+ },
1277
+ outputs: {
1278
+ packet: Type.Unsafe<ResearchPacket>(Type.Object({}, { additionalProperties: true })),
1279
+ },
1280
+ run: async (ctx) => {
1233
1281
  const packet: ResearchPacket = {
1234
1282
  topic: ctx.inputs.topic,
1235
1283
  score: 1,
1236
1284
  sections: [{ heading: "overview", body: "…" }],
1237
1285
  };
1238
1286
  return { packet }; // statically checked against ResearchPacket
1239
- })
1240
- .compile();
1287
+ },
1288
+ });
1241
1289
  ```
1242
1290
 
1243
1291
  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.
1244
1292
 
1245
1293
  #### How types flow
1246
1294
 
1247
- - `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`.
1248
- - 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).
1249
- - `ctx.workflow(child)` returns a discriminated child result. When `child.exited === false`, `child.outputs` is the child's full declared `.output(...)` contract; when `child.exited === true`, `child.outputs` is `Partial<TOutputs>` because child `ctx.exit({ outputs })` may intentionally provide only a subset.
1295
+ - `ctx.inputs.x` is `Static<inputSchema>` for the input you declared as `inputs: { x: schema }` — required and defaulted schemas are always present, and `Type.Optional(...)` adds `| undefined`.
1296
+ - 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).
1297
+ - `ctx.workflow(child)` returns a discriminated child result. When `child.exited === false`, `child.outputs` is the child's full declared `outputs` contract; when `child.exited === true`, `child.outputs` is `Partial<TOutputs>` because child `ctx.exit({ outputs })` may intentionally provide only a subset.
1250
1298
 
1251
1299
  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.
1252
1300
 
1253
1301
  ### Workflow Composition
1254
1302
 
1255
- 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(...)`.
1303
+ 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 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(...)`.
1256
1304
 
1257
- 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.
1305
+ For workflows intended to be called by parent workflows, declare every field a parent should rely on in the child workflow's `outputs` object, including `result`. No output exists without declaration: a child exposes exactly its declared outputs, and returning an undeclared key fails the child call.
1258
1306
 
1259
1307
  #### Compose with a user-defined workflow
1260
1308
 
1261
- User-defined workflows are ordinary TypeScript modules. Import the compiled definition with a relative module specifier and call it directly from the parent workflow:
1309
+ User-defined workflows are ordinary TypeScript modules. Import the workflow definition with a relative module specifier and call it directly from the parent workflow:
1262
1310
 
1263
1311
  ```ts
1264
1312
  // .atomic/workflows/shared-research.ts
1265
- import { defineWorkflow, Type } from "@bastani/workflows";
1266
-
1267
- export default defineWorkflow("shared-research")
1268
- .input("topic", Type.String())
1269
- .output("summary", Type.String({ description: "Research summary markdown." }))
1270
- // Precise element type: child.outputs.sources is `string[] | undefined`, not `unknown[]`.
1271
- .output("sources", Type.Optional(Type.Array(Type.String(), { description: "Source URLs and file references." })))
1272
- .run(async (ctx) => {
1313
+ import { workflow } from "@bastani/workflows";
1314
+ import { Type } from "typebox";
1315
+
1316
+ export default workflow({
1317
+ name: "shared-research",
1318
+ description: "",
1319
+ inputs: {
1320
+ topic: Type.String(),
1321
+ },
1322
+ outputs: {
1323
+ summary: Type.String({ description: "Research summary markdown." }),
1324
+ sources: Type.Optional(Type.Array(Type.String(), { description: "Source URLs and file references." })),
1325
+ },
1326
+ run: async (ctx) => {
1273
1327
  const result = await ctx.task("research", { prompt: `Research ${String(ctx.inputs.topic)}` });
1274
1328
  return { summary: result.text, sources: [] };
1275
- })
1276
- .compile();
1329
+ },
1330
+ });
1277
1331
 
1278
1332
  // .atomic/workflows/research-and-synthesize.ts
1279
- import { defineWorkflow, Type } from "@bastani/workflows";
1333
+ import { workflow } from "@bastani/workflows";
1334
+ import { Type } from "typebox";
1280
1335
  import sharedResearch from "./shared-research.js";
1281
1336
 
1282
- export default defineWorkflow("research-and-synthesize")
1283
- .input("topic", Type.String())
1284
- .output("final", Type.String({ description: "Synthesis built from the child research summary." }))
1285
- .output("child_run_id", Type.String({ description: "Run id of the nested shared-research child." }))
1286
- .run(async (ctx) => {
1337
+ export default workflow({
1338
+ name: "research-and-synthesize",
1339
+ description: "Run shared research and synthesize it.",
1340
+ inputs: {
1341
+ topic: Type.String(),
1342
+ },
1343
+ outputs: {
1344
+ final: Type.String({ description: "Synthesis built from the child research summary." }),
1345
+ child_run_id: Type.String({ description: "Run id of the nested shared-research child." }),
1346
+ },
1347
+ run: async (ctx) => {
1287
1348
  const child = await ctx.workflow(sharedResearch, {
1288
1349
  inputs: { topic: ctx.inputs.topic },
1289
1350
  stageName: "run shared research",
@@ -1296,13 +1357,13 @@ export default defineWorkflow("research-and-synthesize")
1296
1357
  prompt: `Synthesize:\n\n${String(child.outputs.summary)}`,
1297
1358
  });
1298
1359
  return { final: final.text, child_run_id: child.runId };
1299
- })
1300
- .compile();
1360
+ },
1361
+ });
1301
1362
  ```
1302
1363
 
1303
1364
  #### Compose with builtin workflows
1304
1365
 
1305
- 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:
1366
+ Builtin workflows are also exported as workflow definitions, so parent workflows can call them exactly like user-defined workflows. Use the barrel export when you want several builtins:
1306
1367
 
1307
1368
  ```ts
1308
1369
  import { deepResearchCodebase, goal, openClaudeDesign, ralph } from "@bastani/workflows/builtin";
@@ -1329,25 +1390,29 @@ Common builtin import targets:
1329
1390
  Example parent workflow that runs builtin deep research, then chooses either `goal` or `ralph` as the nested implementation runner:
1330
1391
 
1331
1392
  ```ts
1332
- import { defineWorkflow, Type } from "@bastani/workflows";
1393
+ import { workflow } from "@bastani/workflows";
1394
+ import { Type } from "typebox";
1333
1395
  import { deepResearchCodebase, goal, ralph } from "@bastani/workflows/builtin";
1334
1396
 
1335
- export default defineWorkflow("research-then-implement")
1336
- .input("topic", Type.String())
1337
- .input(
1338
- "runner",
1339
- Type.Union([Type.Literal("goal"), Type.Literal("ralph")], {
1397
+ export default workflow({
1398
+ name: "research-then-implement",
1399
+ description: "Run deep research, then dispatch to goal or Ralph.",
1400
+ inputs: {
1401
+ topic: Type.String(),
1402
+ runner: Type.Union([Type.Literal("goal"), Type.Literal("ralph")], {
1340
1403
  default: "goal",
1341
1404
  description: "Use goal for bounded changes or Ralph for broad research-first implementation work.",
1342
1405
  }),
1343
- )
1344
- .output("research_doc_path", Type.Optional(Type.String({ description: "Path to the deep-research document used for implementation." })))
1345
- .output("runner", Type.String({ description: "Which nested runner executed: \"goal\" or \"ralph\"." }))
1346
- // Genuinely dynamic: the nested runner (goal vs ralph) is chosen at runtime and
1347
- // each exposes a different declared output shape, so a loose object is appropriate here.
1348
- // When a child's outputs are known and fixed, declare the precise shape instead.
1349
- .output("implementation", Type.Object({}, { additionalProperties: true, description: "Declared outputs from the nested implementation workflow." }))
1350
- .run(async (ctx) => {
1406
+ },
1407
+ outputs: {
1408
+ research_doc_path: Type.Optional(Type.String({ description: "Path to the deep-research document used for implementation." })),
1409
+ runner: Type.String({ description: "Which nested runner executed: \"goal\" or \"ralph\"." }),
1410
+ // Genuinely dynamic: the nested runner (goal vs ralph) is chosen at runtime and
1411
+ // each exposes a different declared output shape, so a loose object is appropriate here.
1412
+ // When a child's outputs are known and fixed, declare the precise shape instead.
1413
+ implementation: Type.Object({}, { additionalProperties: true, description: "Declared outputs from the nested implementation workflow." }),
1414
+ },
1415
+ run: async (ctx) => {
1351
1416
  const topic = String(ctx.inputs.topic);
1352
1417
  const research = await ctx.workflow(deepResearchCodebase, {
1353
1418
  inputs: { prompt: topic, max_concurrency: 4 },
@@ -1392,11 +1457,11 @@ export default defineWorkflow("research-then-implement")
1392
1457
  runner: "goal",
1393
1458
  implementation: implementation.outputs,
1394
1459
  };
1395
- })
1396
- .compile();
1460
+ },
1461
+ });
1397
1462
  ```
1398
1463
 
1399
- 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`).
1464
+ Passing a workflow 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`).
1400
1465
 
1401
1466
  `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:
1402
1467
 
@@ -1413,7 +1478,7 @@ Passing a compiled definition directly to `ctx.workflow(...)` uses the child wor
1413
1478
 
1414
1479
  | Option | Meaning |
1415
1480
  |---|---|
1416
- | `inputs` | Values validated against the child workflow's `.input()` schema before the child starts. |
1481
+ | `inputs` | Values validated against the child workflow's `inputs` schema map before the child starts. |
1417
1482
  | `stageName` | Parent boundary stage label. Defaults to `workflow:<workflow-name>`. |
1418
1483
 
1419
1484
  Output exposure rules:
@@ -1428,9 +1493,9 @@ if (child.exited === true) {
1428
1493
  }
1429
1494
  ```
1430
1495
 
1431
- A child exposes exactly its declared outputs — the keys it declared with `.output(...)` and returned from `.run()` or supplied to `ctx.exit({ outputs })`. There are no implicit outputs and no raw return-object passthrough. If `.run()` returns a key that was not declared with `.output(...)`, the child run fails with `atomic-workflows: workflow "<childName>" returned undeclared output "<key>"; declare it with .output("<key>", Type....) or remove it from the .run() return`, and the parent surfaces that failure through the wrapper `atomic-workflows: child workflow "<childName>" (<displayName>) failed with status failed: ...`. A child with no declared outputs therefore exposes no outputs. Missing required outputs, schema type mismatches, and non-JSON-serializable returned values fail normal child completion before the parent continues; child `ctx.exit({ outputs })` allows missing required outputs but still validates every provided key and sets `child.exited === true` so parent code must handle the partial shape.
1496
+ A child exposes exactly its declared outputs — the keys declared in `outputs` and returned from `run` or supplied to `ctx.exit({ outputs })`. There are no implicit outputs and no raw return-object passthrough. If `run` returns a key that was not declared in `outputs`, the child run fails with `atomic-workflows: workflow "<childName>" returned undeclared output "<key>"; declare it in outputs or remove it from the run return`, and the parent surfaces that failure through the wrapper `atomic-workflows: child workflow "<childName>" (<displayName>) failed with status failed: ...`. A child with no declared outputs therefore exposes no outputs. Missing required outputs, schema type mismatches, and non-JSON-serializable returned values fail normal child completion before the parent continues; child `ctx.exit({ outputs })` allows missing required outputs but still validates every provided key and sets `child.exited === true` so parent code must handle the partial shape.
1432
1497
 
1433
- 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).
1498
+ Only 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 workflow definition, workflow discovery fails when loading that module. Nested child workflows count against `maxDepth` (default `4` total workflow levels).
1434
1499
 
1435
1500
  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. Skipped or failed boundaries do not retain child-edge metadata (`workflowChild` / `workflowChildRun`), and graph expansion ignores any stale non-completed boundary metadata from older persisted sessions instead of flattening an unrelated child run. Use `stageName` when the parent needs a more specific label, but keep it concise so the child summary remains readable in the graph.
1436
1501
 
@@ -1449,7 +1514,7 @@ Prefer high-level primitives because they create tracked graph nodes, provide co
1449
1514
  | Independent concurrent branches | `ctx.parallel(steps, options?)` |
1450
1515
  | Reusable child workflow | Call `ctx.workflow(workflowDefinition, options?)` |
1451
1516
  | Human input during a workflow run | `ctx.ui.input/confirm/select/editor/custom` |
1452
- | Pure deterministic computation, parsing, or file I/O | Plain TypeScript in `.run()` or helpers |
1517
+ | Pure deterministic computation, parsing, or file I/O | Plain TypeScript in `run` or helpers |
1453
1518
  | Fine-grained session control | `ctx.stage(name, options?)` |
1454
1519
 
1455
1520
  Use `previous` and `{previous}` for compact handoffs only. If no placeholder is present, the runtime appends context, so a large `previous` payload can silently bloat the next model prompt. Chain defaults are:
@@ -1483,7 +1548,7 @@ Common task/stage options include:
1483
1548
  - `context: "fresh" | "fork"`, `forkFromSessionFile`
1484
1549
  - `model`, `fallbackModels`, `thinkingLevel`, `scopedModels`, `modelRegistry` — `model` and each `fallbackModels` entry accept a `model_name:thinking_effort` reasoning suffix and an optional parenthesized context-window token such as `model (1m)` (see [Reasoning levels](#reasoning-levels) and [Context windows](#context-windows)); the standalone `thinkingLevel` is deprecated
1485
1550
  - `contextWindow`, `contextWindowStrict` — stage-wide context-window budget mapped to the SDK `createAgentSession` options of the same name (non-strict by default)
1486
- - `tools`, `noTools`, `customTools`, `mcp: { allow?: string[], deny?: string[] }`, `bashPolicy`
1551
+ - `tools`, `noTools`, `customTools`, `mcp: { allow?: string[], deny?: string[] }`
1487
1552
  - `schema` for a structured final answer from this workflow item
1488
1553
  - `output`, `outputMode`, `reads`, `worktree`, `gitWorktreeDir`, `baseBranch`, `maxOutput`, `artifacts`, `sessionDir`, `cwd`, `agentDir`
1489
1554
  - advanced host-supplied SDK seams: `authStorage`, `resourceLoader`, `sessionManager`, `settingsManager`, `sessionStartEvent`
@@ -1494,43 +1559,30 @@ Workflow stages inherit the active host session directory only when the host is
1494
1559
 
1495
1560
  `subagent` is available as a default workflow-stage tool, with the same default two-hop nesting budget as main chat: a workflow stage can launch a subagent, and that subagent can launch one nested subagent before the guard blocks further delegation. `tools` remains an allowlist across built-in tools and bundled extension tools; if you set `tools`, list every tool the stage should see. Explicitly listing tools such as `subagent`, `web_search`, `fetch_content`, or `intercom` exposes those tools to the stage, while `excludedTools` and `noTools: "all"` still win. The bundled subagent definitions from `@bastani/subagents` are available to the `subagent` tool in workflow stages; when a workflow is itself running inside a subagent child process, Atomic isolates stage resource discovery from the parent child-process flags so `subagent` remains available while workflow-stage nested-depth guards remain in force.
1496
1561
 
1497
- `bashPolicy` scopes the built-in `bash` tool for one stage or task. `tools` must still include `"bash"` (or leave it available by default); the policy only narrows command text after the shell tool is exposed. It supports exact strings, `{ prefix }`, command-string `{ glob }`, and `{ regex, flags? }` rules, `default: "allow" | "deny"` (default `"allow"`), `deny` precedence, and `match: "segments" | "whole"` (default `"segments"`). Omitting `bashPolicy`, passing `{}`, or passing a default-allow policy with no `allow`/`deny` rules (including empty arrays or match-only default-allow policies) preserves legacy behavior and does not parse commands; malformed policy shapes such as unknown top-level keys (`denny`, `extra`), non-array `allow`/`deny`, invalid rule objects, invalid regexes, invalid glob bracket ranges, or stateful `g`/`y` regex flags fail closed as `invalid-policy`. Segment mode checks each command in pipelines/chains/substitutions before execution, treats unquoted LF, CRLF, and bare CR as command separators, keeps non-leading Bash `>|` noclobber redirections inside the current command segment, and rejects reserved/compound shell heads, leading redirections, attached command-head redirections, and command heads that are not literal words.
1498
-
1499
- ```ts
1500
- await ctx.task("browser-preview", {
1501
- tools: ["bash"],
1502
- bashPolicy: {
1503
- default: "deny",
1504
- allow: [
1505
- "which playwright-cli",
1506
- { prefix: "playwright-cli open " },
1507
- { prefix: "playwright-cli snapshot" },
1508
- { prefix: "grep " },
1509
- ],
1510
- deny: [{ regex: "\\brm\\b" }],
1511
- },
1512
- prompt: "Open the preview with playwright-cli, then summarize the visible state.",
1513
- });
1514
- ```
1515
-
1516
- A command such as `playwright-cli snapshot | grep title` passes only when both segments are allowed, and `playwright-cli snapshot\nrm -rf /tmp/proof` cannot be hidden behind a `{ prefix: "playwright-cli " }` rule because the newline starts a new segment. Glob rules match command strings rather than filesystem path segments: `*` and `?` may span `/`, so `{ glob: "playwright-cli *" }` matches URLs and slash-bearing paths such as `playwright-cli http://localhost:3000`, `playwright-cli docs/index.html`, and `playwright-cli ./preview/output.html` while still matching the whole target rather than `echo playwright-cli ...`; escaped bracket-class metacharacters such as `\-`, `\^`, `\]`, `\[`, and `\\` stay literal, while malformed glob ranges such as `{ glob: "echo [z-a]" }` become `invalid-policy` denials. Segment mode accepts literal heads such as `grep`, `./script`, `/usr/bin/env`, `bun`, and `playwright-cli`, and treats non-leading `>|` as redirection syntax so `echo ok >|/tmp/out` stays one segment, but conservatively rejects reserved or compound heads (`coproc`, `if`, `for`, `while`, `case`, `{`, `}`, `!`), leading redirections (`>file cmd`, `2>file cmd`, `<file cmd`, `&>file cmd`, `&>>file cmd`, `>|file cmd`, `<&0 cmd`, `>&2 cmd`), redirections attached to the command-head word (`cmd>file`, `cmd>>file`, `cmd>|file`, `cmd2>file`, `cmd>&2`, `cmd</tmp/in`), leading environment assignments (`PATH=/tmp:$PATH playwright-cli snapshot`, `LD_PRELOAD=/tmp/x playwright-cli snapshot`, `FOO=bar`), dynamic heads such as `$cmd`, `${cmd}`, `r''m`, `r\m`, `~/bin/rm`, `r*m`, `{rm,echo}`, `r$(printf m)`, or backtick-built command names. A single denied, redirection-prefixed, attached-redirection, assignment-prefixed, dynamic, or unrecognized segment blocks the whole command with a model-readable tool error and no UI prompt, so the behavior works in headless workflow runs. Use `match: "whole"` only when raw-command matching is intentional.
1562
+ Workflow stages use the same upstream-compatible `bash` tool as normal Atomic sessions. If `bash` is enabled for a stage, commands run through the configured shell with the stage process permissions; workflow options no longer include a command-level allow/deny field for shell text. Use `tools`/`noTools` to expose or hide shell access, prefer narrower custom tools for repeatable operations, and run workflows inside a container, VM, or other sandbox when command allowlisting or stronger isolation is required.
1517
1563
 
1518
1564
  `gitWorktreeDir` selects a reusable Git worktree root for `ctx.stage`, `ctx.task`, `ctx.chain`, and `ctx.parallel`. If the path is missing, Atomic creates it with `git worktree add --detach <path> <baseBranch>`; if it exists, it must be a same-repository worktree root. The default stage cwd becomes the matching cwd inside the worktree and preserves the invoking repo-relative subdirectory. Explicit `cwd` still wins; relative `cwd` values resolve from the worktree cwd, while absolute `cwd` values are used as provided. `gitWorktreeDir` is mutually exclusive with `worktree: true`: use `gitWorktreeDir` for named/reusable worktrees and `worktree: true` for temporary direct-mode worktrees that are cleaned up after the run.
1519
1565
 
1520
- To bind user inputs to a workflow-wide worktree default, use the builder method:
1566
+ To bind user inputs to a workflow-wide worktree default, set `worktreeFromInputs` in `workflow({...})`:
1521
1567
 
1522
1568
  ```ts
1523
- export default defineWorkflow("safe-implementation")
1524
- .input("task", Type.String())
1525
- .input("git_worktree_dir", Type.String({ default: "" }))
1526
- .input("base_branch", Type.String({ default: "origin/main" }))
1527
- .worktreeFromInputs({ gitWorktreeDir: "git_worktree_dir", baseBranch: "base_branch" })
1528
- .output("result", Type.String({ description: "Implementation result text." }))
1529
- .run(async (ctx) => {
1569
+ export default workflow({
1570
+ name: "safe-implementation",
1571
+ description: "",
1572
+ inputs: {
1573
+ task: Type.String(),
1574
+ git_worktree_dir: Type.String({ default: "" }),
1575
+ base_branch: Type.String({ default: "origin/main" }),
1576
+ },
1577
+ outputs: {
1578
+ result: Type.String({ description: "Implementation result text." }),
1579
+ },
1580
+ worktreeFromInputs: { gitWorktreeDir: "git_worktree_dir", baseBranch: "base_branch" },
1581
+ run: async (ctx) => {
1530
1582
  const result = await ctx.task("implement", { task: String(ctx.inputs.task) });
1531
1583
  return { result: result.text };
1532
- })
1533
- .compile();
1584
+ },
1585
+ });
1534
1586
  ```
1535
1587
 
1536
1588
  For lower-level integrations, `@bastani/workflows` also exports `setupGitWorktree({ gitWorktreeDir, baseBranch, cwd })`, returning `{ worktreeRoot, cwd, repositoryRoot, created }` with the same validation, symlink-preserving path handling, and cwd-preservation behavior used by workflow stages.
@@ -1593,20 +1645,26 @@ The budget applies only to the candidate that carries the token; other primary a
1593
1645
  - `/workflow <name> key=value ...` for interactive named runs
1594
1646
  - `/workflow connect|attach|pause|interrupt|resume|status|inputs|reload` for live control, inspection, and rediscovery
1595
1647
  - the `workflow` tool for agent-initiated orchestration and direct one-off runs
1596
- Workflow definition files must export definitions produced by `defineWorkflow(...).compile()`. Keep non-workflow runtime helpers (widget factories, shared utilities) in a subdirectory the discovery scan ignores, such as `.atomic/workflows/lib/` — see [Workflow Locations](#workflow-locations). The former imperative object-form runner is not part of the public SDK, and authored workflow files cannot import `runWorkflow` from `@bastani/workflows`.
1648
+ Workflow definition files must export definitions produced by `workflow({...})`. Keep non-workflow runtime helpers (widget factories, shared utilities) in a subdirectory the discovery scan ignores, such as `.atomic/workflows/lib/` — see [Workflow Locations](#workflow-locations). The former imperative object-form runner is not part of the public SDK, and authored workflow files cannot import `runWorkflow` from `@bastani/workflows`.
1597
1649
 
1598
1650
  Standalone TypeScript workflow packages type-check the SDK import with no hand-authored `.d.ts`, no `declare module` shim, and no `tsconfig` `paths` alias. The SDK types ship with `@bastani/atomic`, so a workflow package depends only on `@bastani/atomic` (plus a `typebox` peer):
1599
1651
 
1600
1652
  ```ts
1601
- import { defineWorkflow, Type } from "@bastani/workflows";
1602
-
1603
- export default defineWorkflow("map-workflow-sdk")
1604
- .input("prompt", Type.String({ default: "map workflow sdk" }))
1605
- .run(async (ctx) => {
1653
+ import { workflow } from "@bastani/workflows";
1654
+ import { Type } from "typebox";
1655
+
1656
+ export default workflow({
1657
+ name: "map-workflow-sdk",
1658
+ description: "Map the workflow SDK.",
1659
+ inputs: {
1660
+ prompt: Type.String({ default: "map workflow sdk" }),
1661
+ },
1662
+ outputs: {},
1663
+ run: async (ctx) => {
1606
1664
  await ctx.task("map", { prompt: ctx.inputs.prompt });
1607
1665
  return {};
1608
- })
1609
- .compile();
1666
+ },
1667
+ });
1610
1668
  ```
1611
1669
 
1612
1670
  How those types resolve depends on what else the package imports:
@@ -1630,22 +1688,29 @@ How those types resolve depends on what else the package imports:
1630
1688
  /// <reference types="@bastani/atomic/workflows/ambient" />
1631
1689
  ```
1632
1690
 
1633
- Either form makes `import { defineWorkflow, Type } from "@bastani/workflows"` and the `@bastani/workflows/builtin/*` composition imports resolve under `tsc` (`moduleResolution: NodeNext`) with no hand-authored `.d.ts`, no `declare module` shim, and no `paths` alias. `@bastani/workflows` is not a separate npm package — its types ship with `@bastani/atomic` — so list both `@bastani/atomic` and `typebox` (the SDK's emitted types reference TypeBox) in `peerDependencies`. Runtime discovery and loading via `atomic.workflows` are unchanged: Atomic's loader still supplies the SDK when workflow files execute.
1691
+ Either form makes `import { workflow } from "@bastani/workflows"
1692
+ import { Type } from "typebox"` and the `@bastani/workflows/builtin/*` composition imports resolve under `tsc` (`moduleResolution: NodeNext`) with no hand-authored `.d.ts`, no `declare module` shim, and no `paths` alias. `@bastani/workflows` is not a separate npm package — its types ship with `@bastani/atomic` — so list both `@bastani/atomic` and `typebox` (workflow files import `Type` from `typebox`) in `peerDependencies`. Runtime discovery and loading via `atomic.workflows` are unchanged: Atomic's loader still supplies the SDK when workflow files execute.
1634
1693
 
1635
1694
  The `workflow` tool still supports direct one-off `task`, `tasks`, and `chain` modes. Direct chains support `chainName` for status/artifact grouping and `chainDir` as a shared directory for relative reads, outputs, and worktree diffs.
1636
1695
 
1637
1696
  Use `createRegistry()` when code needs to group definitions explicitly:
1638
1697
 
1639
1698
  ```ts
1640
- import { createRegistry, defineWorkflow, Type } from "@bastani/workflows";
1641
-
1642
- const alpha = defineWorkflow("alpha")
1643
- .output("text", Type.String({ description: "Alpha task output text." }))
1644
- .run(async (ctx) => {
1699
+ import { createRegistry, workflow } from "@bastani/workflows";
1700
+ import { Type } from "typebox";
1701
+
1702
+ const alpha = workflow({
1703
+ name: "alpha",
1704
+ description: "",
1705
+ inputs: {},
1706
+ outputs: {
1707
+ text: Type.String({ description: "Alpha task output text." }),
1708
+ },
1709
+ run: async (ctx) => {
1645
1710
  const result = await ctx.task("alpha", { prompt: "Run alpha." });
1646
1711
  return { text: result.text };
1647
- })
1648
- .compile();
1712
+ },
1713
+ });
1649
1714
 
1650
1715
  const registry = createRegistry().register(alpha);
1651
1716
  registry.names();
@@ -1816,7 +1881,7 @@ Before implementing or shipping a non-trivial workflow, answer these questions:
1816
1881
  - **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?
1817
1882
  - **Local stage contract:** Can this stage prompt stand alone with its current objective, inputs/artifacts, expected outputs, tools/checks, and success criteria, without unexplained workflow internals or future-stage assumptions?
1818
1883
  - **Information flow:** For every edge between stages, is `previous` enough, or should the handoff use structured returns, files, `reads`, `output`, or `outputMode`?
1819
- - **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?
1884
+ - **Output contract:** Which outputs should be declared in `outputs`, 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?
1820
1885
  - **Context size:** Can downstream stages succeed from the handoff alone? Should large transcripts, logs, or research bundles be summarized or saved as artifacts?
1821
1886
  - **Control flow:** Should the workflow use `ctx.chain`, `ctx.parallel`, `ctx.ui`, bounded loops, `failFast`, or `fallbackModels`?
1822
1887
  - **User experience:** Are stage names readable in status and graph views? Is the final output compact? Are important artifacts saved with stable paths?
@@ -1830,8 +1895,8 @@ Good workflows are information-flow systems, not just prompt sequences. Keep sta
1830
1895
  - Do not guess input keys; inspect with `inputs` or `get` first.
1831
1896
  - Do not call `create`, `update`, or `delete` on the workflow tool; definitions are code-authored.
1832
1897
  - Do not use legacy workflow tool fields like `agent`, `stage`, or run-control `name`.
1833
- - 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.
1834
- - 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()`.
1898
+ - Do not pass strings such as `"goal"` or path objects to `ctx.workflow(...)`; import the workflow definition from `@bastani/workflows/builtin` or another TypeScript module first.
1899
+ - 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`.
1835
1900
  - 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`.
1836
1901
  - Do not expect named workflow runs to block the chat turn; they are background tasks.
1837
1902
  - Do not call `kill` when the user asks to interrupt or pause resumably.