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

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 (223) hide show
  1. package/CHANGELOG.md +29 -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 +19 -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-ledger.ts +2 -0
  16. package/dist/builtin/workflows/builtin/goal-reports.ts +5 -0
  17. package/dist/builtin/workflows/builtin/goal-runner.ts +17 -20
  18. package/dist/builtin/workflows/builtin/goal-types.ts +2 -0
  19. package/dist/builtin/workflows/builtin/goal.d.ts +1 -0
  20. package/dist/builtin/workflows/builtin/goal.ts +40 -44
  21. package/dist/builtin/workflows/builtin/index.d.ts +1 -0
  22. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +16 -17
  23. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +1 -0
  24. package/dist/builtin/workflows/builtin/open-claude-design.ts +42 -50
  25. package/dist/builtin/workflows/builtin/prompt-refinement.ts +102 -0
  26. package/dist/builtin/workflows/builtin/ralph-core.ts +6 -4
  27. package/dist/builtin/workflows/builtin/ralph-runner.ts +22 -24
  28. package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
  29. package/dist/builtin/workflows/builtin/ralph.ts +46 -41
  30. package/dist/builtin/workflows/package.json +2 -2
  31. package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
  32. package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +217 -0
  33. package/dist/builtin/workflows/src/authoring/workflow.ts +184 -0
  34. package/dist/builtin/workflows/src/authoring.d.ts +14 -66
  35. package/dist/builtin/workflows/src/engine/graph-inference.ts +100 -0
  36. package/dist/builtin/workflows/src/engine/options.ts +40 -0
  37. package/dist/builtin/workflows/src/engine/primitives/chain.ts +29 -0
  38. package/dist/builtin/workflows/src/engine/primitives/exit.ts +2 -0
  39. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +47 -0
  40. package/dist/builtin/workflows/src/engine/primitives/task.ts +108 -0
  41. package/dist/builtin/workflows/src/engine/primitives/ui.ts +41 -0
  42. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +159 -0
  43. package/dist/builtin/workflows/src/engine/replay.ts +8 -0
  44. package/dist/builtin/workflows/src/engine/run.ts +356 -0
  45. package/dist/builtin/workflows/src/engine/runtime.ts +160 -0
  46. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +9 -3
  47. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +3 -1
  48. package/dist/builtin/workflows/src/extension/workflow-schema.ts +0 -18
  49. package/dist/builtin/workflows/src/index.ts +0 -2
  50. package/dist/builtin/workflows/src/runs/background/runner.ts +6 -3
  51. package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +3 -3
  52. package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +4 -4
  53. package/dist/builtin/workflows/src/runs/foreground/executor-child-workflow.ts +1 -158
  54. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +1 -1
  55. package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +2 -2
  56. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +1 -1
  57. package/dist/builtin/workflows/src/runs/foreground/executor-run.ts +1 -359
  58. package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +1 -1
  59. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +2 -5
  60. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +12 -4
  61. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +4 -3
  62. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +9 -2
  63. package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +2 -132
  64. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -2
  65. package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +2 -100
  66. package/dist/builtin/workflows/src/sdk-surface.ts +6 -9
  67. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +9 -3
  68. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +17 -3
  69. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +3 -33
  70. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +9 -81
  71. package/dist/builtin/workflows/src/shared/types.ts +25 -8
  72. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +49 -0
  73. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +84 -0
  74. package/dist/builtin/workflows/src/workflows/registry.ts +7 -3
  75. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  76. package/dist/core/agent-session-auto-compaction.js +6 -1
  77. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  78. package/dist/core/agent-session-bash.d.ts.map +1 -1
  79. package/dist/core/agent-session-bash.js +0 -5
  80. package/dist/core/agent-session-bash.js.map +1 -1
  81. package/dist/core/agent-session-methods.d.ts +0 -2
  82. package/dist/core/agent-session-methods.d.ts.map +1 -1
  83. package/dist/core/agent-session-methods.js.map +1 -1
  84. package/dist/core/agent-session-services.d.ts +0 -1
  85. package/dist/core/agent-session-services.d.ts.map +1 -1
  86. package/dist/core/agent-session-services.js +0 -1
  87. package/dist/core/agent-session-services.js.map +1 -1
  88. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  89. package/dist/core/agent-session-tool-registry.js +0 -2
  90. package/dist/core/agent-session-tool-registry.js.map +1 -1
  91. package/dist/core/agent-session-types.d.ts +0 -2
  92. package/dist/core/agent-session-types.d.ts.map +1 -1
  93. package/dist/core/agent-session-types.js.map +1 -1
  94. package/dist/core/agent-session.d.ts +0 -2
  95. package/dist/core/agent-session.d.ts.map +1 -1
  96. package/dist/core/agent-session.js +0 -1
  97. package/dist/core/agent-session.js.map +1 -1
  98. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  99. package/dist/core/atomic-guide-command.js +1 -1
  100. package/dist/core/atomic-guide-command.js.map +1 -1
  101. package/dist/core/extensions/loader-core.d.ts +1 -3
  102. package/dist/core/extensions/loader-core.d.ts.map +1 -1
  103. package/dist/core/extensions/loader-core.js +13 -6
  104. package/dist/core/extensions/loader-core.js.map +1 -1
  105. package/dist/core/extensions/loader-virtual-modules.d.ts +7 -1
  106. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  107. package/dist/core/extensions/loader-virtual-modules.js +34 -2
  108. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  109. package/dist/core/extensions/loader.d.ts +2 -1
  110. package/dist/core/extensions/loader.d.ts.map +1 -1
  111. package/dist/core/extensions/loader.js +2 -1
  112. package/dist/core/extensions/loader.js.map +1 -1
  113. package/dist/core/index.d.ts +0 -1
  114. package/dist/core/index.d.ts.map +1 -1
  115. package/dist/core/index.js +0 -1
  116. package/dist/core/index.js.map +1 -1
  117. package/dist/core/model-registry-builtins.d.ts.map +1 -1
  118. package/dist/core/model-registry-builtins.js +6 -0
  119. package/dist/core/model-registry-builtins.js.map +1 -1
  120. package/dist/core/model-registry-schemas.d.ts +65 -13
  121. package/dist/core/model-registry-schemas.d.ts.map +1 -1
  122. package/dist/core/model-registry-schemas.js +10 -0
  123. package/dist/core/model-registry-schemas.js.map +1 -1
  124. package/dist/core/resource-loader-core.d.ts +1 -0
  125. package/dist/core/resource-loader-core.d.ts.map +1 -1
  126. package/dist/core/resource-loader-core.js +2 -0
  127. package/dist/core/resource-loader-core.js.map +1 -1
  128. package/dist/core/resource-loader-extensions.d.ts.map +1 -1
  129. package/dist/core/resource-loader-extensions.js +3 -3
  130. package/dist/core/resource-loader-extensions.js.map +1 -1
  131. package/dist/core/resource-loader-internals.d.ts +1 -0
  132. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  133. package/dist/core/resource-loader-internals.js.map +1 -1
  134. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  135. package/dist/core/resource-loader-reload.js +6 -2
  136. package/dist/core/resource-loader-reload.js.map +1 -1
  137. package/dist/core/sdk-exports.d.ts +1 -1
  138. package/dist/core/sdk-exports.d.ts.map +1 -1
  139. package/dist/core/sdk-exports.js.map +1 -1
  140. package/dist/core/sdk-types.d.ts +0 -3
  141. package/dist/core/sdk-types.d.ts.map +1 -1
  142. package/dist/core/sdk-types.js.map +1 -1
  143. package/dist/core/sdk.d.ts.map +1 -1
  144. package/dist/core/sdk.js +0 -1
  145. package/dist/core/sdk.js.map +1 -1
  146. package/dist/core/session-manager-history.d.ts.map +1 -1
  147. package/dist/core/session-manager-history.js +2 -1
  148. package/dist/core/session-manager-history.js.map +1 -1
  149. package/dist/core/system-prompt.d.ts.map +1 -1
  150. package/dist/core/system-prompt.js +0 -1
  151. package/dist/core/system-prompt.js.map +1 -1
  152. package/dist/core/tools/bash.d.ts +0 -5
  153. package/dist/core/tools/bash.d.ts.map +1 -1
  154. package/dist/core/tools/bash.js +10 -11
  155. package/dist/core/tools/bash.js.map +1 -1
  156. package/dist/core/tools/edit-diff-preserve.d.ts +18 -0
  157. package/dist/core/tools/edit-diff-preserve.d.ts.map +1 -0
  158. package/dist/core/tools/edit-diff-preserve.js +85 -0
  159. package/dist/core/tools/edit-diff-preserve.js.map +1 -0
  160. package/dist/core/tools/edit-diff.d.ts +3 -2
  161. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  162. package/dist/core/tools/edit-diff.js +15 -18
  163. package/dist/core/tools/edit-diff.js.map +1 -1
  164. package/dist/core/tools/index.d.ts +0 -1
  165. package/dist/core/tools/index.d.ts.map +1 -1
  166. package/dist/core/tools/index.js +0 -1
  167. package/dist/core/tools/index.js.map +1 -1
  168. package/dist/index.d.ts +2 -2
  169. package/dist/index.d.ts.map +1 -1
  170. package/dist/index.js +1 -1
  171. package/dist/index.js.map +1 -1
  172. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  173. package/dist/modes/interactive/components/model-selector.js +2 -2
  174. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  175. package/dist/modes/interactive/model-search.d.ts +5 -0
  176. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  177. package/dist/modes/interactive/model-search.js +9 -0
  178. package/dist/modes/interactive/model-search.js.map +1 -1
  179. package/dist/utils/shell.d.ts +1 -0
  180. package/dist/utils/shell.d.ts.map +1 -1
  181. package/dist/utils/shell.js +12 -5
  182. package/dist/utils/shell.js.map +1 -1
  183. package/docs/custom-provider.md +4 -3
  184. package/docs/models.md +3 -2
  185. package/docs/packages.md +2 -2
  186. package/docs/quickstart.md +1 -1
  187. package/docs/sdk.md +2 -40
  188. package/docs/security.md +1 -1
  189. package/docs/workflows.md +991 -176
  190. package/package.json +5 -5
  191. package/dist/builtin/workflows/src/workflows/define-workflow.ts +0 -277
  192. package/dist/core/tools/bash-policy-compile.d.ts +0 -5
  193. package/dist/core/tools/bash-policy-compile.d.ts.map +0 -1
  194. package/dist/core/tools/bash-policy-compile.js +0 -241
  195. package/dist/core/tools/bash-policy-compile.js.map +0 -1
  196. package/dist/core/tools/bash-policy-evaluate.d.ts +0 -3
  197. package/dist/core/tools/bash-policy-evaluate.d.ts.map +0 -1
  198. package/dist/core/tools/bash-policy-evaluate.js +0 -92
  199. package/dist/core/tools/bash-policy-evaluate.js.map +0 -1
  200. package/dist/core/tools/bash-policy-format.d.ts +0 -5
  201. package/dist/core/tools/bash-policy-format.d.ts.map +0 -1
  202. package/dist/core/tools/bash-policy-format.js +0 -49
  203. package/dist/core/tools/bash-policy-format.js.map +0 -1
  204. package/dist/core/tools/bash-policy-parser.d.ts +0 -4
  205. package/dist/core/tools/bash-policy-parser.d.ts.map +0 -1
  206. package/dist/core/tools/bash-policy-parser.js +0 -155
  207. package/dist/core/tools/bash-policy-parser.js.map +0 -1
  208. package/dist/core/tools/bash-policy-segment.d.ts +0 -3
  209. package/dist/core/tools/bash-policy-segment.d.ts.map +0 -1
  210. package/dist/core/tools/bash-policy-segment.js +0 -275
  211. package/dist/core/tools/bash-policy-segment.js.map +0 -1
  212. package/dist/core/tools/bash-policy-shell.d.ts +0 -11
  213. package/dist/core/tools/bash-policy-shell.d.ts.map +0 -1
  214. package/dist/core/tools/bash-policy-shell.js +0 -267
  215. package/dist/core/tools/bash-policy-shell.js.map +0 -1
  216. package/dist/core/tools/bash-policy-types.d.ts +0 -146
  217. package/dist/core/tools/bash-policy-types.d.ts.map +0 -1
  218. package/dist/core/tools/bash-policy-types.js +0 -2
  219. package/dist/core/tools/bash-policy-types.js.map +0 -1
  220. package/dist/core/tools/bash-policy.d.ts +0 -6
  221. package/dist/core/tools/bash-policy.d.ts.map +0 -1
  222. package/dist/core/tools/bash-policy.js +0 -5
  223. package/dist/core/tools/bash-policy.js.map +0 -1
@@ -51,19 +51,25 @@ When a stage human-in-the-loop prompt is answered from the workflow TUI/stage ch
51
51
  ### Example 1 — Single task
52
52
 
53
53
  ```typescript
54
- import { defineWorkflow, Type } from "@bastani/workflows";
55
-
56
- export default defineWorkflow("summarize-pr")
57
- .description("Summarize a pull request in one task.")
58
- .input("pr_url", Type.String({ description: "URL of the pull request to summarize." }))
59
- .output("summary", Type.String({ description: "One-task summary of the pull request." }))
60
- .run(async (ctx) => {
54
+ import { workflow } from "@bastani/workflows";
55
+ import { Type } from "typebox";
56
+
57
+ export default workflow({
58
+ name: "summarize-pr",
59
+ description: "Summarize a pull request in one task.",
60
+ inputs: {
61
+ pr_url: Type.String({ description: "URL of the pull request to summarize." }),
62
+ },
63
+ outputs: {
64
+ summary: Type.String({ description: "One-task summary of the pull request." }),
65
+ },
66
+ run: async (ctx) => {
61
67
  const summary = await ctx.task("summarize", {
62
68
  prompt: `Summarize the pull request at ${String(ctx.inputs.pr_url)} clearly and concisely.`,
63
69
  });
64
70
  return { summary: summary.text };
65
- })
66
- .compile();
71
+ },
72
+ });
67
73
  ```
68
74
 
69
75
  ### Example 2 — Parallel fan-out with `ctx.parallel`
@@ -71,13 +77,19 @@ export default defineWorkflow("summarize-pr")
71
77
  Use `ctx.parallel` for independent specialist work. The aggregator receives the specialist outputs through typed task results instead of manual stage/session plumbing. The runtime snapshots the parent graph frontier when the fan-out starts, so every branch shares the same parents even when limited `concurrency` queues later branches or an earlier sibling fails with `failFast: false`.
72
78
 
73
79
  ```typescript
74
- import { defineWorkflow, Type } from "@bastani/workflows";
75
-
76
- export default defineWorkflow("parallel-research")
77
- .description("Scout three parallel specialists → aggregator.")
78
- .input("topic", Type.String({ description: "Research topic." }))
79
- .output("summary", Type.String({ description: "Synthesized summary of the specialist reports." }))
80
- .run(async (ctx) => {
80
+ import { workflow } from "@bastani/workflows";
81
+ import { Type } from "typebox";
82
+
83
+ export default workflow({
84
+ name: "parallel-research",
85
+ description: "Scout three parallel specialists → aggregator.",
86
+ inputs: {
87
+ topic: Type.String({ description: "Research topic." }),
88
+ },
89
+ outputs: {
90
+ summary: Type.String({ description: "Synthesized summary of the specialist reports." }),
91
+ },
92
+ run: async (ctx) => {
81
93
  const topic = ctx.inputs.topic;
82
94
 
83
95
  const reportPaths = {
@@ -103,21 +115,27 @@ export default defineWorkflow("parallel-research")
103
115
  reads: Object.values(reportPaths),
104
116
  });
105
117
  return { summary: summary.text };
106
- })
107
- .compile();
118
+ },
119
+ });
108
120
  ```
109
121
 
110
122
  ### Example 3 — Human-in-the-loop (HIL)
111
123
 
112
124
  ```typescript
113
- import { defineWorkflow, Type } from "@bastani/workflows";
114
-
115
- export default defineWorkflow("review-and-merge")
116
- .description("Plan a change, ask for human approval, then execute.")
117
- .input("task", Type.String({ description: "What to implement." }))
118
- .output("status", Type.Optional(Type.String({ description: "Set to \"cancelled\" when the human rejects the plan." })))
119
- .output("result", Type.Optional(Type.String({ description: "Implementation result when the plan is approved." })))
120
- .run(async (ctx) => {
125
+ import { workflow } from "@bastani/workflows";
126
+ import { Type } from "typebox";
127
+
128
+ export default workflow({
129
+ name: "review-and-merge",
130
+ description: "Plan a change, ask for human approval, then execute.",
131
+ inputs: {
132
+ task: Type.String({ description: "What to implement." }),
133
+ },
134
+ outputs: {
135
+ status: Type.Optional(Type.String({ description: "Set to \"cancelled\" when the human rejects the plan." })),
136
+ result: Type.Optional(Type.String({ description: "Implementation result when the plan is approved." })),
137
+ },
138
+ run: async (ctx) => {
121
139
  const planPath = ".atomic/workflows/runs/review-and-merge/plan.md";
122
140
  const plan = await ctx.task("planner", {
123
141
  prompt: `Create a concise implementation plan for: ${String(ctx.inputs.task)}`,
@@ -135,27 +153,34 @@ export default defineWorkflow("review-and-merge")
135
153
  reads: [planPath],
136
154
  });
137
155
  return { result: result.text };
138
- })
139
- .compile();
156
+ },
157
+ });
140
158
  ```
141
159
 
142
- Human input is runtime-only: call `ctx.ui.input`, `ctx.ui.confirm`, `ctx.ui.select`, `ctx.ui.editor`, or `ctx.ui.custom<T>` at the point where the workflow actually needs a decision. No builder-level declaration is required or supported.
160
+ Human input is runtime-only: call `ctx.ui.input`, `ctx.ui.confirm`, `ctx.ui.select`, `ctx.ui.editor`, or `ctx.ui.custom<T>` at the point where the workflow actually needs a decision. No declaration-time HIL marker is required or supported.
143
161
 
144
162
  `ctx.ui.custom<T>(factory, options?)` mounts an arbitrary focused TUI component in the attached workflow graph/stage UI and resolves with the value passed to `done(value)`. The factory uses the same real TUI/theme/keybinding/component types as Atomic extension `ctx.ui.custom`. Use `options.label` for a safe display-only graph/status label and `options.replayIdentity` (do not include secrets) when the widget's semantics can change without the callsite changing; label text is not part of replay identity. Custom widget prompts require an interactive workflow graph; they are not answerable through non-TUI `workflow send` in iteration 1. Inline graph rendering is supported; `overlay: true` is rejected clearly because nested workflow graph overlays are not safely supported yet.
145
163
 
146
164
  ### Example 4 — Compose workflows
147
165
 
148
- Prefer regular TypeScript module imports for reusable child workflows: import the compiled workflow definition, then pass it directly to `ctx.workflow(workflowDefinition, options)`.
166
+ Prefer regular TypeScript module imports for reusable child workflows: import the workflow definition returned by `workflow({...})`, then pass it directly to `ctx.workflow(workflowDefinition, options)`.
149
167
 
150
168
  ```typescript
151
- import { defineWorkflow, Type } from "@bastani/workflows";
169
+ import { workflow } from "@bastani/workflows";
170
+ import { Type } from "typebox";
152
171
  import goal from "@bastani/workflows/builtin/goal";
153
172
  import sharedResearch from "./shared-research.js";
154
173
 
155
- export default defineWorkflow("research-and-synthesize")
156
- .input("topic", Type.String())
157
- .output("final", Type.String({ description: "Synthesis of the child research and implementation." }))
158
- .run(async (ctx) => {
174
+ export default workflow({
175
+ name: "research-and-synthesize",
176
+ description: "Run shared research, implement from it, then synthesize the result.",
177
+ inputs: {
178
+ topic: Type.String(),
179
+ },
180
+ outputs: {
181
+ final: Type.String({ description: "Synthesis of the child research and implementation." }),
182
+ },
183
+ run: async (ctx) => {
159
184
  const child = await ctx.workflow(sharedResearch, {
160
185
  inputs: { topic: ctx.inputs.topic },
161
186
  });
@@ -168,31 +193,38 @@ export default defineWorkflow("research-and-synthesize")
168
193
  prompt: `Synthesize this research and implementation:\n\n${String(child.outputs.summary)}\n\n${String(implementation.outputs.result)}`,
169
194
  });
170
195
  return { final: final.text };
171
- })
172
- .compile();
196
+ },
197
+ });
173
198
  ```
174
199
 
175
- The child executes as a nested workflow behind a parent boundary stage named `workflow:<workflow-name>` by default, but user-facing status and graph views flatten it into the parent run. In practice it should feel like inlining the child workflow code: child stages, HIL prompt nodes, and deeper imported children appear in one expanded parent graph, while implementation-owned child run ids stay hidden from top-level `/workflow status` lists. The child still has a run id internally so the graph can attach to, pause, interrupt, resume, or kill live child stages correctly. Inputs are strictly validated against the child workflow before it starts: unknown keys, missing required values, type mismatches, and invalid `select` choices fail before the child body runs. The parent receives the child's declared `.output(...)` outputs on `child.outputs` after those outputs pass their declared runtime type checks.
200
+ The child executes as a nested workflow behind a parent boundary stage named `workflow:<workflow-name>` by default, but user-facing status and graph views flatten it into the parent run. In practice it should feel like inlining the child workflow code: child stages, HIL prompt nodes, and deeper imported children appear in one expanded parent graph, while implementation-owned child run ids stay hidden from top-level `/workflow status` lists. The child still has a run id internally so the graph can attach to, pause, interrupt, resume, or kill live child stages correctly. Inputs are strictly validated against the child workflow before it starts: unknown keys, missing required values, type mismatches, and invalid `select` choices fail before the child body runs. The parent receives the child's declared `outputs` on `child.outputs` after those outputs pass their declared runtime type checks.
176
201
 
177
- For workflows intended to be called as children, declare `.output(...)` for every non-default field a parent should rely on. `.output(...)` is only the schema/contract: use normal TypeScript in `.run()` to gather values from any stage/task/child workflow and return those keys.
202
+ For workflows intended to be called as children, declare an `outputs` entry for every non-default field a parent should rely on. `outputs` is only the schema/contract: use normal TypeScript in `run()` to gather values from any stage/task/child workflow and return those keys.
178
203
 
179
- **Return convention:** child 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.summary`, the child workflow's `.run()` must both declare `.output("summary", schema)` and return `{ summary }`. `result` is not special and is never added for you: to expose `result`, declare `.output("result", schema)` and return `{ result }` 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` (the child-call variant reports `... child "<alias>" returned undeclared output "<key>" from "<childName>"`).
204
+ **Return convention:** child 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.summary`, the child workflow's `outputs` map must declare `summary` and `run()` must return `{ summary }`. `result` is not special and is never added for you: to expose `result`, declare `outputs: { result: schema }` and return `{ result }` 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` (the child-call variant reports `... child "<alias>" returned undeclared output "<key>" from "<childName>"`).
180
205
 
181
- A reusable child module can simply default-export a compiled workflow:
206
+ A reusable child module can simply default-export a workflow definition:
182
207
 
183
208
  ```typescript
184
- import { defineWorkflow, Type } from "@bastani/workflows";
185
-
186
- export default defineWorkflow("shared-research")
187
- .input("topic", Type.String())
188
- .output("summary", Type.String())
189
- .run(async (ctx) => {
209
+ import { workflow } from "@bastani/workflows";
210
+ import { Type } from "typebox";
211
+
212
+ export default workflow({
213
+ name: "shared-research",
214
+ description: "Reusable research helper.",
215
+ inputs: {
216
+ topic: Type.String(),
217
+ },
218
+ outputs: {
219
+ summary: Type.String(),
220
+ },
221
+ run: async (ctx) => {
190
222
  const report = await ctx.task("research", {
191
223
  prompt: `Research: ${String(ctx.inputs.topic)}`,
192
224
  });
193
225
  return { summary: report.text };
194
- })
195
- .compile();
226
+ },
227
+ });
196
228
  ```
197
229
 
198
230
  Builtin workflows are also callable as modules for reuse:
@@ -203,34 +235,40 @@ import goalWorkflow from "@bastani/workflows/builtin/goal";
203
235
  import openClaudeDesignWorkflow from "@bastani/workflows/builtin/open-claude-design";
204
236
  ```
205
237
 
206
- Only compiled workflow definitions can be passed to `ctx.workflow(...)`; registry names, strings, and path objects are intentionally not supported for child workflow calls. Missing or invalid module imports fail when the workflow file itself is loaded. A parent receives the child's declared `.output(...)` outputs from the child `.run()` return object. Missing required outputs, schema type mismatches, returning an undeclared output, and non-JSON-serializable returned child values fail the child call before the parent continues.
238
+ Only `workflow({...})` definitions can be passed to `ctx.workflow(...)`; registry names, strings, and path objects are intentionally not supported for child workflow calls. Missing or invalid module imports fail when the workflow file itself is loaded. A parent receives the child's declared `outputs` from the child `run()` return object. Missing required outputs, schema type mismatches, returning an undeclared output, and non-JSON-serializable returned child values fail the child call before the parent continues.
207
239
 
208
240
  ### Reusable Git worktrees
209
241
 
210
242
  Use `gitWorktreeDir` when a workflow should run in a reusable Git worktree instead of the invoking checkout. The executor creates the worktree if it is missing, reuses it when it already exists as a same-repository worktree root, defaults workflow `ctx.cwd` to the matching path inside that worktree for `worktreeFromInputs`, and defaults stage/task `cwd` to that worktree path.
211
243
 
212
244
  ```typescript
213
- import { defineWorkflow, Type } from "@bastani/workflows";
214
-
215
- export default defineWorkflow("safe-implementation")
216
- .description("Run implementation stages in a reusable worktree.")
217
- .input("task", Type.String())
218
- .input("worktree", Type.String({ default: "" }))
219
- .input("base_branch", Type.String({ default: "origin/main" }))
220
- .worktreeFromInputs({
245
+ import { workflow } from "@bastani/workflows";
246
+ import { Type } from "typebox";
247
+
248
+ export default workflow({
249
+ name: "safe-implementation",
250
+ description: "Run implementation stages in a reusable worktree.",
251
+ inputs: {
252
+ task: Type.String(),
253
+ worktree: Type.String({ default: "" }),
254
+ base_branch: Type.String({ default: "origin/main" }),
255
+ },
256
+ worktreeFromInputs: {
221
257
  gitWorktreeDir: "worktree",
222
258
  baseBranch: "base_branch",
223
- })
224
- .output("result", Type.String({ description: "Implementation result text." }))
225
- .run(async (ctx) => {
259
+ },
260
+ outputs: {
261
+ result: Type.String({ description: "Implementation result text." }),
262
+ },
263
+ run: async (ctx) => {
226
264
  const result = await ctx.task("implement", {
227
265
  task: String(ctx.inputs.task),
228
266
  // No cwd needed: when `worktree` is non-empty, this task runs from the
229
267
  // corresponding cwd inside that reusable Git worktree.
230
268
  });
231
269
  return { result: result.text };
232
- })
233
- .compile();
270
+ },
271
+ });
234
272
  ```
235
273
 
236
274
  You can also pass worktree options per stage/task or as shared chain/parallel defaults:
@@ -289,16 +327,22 @@ Atomic registers the canonical `structured_output` tool only for schema-enabled
289
327
  Stages and high-level task helpers can retry transient provider/model failures with an ordered `fallbackModels` list. The primary `model` is tried first, then each fallback, and finally the current Atomic-selected model when available. Fallbacks are only used for retryable model/provider failures such as rate limits, quota/auth/provider outages, unavailable models, network timeouts, and 5xx errors — ordinary tool, shell, validation, cancellation, and workflow-code failures are not retried.
290
328
 
291
329
  ```typescript
292
- import { defineWorkflow, Type } from "@bastani/workflows";
293
-
294
- export default defineWorkflow("fallback-review")
295
- .description("Review with a model fallback chain.")
296
- .input("topic", Type.String())
297
- .output("review", Type.String({ description: "Reviewer output text." }))
298
- .output("model", Type.Optional(Type.String({ description: "Model that produced the review." })))
299
- .output("attemptedModels", Type.Optional(Type.Array(Type.String(), { description: "Models tried, in fallback order." })))
300
- .output("modelAttempts", Type.Optional(Type.Array(Type.Unknown(), { description: "Per-attempt model fallback details." })))
301
- .run(async (ctx) => {
330
+ import { workflow } from "@bastani/workflows";
331
+ import { Type } from "typebox";
332
+
333
+ export default workflow({
334
+ name: "fallback-review",
335
+ description: "Review with a model fallback chain.",
336
+ inputs: {
337
+ topic: Type.String(),
338
+ },
339
+ outputs: {
340
+ review: Type.String({ description: "Reviewer output text." }),
341
+ model: Type.Optional(Type.String({ description: "Model that produced the review." })),
342
+ attemptedModels: Type.Optional(Type.Array(Type.String(), { description: "Models tried, in fallback order." })),
343
+ modelAttempts: Type.Optional(Type.Array(Type.Unknown(), { description: "Per-attempt model fallback details." })),
344
+ },
345
+ run: async (ctx) => {
302
346
  const review = await ctx.task("reviewer", {
303
347
  prompt: `Review this topic: ${String(ctx.inputs.topic)}`,
304
348
  model: "anthropic/claude-sonnet-4",
@@ -311,8 +355,8 @@ export default defineWorkflow("fallback-review")
311
355
  attemptedModels: review.attemptedModels ? [...review.attemptedModels] : undefined,
312
356
  modelAttempts: review.modelAttempts ? [...review.modelAttempts] : undefined,
313
357
  };
314
- })
315
- .compile();
358
+ },
359
+ });
316
360
  ```
317
361
 
318
362
  Direct helpers and workflow tool direct modes can set task-local fallbacks or a top-level default:
@@ -331,11 +375,11 @@ When pi exposes its model registry, workflow runs validate user-specified `model
331
375
  ### `createRegistry` — grouping workflows
332
376
 
333
377
  ```typescript
334
- import { createRegistry, defineWorkflow } from "@bastani/workflows";
378
+ import { createRegistry, workflow } from "@bastani/workflows";
335
379
 
336
- const alpha = defineWorkflow("alpha").run(async () => ({})).compile();
337
- const beta = defineWorkflow("beta").run(async () => ({})).compile();
338
- const gamma = defineWorkflow("gamma").run(async () => ({})).compile();
380
+ const alpha = workflow({ name: "alpha", description: "", outputs: {}, run: async () => ({}) });
381
+ const beta = workflow({ name: "beta", description: "", outputs: {}, run: async () => ({}) });
382
+ const gamma = workflow({ name: "gamma", description: "", outputs: {}, run: async () => ({}) });
339
383
 
340
384
  const registry = createRegistry()
341
385
  .register(alpha)
@@ -343,28 +387,35 @@ const registry = createRegistry()
343
387
  .merge(createRegistry().register(gamma));
344
388
 
345
389
  registry.names(); // ["alpha", "beta", "gamma"]
346
- registry.all(); // compiled workflow definitions
347
- registry.get("alpha"); // compiled workflow definition | undefined
390
+ registry.all(); // workflow definitions
391
+ registry.get("alpha"); // workflow definition | undefined
348
392
  ```
349
393
 
350
394
  ### Declaring inputs and outputs with TypeBox
351
395
 
352
- Inputs and outputs are declared with [TypeBox](https://github.com/sinclairzx81/typebox) schemas. Import `Type` from `@bastani/workflows` (alongside `defineWorkflow`) and pass a schema to `.input(key, schema)` / `.output(key, schema)`. The builder infers precise static types for `ctx.inputs`, the `.run()` return, and `child.outputs` from those schemas, and the runtime validates against them with TypeBox `Value`.
396
+ Inputs and outputs are declared with [TypeBox](https://github.com/sinclairzx81/typebox) schemas. Import `workflow` from `@bastani/workflows`, import `Type` from `typebox`, and put schemas in the `inputs` and `outputs` maps. `workflow({...})` infers precise static types for `ctx.inputs`, the `run()` return, and `child.outputs` from those schemas, and the runtime validates against them with TypeBox `Value`.
353
397
 
354
398
  **Prefer precise schemas.** A precise schema (`Type.Object({ topic: Type.String(), score: Type.Number() })`, `Type.Array(Type.String())`) gives consumers a precise `Static<>` type and makes runtime validation enforce the real shape. Reserve `Type.Unknown()`, `Type.Any()`, `Type.Array(Type.Unknown())`, and `Type.Object({}, { additionalProperties: true })` for genuinely dynamic data whose shape you cannot know ahead of time.
355
399
 
356
400
  ```typescript
357
- import { defineWorkflow, Type } from "@bastani/workflows";
358
-
359
- defineWorkflow("example")
360
- .input("prompt", Type.String({ description: "Required free-text input." })) // required key -> ctx.inputs.prompt: string
361
- .input("ref", Type.Optional(Type.String())) // optional key -> string | undefined
362
- .input("count", Type.Number({ default: 2 })) // defaulted -> required key, ctx.inputs.count: number
363
- .input("flavor", Type.Union([Type.Literal("a"), Type.Literal("b")], { default: "a" })) // select
364
- .output("packet", Type.Object({ topic: Type.String(), score: Type.Number() })) // required object output
365
- .output("note", Type.Optional(Type.String())) // optional output
366
- .run(async (ctx) => ({ packet: { topic: ctx.inputs.prompt, score: ctx.inputs.count } }))
367
- .compile();
401
+ import { workflow } from "@bastani/workflows";
402
+ import { Type } from "typebox";
403
+
404
+ workflow({
405
+ name: "example",
406
+ description: "",
407
+ inputs: {
408
+ prompt: Type.String({ description: "Required free-text input." }), // required key -> ctx.inputs.prompt: string
409
+ ref: Type.Optional(Type.String()), // optional key -> string | undefined
410
+ count: Type.Number({ default: 2 }), // defaulted -> required key, ctx.inputs.count: number
411
+ flavor: Type.Union([Type.Literal("a"), Type.Literal("b")], { default: "a" }), // select
412
+ },
413
+ outputs: {
414
+ packet: Type.Object({ topic: Type.String(), score: Type.Number() }), // required object output
415
+ note: Type.Optional(Type.String()), // optional output
416
+ },
417
+ run: async (ctx) => ({ packet: { topic: ctx.inputs.prompt, score: ctx.inputs.count } }),
418
+ });
368
419
  ```
369
420
 
370
421
  `Static` and `TSchema` are also re-exported from `@bastani/workflows` for advanced typing.
@@ -380,11 +431,11 @@ defineWorkflow("example")
380
431
  | `Type.Union([Type.Literal("a"), Type.Literal("b")], { default? })` | `select` | Enumerated string choices |
381
432
  | `Type.Optional(schema)` | — | Makes the key optional (`T \| undefined`) |
382
433
 
383
- A required input is any schema that is neither `Type.Optional(...)` nor carries a `default` (a defaulted input is a required key at the type level but optional for the caller to provide). Input validation is strict for named workflow runs and `ctx.workflow(...)` child calls: Atomic rejects unknown keys, missing required values, values whose runtime type does not match the declared schema, and `select` values outside the declared literals. It does not coerce strings like `"3"` into numbers; pass JSON numbers (`count=3`) for `Type.Number()`. `.input(...)` narrows `ctx.inputs` for intellisense: required/defaulted strings are `string`, numbers are `number`, booleans are `boolean`, selects are the literal union, and `Type.Optional(...)` inputs include `undefined`.
434
+ A required input is any schema that is neither `Type.Optional(...)` nor carries a `default` (a defaulted input is a required key at the type level but optional for the caller to provide). Input validation is strict for named workflow runs and `ctx.workflow(...)` child calls: Atomic rejects unknown keys, missing required values, values whose runtime type does not match the declared schema, and `select` values outside the declared literals. It does not coerce strings like `"3"` into numbers; pass JSON numbers (`count=3`) for `Type.Number()`. The `inputs` map narrows `ctx.inputs` for intellisense: required/defaulted strings are `string`, numbers are `number`, booleans are `boolean`, selects are the literal union, and `Type.Optional(...)` inputs include `undefined`.
384
435
 
385
436
  ### Output types
386
437
 
387
- Declare outputs with `.output(key, schema)` when a workflow result should be part of its runtime contract, especially when another workflow will call it as a child. Lead with the most precise schema you can express — the loose rows at the bottom are last resorts for genuinely dynamic data.
438
+ Declare outputs in `outputs` when a workflow result should be part of its runtime contract, especially when another workflow will call it as a child. Lead with the most precise schema you can express — the loose rows at the bottom are last resorts for genuinely dynamic data.
388
439
 
389
440
  | Schema | Runtime value accepted |
390
441
  | --------------------------------------------------- | --------------------------------------------------- |
@@ -400,7 +451,7 @@ Declare outputs with `.output(key, schema)` when a workflow result should be par
400
451
  | `Type.Object({}, { additionalProperties: true })` | any JSON object (last resort, dynamic only) |
401
452
  | `Type.Unknown()` / `Type.Any()` | any JSON-serializable value (last resort) |
402
453
 
403
- Wrap an output schema in `Type.Optional(...)` to make the key optional; an un-wrapped output schema is required. `.run()` must return a JSON-serializable object. Functions, symbols, `undefined` properties, `NaN`, infinite numbers, and non-plain objects (e.g. `Date`) fail validation. Declared outputs are validated before a workflow is marked completed. A required output that is missing fails with `missing output "<key>"`, and a type mismatch fails with `output "<key>" expected <kind>, got <actual>`. A workflow exposes exactly the outputs it declares with `.output(...)`: there is no automatic `result` output, and returning a key that was not declared 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`. To expose `result`, declare `.output("result", schema)` and return `{ result }`. Child output replay still performs a structured-clone safety check after JSON validation so completed child boundaries can be replayed.
454
+ Wrap an output schema in `Type.Optional(...)` to make the key optional; an un-wrapped output schema is required. `run()` must return a JSON-serializable object. Functions, symbols, `undefined` properties, `NaN`, infinite numbers, and non-plain objects (e.g. `Date`) fail validation. Declared outputs are validated before a workflow is marked completed. A required output that is missing fails with `missing output "<key>"`, and a type mismatch fails with `output "<key>" expected <kind>, got <actual>`. A workflow exposes exactly the outputs it declares in `outputs`: there is no automatic `result` output, and returning a key that was not declared fails the run with `atomic-workflows: workflow "<name>" returned undeclared output "<key>"; declare it in outputs or remove it from the run() return`. To expose `result`, declare `outputs: { result: schema }` and return `{ result }`. Child output replay still performs a structured-clone safety check after JSON validation so completed child boundaries can be replayed.
404
455
 
405
456
  #### Why precise schemas
406
457
 
@@ -408,23 +459,26 @@ A loose schema types the value as `unknown`/`Record<string, unknown>` everywhere
408
459
 
409
460
  ```typescript
410
461
  // ❌ Loose: child.outputs.report is `unknown`; runtime only checks "is JSON".
411
- .output("report", Type.Unknown())
462
+ outputs: { report: Type.Unknown() }
412
463
 
413
464
  // ✅ Precise: child.outputs.report is `{ topic: string; score: number; tags: string[] }`,
414
465
  // and TypeBox rejects a returned value missing `score` or with a non-number `score`.
415
- .output("report", Type.Object({
416
- topic: Type.String(),
417
- score: Type.Number(),
418
- tags: Type.Array(Type.String()),
419
- }))
466
+ outputs: {
467
+ report: Type.Object({
468
+ topic: Type.String(),
469
+ score: Type.Number(),
470
+ tags: Type.Array(Type.String()),
471
+ }),
472
+ }
420
473
  ```
421
474
 
422
475
  #### `Type.Unsafe<T>()` escape hatch
423
476
 
424
- When you already have a precise TypeScript type for a deeply-nested serializable value and don't want to hand-write the full 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** 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:
477
+ When you already have a precise TypeScript type for a deeply-nested serializable value and don't want to hand-write the full 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** 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:
425
478
 
426
479
  ```typescript
427
- import { defineWorkflow, Type } from "@bastani/workflows";
480
+ import { workflow } from "@bastani/workflows";
481
+ import { Type } from "typebox";
428
482
 
429
483
  type ResearchPacket = {
430
484
  readonly topic: string;
@@ -432,19 +486,25 @@ type ResearchPacket = {
432
486
  readonly sections: readonly { readonly heading: string; readonly body: string }[];
433
487
  };
434
488
 
435
- export default defineWorkflow("research-packet")
436
- .input("topic", Type.String())
437
- // Static type = ResearchPacket; runtime only checks "is a JSON object".
438
- .output("packet", Type.Unsafe<ResearchPacket>(Type.Object({}, { additionalProperties: true })))
439
- .run(async (ctx) => {
489
+ export default workflow({
490
+ name: "research-packet",
491
+ description: "Return a typed research packet.",
492
+ inputs: {
493
+ topic: Type.String(),
494
+ },
495
+ outputs: {
496
+ // Static type = ResearchPacket; runtime only checks "is a JSON object".
497
+ packet: Type.Unsafe<ResearchPacket>(Type.Object({}, { additionalProperties: true })),
498
+ },
499
+ run: async (ctx) => {
440
500
  const packet: ResearchPacket = {
441
501
  topic: ctx.inputs.topic,
442
502
  score: 1,
443
503
  sections: [{ heading: "overview", body: "…" }],
444
504
  };
445
505
  return { packet };
446
- })
447
- .compile();
506
+ },
507
+ });
448
508
  ```
449
509
 
450
510
  Tradeoff: `Type.Unsafe<T>()` does not deeply validate at runtime — it trusts the produced value matches `T`. Use it when the producing code already guarantees the shape; 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 loose `additionalProperties` objects for genuinely dynamic data.
@@ -452,8 +512,8 @@ Tradeoff: `Type.Unsafe<T>()` does not deeply validate at runtime — it trusts t
452
512
  #### How types flow
453
513
 
454
514
  - `ctx.inputs.x` is `Static<inputSchema>` — required/defaulted inputs are present, `Type.Optional(...)` adds `| undefined`.
455
- - The `.run()` return is checked against declared outputs at compile time (missing-required and wrong-type are TypeScript errors) and at runtime via TypeBox `Value` (undeclared keys rejected, declared shape enforced recursively).
456
- - `ctx.workflow(child).outputs` is typed from the child's declared `.output(...)` contract, so a parent reads precisely-typed child outputs without casting.
515
+ - The `run()` return is checked against declared outputs at compile time (missing-required, wrong-type, and undeclared-output keys are TypeScript errors for object-form `workflow({...})`) and at runtime via TypeBox `Value` (undeclared keys rejected, declared shape enforced recursively).
516
+ - `ctx.workflow(child).outputs` is typed from the child's declared `outputs` contract, so a parent reads precisely-typed child outputs without casting.
457
517
 
458
518
  `Static` and `TSchema` are re-exported from `@bastani/workflows`; use `Static<typeof schema>` when you need a schema's inferred TypeScript type directly.
459
519
 
@@ -544,19 +604,26 @@ For interactive use, run workflows through `/workflow <name> [key=value ...]` or
544
604
 
545
605
  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 the HIL example above into a non-interactive session, it can pass dispatch and then fail when execution reaches the prompt with an error such as `atomic-workflows: interactive ctx.ui.confirm is unavailable in headless (non-interactive) mode; run the workflow in interactive mode or remove the interactive prompt from this stage` (the primitive name varies, including `ctx.ui.custom`). Run those workflows interactively, or guard/remove runtime `ctx.ui.*` calls before using headless mode.
546
606
 
547
- For library or package authoring, define reusable workflows with the builder and export the compiled definition. Hand-written objects with `__piWorkflow: true` are rejected by discovery and composition; `defineWorkflow(...).compile()` is the public authoring surface. Standalone TypeScript workflow packages can import `defineWorkflow` and `Type` from `@bastani/workflows` directly with no local `.d.ts` file or `declare module` shim. The former imperative `runWorkflow` object-form API is removed; use compiled workflow definitions with the exported `run()` / registry helpers for programmatic execution.
607
+ For library or package authoring, define reusable workflows with `workflow({...})` and export the returned definition. Hand-written objects with `__piWorkflow: true` are rejected by discovery and composition; `workflow({...})` is the public authoring surface. Standalone TypeScript workflow packages import `workflow` from `@bastani/workflows` and `Type` from `typebox` directly with no local `.d.ts` file or `declare module` shim. Migration from the removed builder API is mechanical: move `.description(...)` to `description`, `.input(key, schema)` calls into `inputs`, `.output(key, schema)` calls into `outputs`, `.worktreeFromInputs(...)` to `worktreeFromInputs`, and the `.run(fn)` callback to `run: fn`; delete `.compile()`. The former imperative `runWorkflow` object-form API is removed; use workflow definitions with the exported `run()` / registry helpers for programmatic execution.
548
608
 
549
609
  ```ts
550
- import { defineWorkflow, Type } from "@bastani/workflows";
551
-
552
- export default defineWorkflow("audit-auth")
553
- .input("prompt", Type.String({ default: "Investigate the auth module" }))
554
- .output("summary", Type.String())
555
- .run(async (ctx) => {
610
+ import { workflow } from "@bastani/workflows";
611
+ import { Type } from "typebox";
612
+
613
+ export default workflow({
614
+ name: "audit-auth",
615
+ description: "Audit the authentication module.",
616
+ inputs: {
617
+ prompt: Type.String({ default: "Investigate the auth module" }),
618
+ },
619
+ outputs: {
620
+ summary: Type.String(),
621
+ },
622
+ run: async (ctx) => {
556
623
  const result = await ctx.task("audit", { prompt: ctx.inputs.prompt });
557
624
  return { summary: result.text };
558
- })
559
- .compile();
625
+ },
626
+ });
560
627
  ```
561
628
 
562
629
  The `workflow` tool still supports direct one-off `task`, `tasks`, and `chain` modes for agent-initiated orchestration. Those direct modes are runtime tool inputs, not workflow definition files.
@@ -8,37 +8,40 @@
8
8
  * ctx.parallel(), and ctx.chain().
9
9
  */
10
10
 
11
- import { defineWorkflow } from "../src/workflows/define-workflow.js";
12
11
  import { Type } from "typebox";
12
+ import { workflow } from "../src/authoring/workflow.js";
13
13
  import {
14
14
  DEFAULT_MAX_CONCURRENCY,
15
15
  DEFAULT_MAX_PARTITIONS,
16
16
  } from "./deep-research-codebase-utils.js";
17
17
  import { runDeepResearchCodebase } from "./deep-research-codebase-runner.js";
18
18
 
19
- export default defineWorkflow("deep-research-codebase")
20
- .description(
21
- "Scout + research-history chain → parallel specialist waves → aggregator for deep codebase research.",
22
- )
23
- .input("prompt", Type.String({ description: "Research question or investigation focus for the codebase." }))
24
- .input("max_partitions", Type.Number({
25
- default: DEFAULT_MAX_PARTITIONS,
26
- description:
27
- "Maximum number of codebase partitions to explore in parallel. Actual partitions scale by one per 10K LoC, capped by this value.",
28
- }))
29
- .input("max_concurrency", Type.Number({
30
- default: DEFAULT_MAX_CONCURRENCY,
31
- description: "Maximum number of workflow stages to run concurrently during deep research.",
32
- }))
33
- .output("result", Type.Optional(Type.String({ description: "Final Markdown research report text, matching findings." })))
34
- .output("findings", Type.Optional(Type.String({ description: "Final Markdown research report text." })))
35
- .output("research_doc_path", Type.Optional(Type.String({ description: "Public report path under research/<date>-<topic>.md." })))
36
- .output("artifact_dir", Type.Optional(Type.String({ description: "Hidden per-run handoff directory containing deep-research artifacts." })))
37
- .output("manifest_path", Type.Optional(Type.String({ description: "Manifest JSON path inside the hidden artifact directory." })))
38
- .output("partitions", Type.Optional(Type.Array(Type.String(), { description: "Codebase partitions the specialists explored." })))
39
- .output("explorer_count", Type.Optional(Type.Number({ description: "Number of partition explorer groups used." })))
40
- .output("specialist_count", Type.Optional(Type.Number({ description: "Number of specialist stages run across the research waves." })))
41
- .output("max_concurrency", Type.Optional(Type.Number({ description: "Concurrency limit used for the run." })))
42
- .output("history", Type.Optional(Type.String({ description: "Prior-research/history overview included in the final synthesis." })))
43
- .run(runDeepResearchCodebase)
44
- .compile();
19
+ export default workflow({
20
+ name: "deep-research-codebase",
21
+ description: "Scout + research-history chain → parallel specialist waves → aggregator for deep codebase research.",
22
+ inputs: {
23
+ prompt: Type.String({ description: "Research question or investigation focus for the codebase." }),
24
+ max_partitions: Type.Number({
25
+ default: DEFAULT_MAX_PARTITIONS,
26
+ description:
27
+ "Maximum number of codebase partitions to explore in parallel. Actual partitions scale by one per 10K LoC, capped by this value.",
28
+ }),
29
+ max_concurrency: Type.Number({
30
+ default: DEFAULT_MAX_CONCURRENCY,
31
+ description: "Maximum number of workflow stages to run concurrently during deep research.",
32
+ }),
33
+ },
34
+ outputs: {
35
+ result: Type.Optional(Type.String({ description: "Final Markdown research report text, matching findings." })),
36
+ findings: Type.Optional(Type.String({ description: "Final Markdown research report text." })),
37
+ research_doc_path: Type.Optional(Type.String({ description: "Public report path under research/<date>-<topic>.md." })),
38
+ artifact_dir: Type.Optional(Type.String({ description: "Hidden per-run handoff directory containing deep-research artifacts." })),
39
+ manifest_path: Type.Optional(Type.String({ description: "Manifest JSON path inside the hidden artifact directory." })),
40
+ partitions: Type.Optional(Type.Array(Type.String(), { description: "Codebase partitions the specialists explored." })),
41
+ explorer_count: Type.Optional(Type.Number({ description: "Number of partition explorer groups used." })),
42
+ specialist_count: Type.Optional(Type.Number({ description: "Number of specialist stages run across the research waves." })),
43
+ max_concurrency: Type.Optional(Type.Number({ description: "Concurrency limit used for the run." })),
44
+ history: Type.Optional(Type.String({ description: "Prior-research/history overview included in the final synthesis." })),
45
+ },
46
+ run: async (ctx) => await runDeepResearchCodebase(ctx),
47
+ });
@@ -21,12 +21,14 @@ export function appendLifecycleEvent(
21
21
 
22
22
  export async function createGoalLedger(
23
23
  objective: string,
24
+ originalObjective?: string,
24
25
  ): Promise<{ ledger: GoalLedger; ledgerPath: string; artifactDir: string }> {
25
26
  const artifactDir = await mkdtemp(join(tmpdir(), "atomic-goal-runner-"));
26
27
  const now = new Date().toISOString();
27
28
  const ledger: GoalLedger = {
28
29
  goal_id: randomUUID(),
29
30
  objective,
31
+ ...(originalObjective === undefined || originalObjective === objective ? {} : { original_objective: originalObjective }),
30
32
  status: "active",
31
33
  turns: 0,
32
34
  created_at: now,
@@ -35,6 +35,11 @@ export function renderFinalReport(
35
35
  "## Objective",
36
36
  ledger.objective,
37
37
  "",
38
+ ...(ledger.original_objective === undefined ? [] : [
39
+ "## Original objective (before prompt refinement)",
40
+ ledger.original_objective,
41
+ "",
42
+ ]),
38
43
  "## Final status",
39
44
  ledger.status,
40
45
  "",