@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.
- package/CHANGELOG.md +23 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/README.md +189 -122
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +30 -27
- package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
- package/dist/builtin/workflows/builtin/goal.ts +39 -44
- package/dist/builtin/workflows/builtin/index.d.ts +1 -0
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +16 -17
- package/dist/builtin/workflows/builtin/open-claude-design.d.ts +1 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +42 -50
- package/dist/builtin/workflows/builtin/ralph.ts +44 -41
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
- package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +217 -0
- package/dist/builtin/workflows/src/authoring/workflow.ts +184 -0
- package/dist/builtin/workflows/src/authoring.d.ts +14 -66
- package/dist/builtin/workflows/src/engine/graph-inference.ts +100 -0
- package/dist/builtin/workflows/src/engine/options.ts +40 -0
- package/dist/builtin/workflows/src/engine/primitives/chain.ts +29 -0
- package/dist/builtin/workflows/src/engine/primitives/exit.ts +2 -0
- package/dist/builtin/workflows/src/engine/primitives/parallel.ts +47 -0
- package/dist/builtin/workflows/src/engine/primitives/task.ts +108 -0
- package/dist/builtin/workflows/src/engine/primitives/ui.ts +41 -0
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +159 -0
- package/dist/builtin/workflows/src/engine/replay.ts +8 -0
- package/dist/builtin/workflows/src/engine/run.ts +356 -0
- package/dist/builtin/workflows/src/engine/runtime.ts +160 -0
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +9 -3
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +0 -18
- package/dist/builtin/workflows/src/index.ts +0 -2
- package/dist/builtin/workflows/src/runs/background/runner.ts +6 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +3 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +4 -4
- package/dist/builtin/workflows/src/runs/foreground/executor-child-workflow.ts +1 -158
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +2 -2
- package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-run.ts +1 -359
- package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +2 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +12 -4
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +4 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +9 -2
- package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +2 -132
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -2
- package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +2 -100
- package/dist/builtin/workflows/src/sdk-surface.ts +6 -9
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +9 -3
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +17 -3
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +3 -33
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +9 -81
- package/dist/builtin/workflows/src/shared/types.ts +25 -8
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +49 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +84 -0
- package/dist/builtin/workflows/src/workflows/registry.ts +7 -3
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +6 -1
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-bash.d.ts.map +1 -1
- package/dist/core/agent-session-bash.js +0 -5
- package/dist/core/agent-session-bash.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +0 -2
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +0 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +0 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +0 -2
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +0 -2
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +0 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +0 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +1 -1
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/extensions/loader-core.d.ts +1 -3
- package/dist/core/extensions/loader-core.d.ts.map +1 -1
- package/dist/core/extensions/loader-core.js +13 -6
- package/dist/core/extensions/loader-core.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts +7 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +34 -2
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +2 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +0 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-registry-builtins.d.ts.map +1 -1
- package/dist/core/model-registry-builtins.js +6 -0
- package/dist/core/model-registry-builtins.js.map +1 -1
- package/dist/core/model-registry-schemas.d.ts +65 -13
- package/dist/core/model-registry-schemas.d.ts.map +1 -1
- package/dist/core/model-registry-schemas.js +10 -0
- package/dist/core/model-registry-schemas.js.map +1 -1
- package/dist/core/resource-loader-core.d.ts +1 -0
- package/dist/core/resource-loader-core.d.ts.map +1 -1
- package/dist/core/resource-loader-core.js +2 -0
- package/dist/core/resource-loader-core.js.map +1 -1
- package/dist/core/resource-loader-extensions.d.ts.map +1 -1
- package/dist/core/resource-loader-extensions.js +3 -3
- package/dist/core/resource-loader-extensions.js.map +1 -1
- package/dist/core/resource-loader-internals.d.ts +1 -0
- package/dist/core/resource-loader-internals.d.ts.map +1 -1
- package/dist/core/resource-loader-internals.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +6 -2
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/sdk-exports.d.ts +1 -1
- package/dist/core/sdk-exports.d.ts.map +1 -1
- package/dist/core/sdk-exports.js.map +1 -1
- package/dist/core/sdk-types.d.ts +0 -3
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +0 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +2 -1
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/tools/bash.d.ts +0 -5
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +10 -11
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff-preserve.d.ts +18 -0
- package/dist/core/tools/edit-diff-preserve.d.ts.map +1 -0
- package/dist/core/tools/edit-diff-preserve.js +85 -0
- package/dist/core/tools/edit-diff-preserve.js.map +1 -0
- package/dist/core/tools/edit-diff.d.ts +3 -2
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +15 -18
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/index.d.ts +0 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +0 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/model-search.d.ts +5 -0
- package/dist/modes/interactive/model-search.d.ts.map +1 -1
- package/dist/modes/interactive/model-search.js +9 -0
- package/dist/modes/interactive/model-search.js.map +1 -1
- package/dist/utils/shell.d.ts +1 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +12 -5
- package/dist/utils/shell.js.map +1 -1
- package/docs/custom-provider.md +4 -3
- package/docs/models.md +3 -2
- package/docs/packages.md +2 -2
- package/docs/quickstart.md +1 -1
- package/docs/sdk.md +2 -40
- package/docs/security.md +1 -1
- package/docs/workflows.md +238 -173
- package/package.json +5 -5
- package/dist/builtin/workflows/src/workflows/define-workflow.ts +0 -277
- package/dist/core/tools/bash-policy-compile.d.ts +0 -5
- package/dist/core/tools/bash-policy-compile.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-compile.js +0 -241
- package/dist/core/tools/bash-policy-compile.js.map +0 -1
- package/dist/core/tools/bash-policy-evaluate.d.ts +0 -3
- package/dist/core/tools/bash-policy-evaluate.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-evaluate.js +0 -92
- package/dist/core/tools/bash-policy-evaluate.js.map +0 -1
- package/dist/core/tools/bash-policy-format.d.ts +0 -5
- package/dist/core/tools/bash-policy-format.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-format.js +0 -49
- package/dist/core/tools/bash-policy-format.js.map +0 -1
- package/dist/core/tools/bash-policy-parser.d.ts +0 -4
- package/dist/core/tools/bash-policy-parser.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-parser.js +0 -155
- package/dist/core/tools/bash-policy-parser.js.map +0 -1
- package/dist/core/tools/bash-policy-segment.d.ts +0 -3
- package/dist/core/tools/bash-policy-segment.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-segment.js +0 -275
- package/dist/core/tools/bash-policy-segment.js.map +0 -1
- package/dist/core/tools/bash-policy-shell.d.ts +0 -11
- package/dist/core/tools/bash-policy-shell.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-shell.js +0 -267
- package/dist/core/tools/bash-policy-shell.js.map +0 -1
- package/dist/core/tools/bash-policy-types.d.ts +0 -146
- package/dist/core/tools/bash-policy-types.d.ts.map +0 -1
- package/dist/core/tools/bash-policy-types.js +0 -2
- package/dist/core/tools/bash-policy-types.js.map +0 -1
- package/dist/core/tools/bash-policy.d.ts +0 -6
- package/dist/core/tools/bash-policy.d.ts.map +0 -1
- package/dist/core/tools/bash-policy.js +0 -5
- 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 `
|
|
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 {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
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:
|
|
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 {
|
|
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`, `
|
|
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
|
|
998
|
+
Workflow files are TypeScript modules that export a workflow definition:
|
|
995
999
|
|
|
996
1000
|
```ts
|
|
997
|
-
import {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
-
|
|
1060
|
+
},
|
|
1061
|
+
});
|
|
1052
1062
|
```
|
|
1053
1063
|
|
|
1054
|
-
|
|
1064
|
+
Authoring basics:
|
|
1055
1065
|
|
|
1056
|
-
- `
|
|
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
|
-
-
|
|
1059
|
-
-
|
|
1060
|
-
-
|
|
1061
|
-
-
|
|
1062
|
-
-
|
|
1063
|
-
|
|
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
|
|
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
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
1151
|
-
|
|
1152
|
-
.
|
|
1153
|
-
|
|
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
|
-
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1171
1212
|
```
|
|
1172
1213
|
|
|
1173
|
-
There is no automatic `result` output. A workflow exposes exactly the keys it declares
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1204
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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
|
-
|
|
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
|
|
1248
|
-
- The
|
|
1249
|
-
- `ctx.workflow(child)` returns a discriminated child result. When `child.exited === false`, `child.outputs` is the child's full declared
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
-
|
|
1329
|
+
},
|
|
1330
|
+
});
|
|
1277
1331
|
|
|
1278
1332
|
// .atomic/workflows/research-and-synthesize.ts
|
|
1279
|
-
import {
|
|
1333
|
+
import { workflow } from "@bastani/workflows";
|
|
1334
|
+
import { Type } from "typebox";
|
|
1280
1335
|
import sharedResearch from "./shared-research.js";
|
|
1281
1336
|
|
|
1282
|
-
export default
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
1360
|
+
},
|
|
1361
|
+
});
|
|
1301
1362
|
```
|
|
1302
1363
|
|
|
1303
1364
|
#### Compose with builtin workflows
|
|
1304
1365
|
|
|
1305
|
-
Builtin workflows are also exported as
|
|
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 {
|
|
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
|
|
1336
|
-
|
|
1337
|
-
.
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
1460
|
+
},
|
|
1461
|
+
});
|
|
1397
1462
|
```
|
|
1398
1463
|
|
|
1399
|
-
Passing a
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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[] }
|
|
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
|
-
|
|
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,
|
|
1566
|
+
To bind user inputs to a workflow-wide worktree default, set `worktreeFromInputs` in `workflow({...})`:
|
|
1521
1567
|
|
|
1522
1568
|
```ts
|
|
1523
|
-
export default
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
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 `
|
|
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 {
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1834
|
-
- Do not rely on undeclared child outputs; returning a key that is not declared
|
|
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.
|