@bastani/atomic 0.5.12-4 → 0.5.12-5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/workflow-creator/SKILL.md +24 -17
- package/.agents/skills/workflow-creator/references/agent-sessions.md +67 -24
- package/.agents/skills/workflow-creator/references/computation-and-validation.md +5 -3
- package/.agents/skills/workflow-creator/references/control-flow.md +25 -11
- package/.agents/skills/workflow-creator/references/discovery-and-verification.md +3 -2
- package/.agents/skills/workflow-creator/references/failure-modes.md +35 -36
- package/.agents/skills/workflow-creator/references/getting-started.md +25 -12
- package/.agents/skills/workflow-creator/references/session-config.md +26 -5
- package/.agents/skills/workflow-creator/references/state-and-data-flow.md +3 -3
- package/.agents/skills/workflow-creator/references/workflow-inputs.md +52 -47
- package/README.md +63 -41
- package/package.json +2 -2
- package/src/sdk/components/workflow-picker-panel.tsx +15 -21
- package/src/sdk/define-workflow.test.ts +58 -0
- package/src/sdk/define-workflow.ts +48 -30
- package/src/sdk/providers/claude.ts +234 -233
- package/src/sdk/runtime/discovery.ts +1 -2
- package/src/sdk/runtime/executor.ts +6 -1
- package/src/sdk/types.ts +24 -19
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +11 -30
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +7 -4
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +6 -2
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +32 -38
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +5 -1
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +5 -1
- package/src/sdk/workflows/index.ts +2 -2
- package/src/sdk/workflow-inputs.ts +0 -54
|
@@ -4,7 +4,7 @@ This guide covers the basics of creating workflows with the `defineWorkflow().ru
|
|
|
4
4
|
|
|
5
5
|
## Quick-start example
|
|
6
6
|
|
|
7
|
-
Use `defineWorkflow<"agent">().run(callback).compile()` to define your workflow. Inside the `.run()` callback, use `ctx.stage()` to spawn agent sessions dynamically. Each session gets its own tmux window and graph node. Use native TypeScript control flow (`for`, `if`, `Promise.all()`) for orchestration.
|
|
7
|
+
Use `defineWorkflow({...}).for<"agent">().run(callback).compile()` to define your workflow. Inside the `.run()` callback, use `ctx.stage()` to spawn agent sessions dynamically. Each session gets its own tmux window and graph node. Use native TypeScript control flow (`for`, `if`, `Promise.all()`) for orchestration.
|
|
8
8
|
|
|
9
9
|
The runtime manages the full session lifecycle automatically — it creates the client, creates the session, runs your callback, then cleans up. You never need to manually disconnect or stop anything.
|
|
10
10
|
|
|
@@ -12,15 +12,17 @@ The runtime manages the full session lifecycle automatically — it creates the
|
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
// .atomic/workflows/my-workflow/claude/index.ts
|
|
15
|
-
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
15
|
+
import { defineWorkflow, extractAssistantText } from "@bastani/atomic/workflows";
|
|
16
16
|
|
|
17
|
-
export default defineWorkflow
|
|
17
|
+
export default defineWorkflow({
|
|
18
18
|
name: "my-workflow",
|
|
19
19
|
description: "A two-session pipeline",
|
|
20
|
+
inputs: [
|
|
21
|
+
{ name: "prompt", type: "text", required: true, description: "task to perform" },
|
|
22
|
+
],
|
|
20
23
|
})
|
|
24
|
+
.for<"claude">()
|
|
21
25
|
.run(async (ctx) => {
|
|
22
|
-
// Free-form workflow: the positional CLI prompt lands under
|
|
23
|
-
// `inputs.prompt`. Destructure once and close over it in stages.
|
|
24
26
|
const prompt = ctx.inputs.prompt ?? "";
|
|
25
27
|
|
|
26
28
|
const describe = await ctx.stage(
|
|
@@ -55,10 +57,14 @@ export default defineWorkflow<"claude">({
|
|
|
55
57
|
// .atomic/workflows/my-workflow/copilot/index.ts
|
|
56
58
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
57
59
|
|
|
58
|
-
export default defineWorkflow
|
|
60
|
+
export default defineWorkflow({
|
|
59
61
|
name: "my-workflow",
|
|
60
62
|
description: "A two-session pipeline",
|
|
63
|
+
inputs: [
|
|
64
|
+
{ name: "prompt", type: "text", required: true, description: "task to perform" },
|
|
65
|
+
],
|
|
61
66
|
})
|
|
67
|
+
.for<"copilot">()
|
|
62
68
|
.run(async (ctx) => {
|
|
63
69
|
const prompt = ctx.inputs.prompt ?? "";
|
|
64
70
|
|
|
@@ -94,10 +100,14 @@ export default defineWorkflow<"copilot">({
|
|
|
94
100
|
// .atomic/workflows/my-workflow/opencode/index.ts
|
|
95
101
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
96
102
|
|
|
97
|
-
export default defineWorkflow
|
|
103
|
+
export default defineWorkflow({
|
|
98
104
|
name: "my-workflow",
|
|
99
105
|
description: "A two-session pipeline",
|
|
106
|
+
inputs: [
|
|
107
|
+
{ name: "prompt", type: "text", required: true, description: "task to perform" },
|
|
108
|
+
],
|
|
100
109
|
})
|
|
110
|
+
.for<"opencode">()
|
|
101
111
|
.run(async (ctx) => {
|
|
102
112
|
const prompt = ctx.inputs.prompt ?? "";
|
|
103
113
|
|
|
@@ -172,7 +182,7 @@ const result = await ctx.stage(
|
|
|
172
182
|
// s.client, s.session, s.save(), s.transcript() all work identically
|
|
173
183
|
const result = await s.session.query("Analyze the codebase.");
|
|
174
184
|
s.save(s.sessionId);
|
|
175
|
-
return result
|
|
185
|
+
return extractAssistantText(result, 0);
|
|
176
186
|
},
|
|
177
187
|
);
|
|
178
188
|
// result.result contains the returned value
|
|
@@ -208,7 +218,7 @@ Headless stages are transparent to graph topology — `seed → [3 headless] →
|
|
|
208
218
|
The `@bastani/atomic/workflows` package exports the workflow authoring primitives. For native SDK types and utilities, install and import from the provider packages directly.
|
|
209
219
|
|
|
210
220
|
**Builder:**
|
|
211
|
-
- `defineWorkflow` — entry point
|
|
221
|
+
- `defineWorkflow` — entry point; returns a chainable `WorkflowBuilder`. Use `.for<"agent">()` on the builder to narrow types to a specific provider.
|
|
212
222
|
- `WorkflowBuilder` — the builder class (rarely needed directly)
|
|
213
223
|
|
|
214
224
|
**Types** (import with `import type`):
|
|
@@ -223,13 +233,16 @@ The `@bastani/atomic/workflows` package exports the workflow authoring primitive
|
|
|
223
233
|
- `StageSessionOptions<A>` — provider-specific session create options for `ctx.stage()` third argument
|
|
224
234
|
- `ProviderClient<A>` — the `s.client` type, resolved by agent type
|
|
225
235
|
- `ProviderSession<A>` — the `s.session` type, resolved by agent type
|
|
226
|
-
- `ClaudeSessionWrapper` — Atomic wrapper for Claude sessions (exposes `s.session.query()`)
|
|
236
|
+
- `ClaudeSessionWrapper` — Atomic wrapper for Claude sessions (exposes `s.session.query()`, which returns `SessionMessage[]`)
|
|
227
237
|
- `ClaudeQueryDefaults` — per-stage query defaults (timeouts, poll interval) for Claude sessions
|
|
228
238
|
- `SessionRef` — `string | SessionHandle<unknown>` for transcript/message lookups
|
|
229
239
|
- `WorkflowContext` — top-level context passed to `.run()` callback
|
|
230
240
|
- `WorkflowOptions` — `{ name, description? }` workflow metadata
|
|
231
241
|
- `WorkflowDefinition` — sealed output of `.compile()`
|
|
232
242
|
|
|
243
|
+
**Response utilities:**
|
|
244
|
+
- `extractAssistantText(messages, afterIndex)` — extract plain text from the `SessionMessage[]` returned by `s.session.query()` for Claude; use `extractAssistantText(result, 0)` to get the full assistant response text
|
|
245
|
+
|
|
233
246
|
**Validation helpers:**
|
|
234
247
|
- `validateClaudeWorkflow` — static validation for Claude workflow source files; warns on direct `createClaudeSession` or `claudeQuery` usage
|
|
235
248
|
- `validateCopilotWorkflow` — static validation for Copilot workflow source files; warns on manual `new CopilotClient` or `client.createSession()` usage
|
|
@@ -251,7 +264,7 @@ The Atomic runtime provides `s.client` and `s.session` with types resolved from
|
|
|
251
264
|
|-------|------|-------------|
|
|
252
265
|
| `client` | `ProviderClient<A>` | Pre-created SDK client (auto-managed by runtime) |
|
|
253
266
|
| `session` | `ProviderSession<A>` | Pre-created provider session (auto-managed by runtime) |
|
|
254
|
-
| `inputs` | `
|
|
267
|
+
| `inputs` | `{ [K in N]?: string }` | Typed inputs for this run — only declared field names are valid keys. Accessing an undeclared field is a compile-time error. See `workflow-inputs.md`. |
|
|
255
268
|
| `agent` | `AgentType` | Which agent is running |
|
|
256
269
|
| `transcript(ref)` | `(ref: SessionRef) => Promise<Transcript>` | Get prior session's transcript as `{ path, content }` |
|
|
257
270
|
| `getMessages(ref)` | `(ref: SessionRef) => Promise<SavedMessage[]>` | Get prior session's raw native messages |
|
|
@@ -286,4 +299,4 @@ Both include `helpers/` directories with SDK-agnostic logic (prompt builders, pa
|
|
|
286
299
|
|
|
287
300
|
## Type safety
|
|
288
301
|
|
|
289
|
-
The SDK is typed with **no `unknown` or `any`**. `SessionContext` fields are precisely typed, and native provider types may appear inside Atomic generic aliases and runtime values — if you need to name those types in your own code, import them from the provider SDK directly. Use `import type` for type-only imports. Use
|
|
302
|
+
The SDK is typed with **no `unknown` or `any`**. `SessionContext` fields are precisely typed, and native provider types may appear inside Atomic generic aliases and runtime values — if you need to name those types in your own code, import them from the provider SDK directly. Use `import type` for type-only imports. Use `.for<"agent">()` to narrow `s.client` and `s.session` to the correct provider types. Declare `inputs` inline so TypeScript enforces typed access on `ctx.inputs`.
|
|
@@ -20,11 +20,13 @@ await ctx.stage({ name: "..." }, {
|
|
|
20
20
|
### Session options (`sessionOpts` — 3rd arg to `ctx.stage()`)
|
|
21
21
|
|
|
22
22
|
These are `ClaudeQueryDefaults` and set defaults for every `s.session.query()`
|
|
23
|
-
call inside the callback
|
|
23
|
+
call inside the callback. The available fields are: `pollIntervalMs`,
|
|
24
|
+
`submitPresses`, `maxSubmitRounds`, `readyTimeoutMs`. Note that `timeoutMs` no
|
|
25
|
+
longer exists — idle detection is automatic (pane capture for interactive
|
|
26
|
+
stages, SDK streaming for headless stages).
|
|
24
27
|
|
|
25
28
|
```ts
|
|
26
29
|
await ctx.stage({ name: "..." }, {}, {
|
|
27
|
-
timeoutMs: 5 * 60 * 1000, // 5 minutes per query (default)
|
|
28
30
|
pollIntervalMs: 1_000, // Poll interval for output
|
|
29
31
|
}, async (s) => {
|
|
30
32
|
await s.session.query((ctx.inputs.prompt ?? ""));
|
|
@@ -101,15 +103,34 @@ const result = query({
|
|
|
101
103
|
the pane ID from `s.paneId` automatically. Call it inside the stage callback:
|
|
102
104
|
|
|
103
105
|
```ts
|
|
106
|
+
import { extractAssistantText } from "@anthropic-ai/claude-agent-sdk";
|
|
107
|
+
|
|
104
108
|
await ctx.stage({ name: "..." }, {}, {}, async (s) => {
|
|
105
109
|
const result = await s.session.query("Your prompt");
|
|
106
|
-
// result
|
|
110
|
+
// extractAssistantText(result, 0) — extract assistant text from the result
|
|
111
|
+
const text = extractAssistantText(result, 0);
|
|
107
112
|
s.save(s.sessionId);
|
|
108
113
|
});
|
|
109
114
|
```
|
|
110
115
|
|
|
111
|
-
The query defaults (
|
|
112
|
-
as shown above.
|
|
116
|
+
The query defaults (poll interval, submit presses, etc.) can be configured via
|
|
117
|
+
`sessionOpts` as shown above.
|
|
118
|
+
|
|
119
|
+
For **headless stages**, SDK options (such as `permissionMode`, `agent`,
|
|
120
|
+
`allowDangerouslySkipPermissions`) can be passed directly as the second
|
|
121
|
+
argument to `s.session.query()`:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
await ctx.stage({ name: "..." }, {}, {}, async (s) => {
|
|
125
|
+
const result = await s.session.query("Your prompt", {
|
|
126
|
+
permissionMode: "bypassPermissions",
|
|
127
|
+
allowDangerouslySkipPermissions: true,
|
|
128
|
+
agent: "worker",
|
|
129
|
+
});
|
|
130
|
+
const text = extractAssistantText(result, 0);
|
|
131
|
+
s.save(s.sessionId);
|
|
132
|
+
});
|
|
133
|
+
```
|
|
113
134
|
|
|
114
135
|
### Claude hooks
|
|
115
136
|
|
|
@@ -114,13 +114,13 @@ Use closures and variables for state within a single session:
|
|
|
114
114
|
);
|
|
115
115
|
|
|
116
116
|
// Accumulate findings
|
|
117
|
-
const review = parseReviewResult(result
|
|
117
|
+
const review = parseReviewResult(extractAssistantText(result, 0));
|
|
118
118
|
if (review) {
|
|
119
119
|
findings.push(...review.findings.map(f => f.title));
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Track clean streak
|
|
123
|
-
if (!hasActionableFindings(review, result
|
|
123
|
+
if (!hasActionableFindings(review, extractAssistantText(result, 0))) {
|
|
124
124
|
consecutiveClean++;
|
|
125
125
|
if (consecutiveClean >= 2) break;
|
|
126
126
|
continue;
|
|
@@ -129,7 +129,7 @@ Use closures and variables for state within a single session:
|
|
|
129
129
|
|
|
130
130
|
// Apply fix
|
|
131
131
|
const fixResult = await s.session.query(buildFixSpec(review, (ctx.inputs.prompt ?? "")));
|
|
132
|
-
priorOutput = fixResult
|
|
132
|
+
priorOutput = extractAssistantText(fixResult, 0);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// All local state is available here
|
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Workflows collect structured data from the user at invocation time through
|
|
4
4
|
a single uniform API: `ctx.inputs` (and `s.inputs` inside stage
|
|
5
|
-
callbacks). This reference covers how the inputs pipe works,
|
|
6
|
-
declare
|
|
7
|
-
|
|
5
|
+
callbacks). This reference covers how the inputs pipe works, how to
|
|
6
|
+
declare input schemas, and how values reach the workflow from the CLI
|
|
7
|
+
and the interactive picker.
|
|
8
8
|
|
|
9
9
|
## The inputs pipe
|
|
10
10
|
|
|
11
|
-
Every workflow run receives a
|
|
11
|
+
Every workflow run receives a typed inputs object. When the workflow
|
|
12
|
+
declares an `inputs` schema, only the declared field names are valid
|
|
13
|
+
keys — accessing undeclared fields is a compile-time error. The
|
|
12
14
|
runtime populates it from whichever invocation surface the user chose:
|
|
13
15
|
|
|
14
16
|
| Surface | How values are supplied | How they land in `ctx.inputs` |
|
|
15
17
|
|---|---|---|
|
|
16
|
-
| **Named run, positional** — `atomic workflow -n hello -a claude "fix the bug"` | A single positional prompt string | `{ prompt: "fix the bug" }` |
|
|
18
|
+
| **Named run, positional** — `atomic workflow -n hello -a claude "fix the bug"` | A single positional prompt string (the workflow must declare a `prompt` input) | `{ prompt: "fix the bug" }` |
|
|
17
19
|
| **Named run, structured** — `atomic workflow -n gen-spec -a claude --research_doc=notes.md --focus=standard` | One `--<field>=<value>` flag per declared input | `{ research_doc: "notes.md", focus: "standard" }` |
|
|
18
|
-
| **Interactive picker** — `atomic workflow -a claude` | The user fills in a form rendered from the declared schema
|
|
20
|
+
| **Interactive picker** — `atomic workflow -a claude` | The user fills in a form rendered from the declared schema | Whatever the user typed, keyed by field name |
|
|
19
21
|
|
|
20
22
|
Workflow code is the same either way — it always reads
|
|
21
23
|
`ctx.inputs.<name>`. The invocation surface is a CLI concern, not a
|
|
@@ -23,12 +25,19 @@ workflow concern.
|
|
|
23
25
|
|
|
24
26
|
## Reading inputs
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
Workflows that accept a user prompt should declare it explicitly as an
|
|
29
|
+
input. Destructure it once at the top of `.run()` so every stage can
|
|
30
|
+
close over a bare string:
|
|
29
31
|
|
|
30
32
|
```ts
|
|
31
|
-
defineWorkflow
|
|
33
|
+
defineWorkflow({
|
|
34
|
+
name: "answer",
|
|
35
|
+
description: "Single-turn answer",
|
|
36
|
+
inputs: [
|
|
37
|
+
{ name: "prompt", type: "text", required: true, description: "question to answer" },
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
|
+
.for<"claude">()
|
|
32
41
|
.run(async (ctx) => {
|
|
33
42
|
const prompt = ctx.inputs.prompt ?? "";
|
|
34
43
|
|
|
@@ -45,7 +54,7 @@ out of `ctx.inputs` once for readability and so downstream stages can
|
|
|
45
54
|
close over locals:
|
|
46
55
|
|
|
47
56
|
```ts
|
|
48
|
-
defineWorkflow
|
|
57
|
+
defineWorkflow({
|
|
49
58
|
name: "gen-spec",
|
|
50
59
|
description: "Convert a research doc into a detailed execution spec",
|
|
51
60
|
inputs: [
|
|
@@ -60,6 +69,7 @@ defineWorkflow<"claude">({
|
|
|
60
69
|
{ name: "notes", type: "text" },
|
|
61
70
|
],
|
|
62
71
|
})
|
|
72
|
+
.for<"claude">()
|
|
63
73
|
.run(async (ctx) => {
|
|
64
74
|
const { research_doc, focus } = ctx.inputs;
|
|
65
75
|
const notes = ctx.inputs.notes ?? "";
|
|
@@ -99,7 +109,7 @@ interface WorkflowInput {
|
|
|
99
109
|
/** Default value — enums use this to pick their initial value. */
|
|
100
110
|
default?: string;
|
|
101
111
|
/** Allowed values — required when `type` is `"enum"`. */
|
|
102
|
-
values?: string[];
|
|
112
|
+
values?: readonly string[];
|
|
103
113
|
}
|
|
104
114
|
```
|
|
105
115
|
|
|
@@ -147,35 +157,29 @@ This validation runs before any workflow code, so a malformed
|
|
|
147
157
|
invocation can never reach your `.run()` callback in a half-filled
|
|
148
158
|
state.
|
|
149
159
|
|
|
150
|
-
##
|
|
160
|
+
## Declaring a prompt input
|
|
151
161
|
|
|
152
|
-
|
|
162
|
+
Workflows that accept a user prompt should declare it explicitly in their
|
|
163
|
+
`inputs` array rather than relying on an implicit key:
|
|
153
164
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
the raw prompt.
|
|
160
|
-
|
|
161
|
-
Read the prompt via `ctx.inputs.prompt ?? ""`.
|
|
165
|
+
```ts
|
|
166
|
+
inputs: [
|
|
167
|
+
{ name: "prompt", type: "text", required: true, description: "task to perform" },
|
|
168
|
+
]
|
|
169
|
+
```
|
|
162
170
|
|
|
163
|
-
|
|
171
|
+
This gives the same CLI ergonomics — `atomic workflow -n hello -a claude "fix the bug"` still works — while providing compile-time safety. Accessing `ctx.inputs.prompt` without declaring it is a type error.
|
|
164
172
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- You want the picker to show a real form (each field, its type, and
|
|
168
|
-
any validation cues) instead of a single blob text area.
|
|
169
|
-
- You want the CLI to reject bad inputs before spawning a workflow —
|
|
170
|
-
e.g. a nonexistent enum value.
|
|
171
|
-
- You want the invocation to be scriptable and auditable — flag-based
|
|
172
|
-
invocation reads cleanly in CI.
|
|
173
|
+
For workflows that need both a free-form prompt AND structured parameters,
|
|
174
|
+
declare all fields in the schema:
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
```ts
|
|
177
|
+
inputs: [
|
|
178
|
+
{ name: "prompt", type: "text", required: true, description: "what to build" },
|
|
179
|
+
{ name: "focus", type: "enum", required: true, values: ["minimal", "standard", "exhaustive"], default: "standard" },
|
|
180
|
+
{ name: "notes", type: "text", description: "extra context" },
|
|
181
|
+
]
|
|
182
|
+
```
|
|
179
183
|
|
|
180
184
|
## The interactive picker
|
|
181
185
|
|
|
@@ -187,8 +191,7 @@ picker. The picker:
|
|
|
187
191
|
2. Loads each workflow's metadata (description + declared inputs).
|
|
188
192
|
3. Shows a Telescope-style fuzzy list. The user types to filter,
|
|
189
193
|
arrows to navigate, ↵ to lock in a selection.
|
|
190
|
-
4. Renders the selected workflow's form.
|
|
191
|
-
single `prompt` text field; structured workflows get one field
|
|
194
|
+
4. Renders the selected workflow's form. The picker renders one field
|
|
192
195
|
per declared input with type-specific rendering.
|
|
193
196
|
5. Validates required fields on ⌃s. If any are empty, focus jumps to
|
|
194
197
|
the first invalid field and the run button stays disabled.
|
|
@@ -246,18 +249,20 @@ Both `--flag=value` and `--flag value` forms are accepted. Short flags
|
|
|
246
249
|
|
|
247
250
|
## Pitfalls
|
|
248
251
|
|
|
249
|
-
###
|
|
252
|
+
### Declare every field you access
|
|
250
253
|
|
|
251
|
-
|
|
252
|
-
`
|
|
253
|
-
|
|
254
|
-
`atomic workflow -n gen-spec -a claude "some text"` you'll get:
|
|
254
|
+
With typed inputs, accessing `ctx.inputs.foo` when `foo` is not declared
|
|
255
|
+
in the workflow's `inputs` array is a compile-time error. If your workflow
|
|
256
|
+
needs a prompt field, declare it:
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
+
```ts
|
|
259
|
+
inputs: [
|
|
260
|
+
{ name: "prompt", type: "text", required: true, description: "task prompt" },
|
|
261
|
+
]
|
|
262
|
+
```
|
|
258
263
|
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
The CLI rejects positional prompt strings for workflows that don't declare
|
|
265
|
+
a `prompt` input.
|
|
261
266
|
|
|
262
267
|
### Don't rename inputs across workflow versions
|
|
263
268
|
|
package/README.md
CHANGED
|
@@ -78,8 +78,9 @@ Each of these is a `.ts` file using Atomic's [Workflow SDK](#workflow-sdk--build
|
|
|
78
78
|
- [Saving Transcripts](#saving-transcripts)
|
|
79
79
|
- [Per-Agent Session APIs](#per-agent-session-apis)
|
|
80
80
|
- [Key Rules](#key-rules)
|
|
81
|
-
- [
|
|
81
|
+
- [Research Codebase](#research-codebase)
|
|
82
82
|
- [Autonomous Execution (Ralph)](#autonomous-execution-ralph)
|
|
83
|
+
- [Deep Research Codebase](#deep-research-codebase)
|
|
83
84
|
- [Containerized Execution](#containerized-execution)
|
|
84
85
|
- [Specialized Sub-Agents](#specialized-sub-agents)
|
|
85
86
|
- [Built-in Skills](#built-in-skills)
|
|
@@ -258,10 +259,10 @@ Here's one of the [canonical use cases](#what-you-can-build) — a team pipeline
|
|
|
258
259
|
// .atomic/workflows/review-to-merge/claude/index.ts
|
|
259
260
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
260
261
|
|
|
261
|
-
export default defineWorkflow
|
|
262
|
+
export default defineWorkflow({
|
|
262
263
|
name: "review-to-merge",
|
|
263
264
|
description: "Review → CI → PR → Notify → Approve → Merge",
|
|
264
|
-
})
|
|
265
|
+
}).for<"claude">()
|
|
265
266
|
.run(async (ctx) => {
|
|
266
267
|
// Step 1: Review the changes
|
|
267
268
|
const review = await ctx.stage(
|
|
@@ -368,10 +369,11 @@ atomic workflow -n my-workflow -a claude "describe this project"
|
|
|
368
369
|
// .atomic/workflows/my-workflow/claude/index.ts
|
|
369
370
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
370
371
|
|
|
371
|
-
export default defineWorkflow
|
|
372
|
+
export default defineWorkflow({
|
|
372
373
|
name: "my-workflow",
|
|
373
374
|
description: "Two-session pipeline: describe -> summarize",
|
|
374
|
-
}
|
|
375
|
+
inputs: [{ name: "prompt", type: "text", required: true, description: "task prompt" }],
|
|
376
|
+
}).for<"claude">()
|
|
375
377
|
.run(async (ctx) => {
|
|
376
378
|
const prompt = ctx.inputs.prompt ?? "";
|
|
377
379
|
|
|
@@ -407,10 +409,11 @@ export default defineWorkflow<"claude">({
|
|
|
407
409
|
```ts
|
|
408
410
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
409
411
|
|
|
410
|
-
export default defineWorkflow
|
|
412
|
+
export default defineWorkflow({
|
|
411
413
|
name: "parallel-demo",
|
|
412
414
|
description: "describe -> [summarize-a, summarize-b] -> merge",
|
|
413
|
-
}
|
|
415
|
+
inputs: [{ name: "prompt", type: "text", required: true, description: "task prompt" }],
|
|
416
|
+
}).for<"claude">()
|
|
414
417
|
.run(async (ctx) => {
|
|
415
418
|
const prompt = ctx.inputs.prompt ?? "";
|
|
416
419
|
|
|
@@ -458,7 +461,7 @@ Declare an `inputs` array on `defineWorkflow` and the CLI materialises one `--<f
|
|
|
458
461
|
// .atomic/workflows/gen-spec/claude/index.ts
|
|
459
462
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
460
463
|
|
|
461
|
-
export default defineWorkflow
|
|
464
|
+
export default defineWorkflow({
|
|
462
465
|
name: "gen-spec",
|
|
463
466
|
description: "Convert a research doc into an execution spec",
|
|
464
467
|
inputs: [
|
|
@@ -483,7 +486,7 @@ export default defineWorkflow<"claude">({
|
|
|
483
486
|
description: "extra guidance for the spec writer (optional)",
|
|
484
487
|
},
|
|
485
488
|
],
|
|
486
|
-
})
|
|
489
|
+
}).for<"claude">()
|
|
487
490
|
.run(async (ctx) => {
|
|
488
491
|
// Read each declared field by name.
|
|
489
492
|
const { research_doc, focus } = ctx.inputs;
|
|
@@ -520,12 +523,13 @@ atomic workflow -a claude
|
|
|
520
523
|
Stages can run in **headless mode** (`headless: true`) — they execute the provider SDK in-process instead of spawning a tmux window. Headless stages are invisible in the workflow graph but tracked via a background task counter in the statusline. Use them for parallel data-gathering tasks that don't need a visible TUI.
|
|
521
524
|
|
|
522
525
|
```ts
|
|
523
|
-
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
526
|
+
import { defineWorkflow, extractAssistantText } from "@bastani/atomic/workflows";
|
|
524
527
|
|
|
525
|
-
export default defineWorkflow
|
|
528
|
+
export default defineWorkflow({
|
|
526
529
|
name: "headless-demo",
|
|
527
530
|
description: "seed -> [3 headless background] -> merge",
|
|
528
|
-
}
|
|
531
|
+
inputs: [{ name: "prompt", type: "text", required: true, description: "task prompt" }],
|
|
532
|
+
}).for<"claude">()
|
|
529
533
|
.run(async (ctx) => {
|
|
530
534
|
const prompt = ctx.inputs.prompt ?? "";
|
|
531
535
|
|
|
@@ -536,7 +540,7 @@ export default defineWorkflow<"claude">({
|
|
|
536
540
|
async (s) => {
|
|
537
541
|
const result = await s.session.query(prompt);
|
|
538
542
|
s.save(s.sessionId);
|
|
539
|
-
return
|
|
543
|
+
return extractAssistantText(result, 0);
|
|
540
544
|
},
|
|
541
545
|
);
|
|
542
546
|
|
|
@@ -545,17 +549,17 @@ export default defineWorkflow<"claude">({
|
|
|
545
549
|
ctx.stage({ name: "pros", headless: true }, {}, {}, async (s) => {
|
|
546
550
|
const r = await s.session.query(`List 3 pros:\n\n${seed.result}`);
|
|
547
551
|
s.save(s.sessionId);
|
|
548
|
-
return
|
|
552
|
+
return extractAssistantText(r, 0);
|
|
549
553
|
}),
|
|
550
554
|
ctx.stage({ name: "cons", headless: true }, {}, {}, async (s) => {
|
|
551
555
|
const r = await s.session.query(`List 3 cons:\n\n${seed.result}`);
|
|
552
556
|
s.save(s.sessionId);
|
|
553
|
-
return
|
|
557
|
+
return extractAssistantText(r, 0);
|
|
554
558
|
}),
|
|
555
559
|
ctx.stage({ name: "uses", headless: true }, {}, {}, async (s) => {
|
|
556
560
|
const r = await s.session.query(`List 3 use cases:\n\n${seed.result}`);
|
|
557
561
|
s.save(s.sessionId);
|
|
558
|
-
return
|
|
562
|
+
return extractAssistantText(r, 0);
|
|
559
563
|
}),
|
|
560
564
|
]);
|
|
561
565
|
|
|
@@ -625,29 +629,29 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
625
629
|
|
|
626
630
|
#### WorkflowContext (`ctx`) — top-level orchestrator
|
|
627
631
|
|
|
628
|
-
| Property | Type | Description
|
|
629
|
-
| ---------------------------------------------- | --------------------------- |
|
|
630
|
-
| `ctx.inputs` | `
|
|
631
|
-
| `ctx.agent` | `AgentType` | Which agent is running (`"claude"`, `"copilot"`, `"opencode"`)
|
|
632
|
-
| `ctx.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a session — returns handle with `name`, `id`, `result`
|
|
633
|
-
| `ctx.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript (`{ path, content }`)
|
|
634
|
-
| `ctx.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages
|
|
632
|
+
| Property | Type | Description |
|
|
633
|
+
| ---------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
634
|
+
| `ctx.inputs` | `{ [K in N]?: string }` | Typed inputs for this run — only declared field names are valid keys. Accessing an undeclared field is a compile-time error. Workflows that need a prompt must declare it in their `inputs` schema |
|
|
635
|
+
| `ctx.agent` | `AgentType` | Which agent is running (`"claude"`, `"copilot"`, `"opencode"`) |
|
|
636
|
+
| `ctx.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a session — returns handle with `name`, `id`, `result` |
|
|
637
|
+
| `ctx.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript (`{ path, content }`) |
|
|
638
|
+
| `ctx.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
635
639
|
|
|
636
640
|
#### SessionContext (`s`) — inside each session callback
|
|
637
641
|
|
|
638
|
-
| Property | Type | Description
|
|
639
|
-
| -------------------------------------------- | --------------------------- |
|
|
640
|
-
| `s.client` | `ProviderClient<A>` | Pre-created SDK client (auto-managed by runtime)
|
|
641
|
-
| `s.session` | `ProviderSession<A>` | Pre-created provider session (auto-managed by runtime)
|
|
642
|
-
| `s.inputs` | `
|
|
643
|
-
| `s.agent` | `AgentType` | Which agent is running
|
|
644
|
-
| `s.paneId` | `string` | tmux pane ID for this session
|
|
645
|
-
| `s.sessionId` | `string` | Session UUID
|
|
646
|
-
| `s.sessionDir` | `string` | Path to this session's storage directory on disk
|
|
647
|
-
| `s.save(messages)` | `SaveTranscript` | Save this session's output for subsequent sessions
|
|
648
|
-
| `s.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript
|
|
649
|
-
| `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages
|
|
650
|
-
| `s.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph)
|
|
642
|
+
| Property | Type | Description |
|
|
643
|
+
| -------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
644
|
+
| `s.client` | `ProviderClient<A>` | Pre-created SDK client (auto-managed by runtime) |
|
|
645
|
+
| `s.session` | `ProviderSession<A>` | Pre-created provider session (auto-managed by runtime) |
|
|
646
|
+
| `s.inputs` | `{ [K in N]?: string }` | Same typed inputs as `ctx.inputs`, forwarded into every stage so session callbacks can read values without closing over the outer `ctx` |
|
|
647
|
+
| `s.agent` | `AgentType` | Which agent is running |
|
|
648
|
+
| `s.paneId` | `string` | tmux pane ID for this session |
|
|
649
|
+
| `s.sessionId` | `string` | Session UUID |
|
|
650
|
+
| `s.sessionDir` | `string` | Path to this session's storage directory on disk |
|
|
651
|
+
| `s.save(messages)` | `SaveTranscript` | Save this session's output for subsequent sessions |
|
|
652
|
+
| `s.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript |
|
|
653
|
+
| `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
654
|
+
| `s.stage(opts, clientOpts, sessionOpts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph) |
|
|
651
655
|
|
|
652
656
|
#### Session Options (`SessionRunOptions`)
|
|
653
657
|
|
|
@@ -691,9 +695,12 @@ The runtime auto-creates `s.client` and `s.session` — use them directly inside
|
|
|
691
695
|
|
|
692
696
|
For the authoring walkthrough with worked examples, ask Atomic to use the `workflow-creator` skill or read the skill reference at `.agents/skills/workflow-creator/`.
|
|
693
697
|
|
|
698
|
+
> [!TIP]
|
|
699
|
+
> **Keeping workflows up to date:** When the Workflow SDK is updated (new return types, new options, deprecated patterns), you can ask the `workflow-creator` skill to migrate your existing workflows to the latest best practices. Just open your workflow file and ask: _"Update this workflow to use the latest SDK patterns."_ The skill stays current with the SDK and will apply the right changes automatically.
|
|
700
|
+
|
|
694
701
|
</details>
|
|
695
702
|
|
|
696
|
-
###
|
|
703
|
+
### Research Codebase
|
|
697
704
|
|
|
698
705
|
The `/research-codebase` command dispatches **specialized sub-agents in parallel** to analyze your codebase:
|
|
699
706
|
|
|
@@ -752,7 +759,7 @@ Research outputs persist in your `research/` directory and specs persist in your
|
|
|
752
759
|
<img src="assets/ralph-wiggum.jpg" alt="Ralph Wiggum" width="600">
|
|
753
760
|
</p>
|
|
754
761
|
|
|
755
|
-
The [Ralph
|
|
762
|
+
The [Ralph Method](https://ghuntley.com/ralph/) enables **multi-hour autonomous coding sessions**. After approving your spec, let Ralph work in the background while you focus on other tasks.
|
|
756
763
|
|
|
757
764
|
**How Ralph works:**
|
|
758
765
|
|
|
@@ -778,6 +785,21 @@ cd ../my-project-ralph
|
|
|
778
785
|
atomic workflow -n ralph -a claude "Build the auth module"
|
|
779
786
|
```
|
|
780
787
|
|
|
788
|
+
### Deep Research Codebase
|
|
789
|
+
|
|
790
|
+
Atomic also ships with `deep-research-codebase`, a built-in workflow that performs **multi-agent parallel research** across your codebase. While `/research-codebase` is a single-shot command, the `deep-research-codebase` workflow is a full multi-stage pipeline:
|
|
791
|
+
|
|
792
|
+
1. **Scout** — A single agent scans the codebase structure and produces an architectural orientation
|
|
793
|
+
2. **History** — A parallel agent surfaces prior research from `research/docs/`
|
|
794
|
+
3. **Explorers** — Multiple parallel agents (count scaled by LOC) each investigate a partition of the codebase, writing findings to scratch files
|
|
795
|
+
4. **Aggregator** — A final agent synthesizes all explorer reports + history into a dated research document at `research/docs/YYYY-MM-DD-<slug>.md`
|
|
796
|
+
|
|
797
|
+
```bash
|
|
798
|
+
atomic workflow -n deep-research-codebase -a claude "How does the authentication system work?"
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
The workflow produces a permanent research artifact that can be referenced by future runs, specs, or other workflows.
|
|
802
|
+
|
|
781
803
|
### Containerized Execution
|
|
782
804
|
|
|
783
805
|
Atomic ships as **devcontainer features** that bundle the CLI, agent, and all dependencies into isolated containers. This is the recommended way to run autonomous agents safely.
|
|
@@ -1043,7 +1065,7 @@ atomic chat -a claude --verbose # Forward --verbose to claude
|
|
|
1043
1065
|
| `-n, --name <name>` | Workflow name (matches directory under `.atomic/workflows/<name>/`) |
|
|
1044
1066
|
| `-a, --agent <name>` | Agent: `claude`, `opencode`, `copilot` |
|
|
1045
1067
|
| `--<field>=<value>` | Structured input for workflows that declare an `inputs` schema (also accepts `--<field> <value>`) |
|
|
1046
|
-
| `[prompt...]` | Positional prompt
|
|
1068
|
+
| `[prompt...]` | Positional prompt — requires the workflow to declare a `prompt` input |
|
|
1047
1069
|
|
|
1048
1070
|
The workflow command supports four invocation shapes:
|
|
1049
1071
|
|
|
@@ -1057,7 +1079,7 @@ atomic workflow list -a claude # filter by agent
|
|
|
1057
1079
|
# and confirm with y/n
|
|
1058
1080
|
atomic workflow -a claude
|
|
1059
1081
|
|
|
1060
|
-
# 3. Run a
|
|
1082
|
+
# 3. Run a workflow with a positional prompt (workflow must declare a "prompt" input)
|
|
1061
1083
|
atomic workflow -n ralph -a claude "build a REST API for user management"
|
|
1062
1084
|
|
|
1063
1085
|
# 4. Run a structured-input workflow with one --<field> flag per declared input
|
|
@@ -1066,7 +1088,7 @@ atomic workflow -n gen-spec -a claude \
|
|
|
1066
1088
|
--focus=standard
|
|
1067
1089
|
```
|
|
1068
1090
|
|
|
1069
|
-
Workflows that declare an `inputs: WorkflowInput[]` schema get CLI flag validation for free — missing required fields and invalid enum values are rejected before any tmux session is spawned, with error messages that spell out the expected flag set. Workflows that
|
|
1091
|
+
Workflows that declare an `inputs: WorkflowInput[]` schema get CLI flag validation for free — missing required fields and invalid enum values are rejected before any tmux session is spawned, with error messages that spell out the expected flag set. Workflows that declare a `prompt` input accept a positional prompt on the command line, which the runtime stores under `ctx.inputs.prompt`. **Builtin workflows (like `ralph`) are reserved names** — a local or global workflow with the same name will not shadow a builtin at resolution time.
|
|
1070
1092
|
|
|
1071
1093
|
#### `atomic completions` — Shell Completions
|
|
1072
1094
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.5.12-
|
|
3
|
+
"version": "0.5.12-5",
|
|
4
4
|
"description": "Configuration management CLI and SDK for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"typescript-language-server": "^5.1.3"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
74
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.108",
|
|
75
75
|
"@clack/prompts": "^1.2.0",
|
|
76
76
|
"@commander-js/extra-typings": "^14.0.0",
|
|
77
77
|
"@github/copilot-sdk": "^0.2.2",
|