@bastani/atomic 0.5.0-3 → 0.5.0-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/.atomic/workflows/hello/claude/index.ts +22 -25
- package/.atomic/workflows/hello/copilot/index.ts +41 -31
- package/.atomic/workflows/hello/opencode/index.ts +40 -40
- package/.atomic/workflows/hello-parallel/claude/index.ts +54 -54
- package/.atomic/workflows/hello-parallel/copilot/index.ts +89 -70
- package/.atomic/workflows/hello-parallel/opencode/index.ts +77 -77
- package/.atomic/workflows/ralph/claude/index.ts +128 -93
- package/.atomic/workflows/ralph/copilot/index.ts +212 -112
- package/.atomic/workflows/ralph/helpers/prompts.ts +45 -2
- package/.atomic/workflows/ralph/opencode/index.ts +174 -111
- package/README.md +138 -59
- package/package.json +1 -1
- package/src/cli.ts +0 -2
- package/src/commands/cli/chat/index.ts +28 -8
- package/src/commands/cli/init/index.ts +7 -10
- package/src/commands/cli/init/scm.ts +27 -10
- package/src/sdk/components/connectors.test.ts +45 -0
- package/src/sdk/components/layout.test.ts +321 -0
- package/src/sdk/components/layout.ts +51 -15
- package/src/sdk/components/orchestrator-panel-contexts.ts +13 -4
- package/src/sdk/components/orchestrator-panel-store.test.ts +156 -0
- package/src/sdk/components/orchestrator-panel-store.ts +24 -0
- package/src/sdk/components/orchestrator-panel.tsx +21 -0
- package/src/sdk/components/session-graph-panel.tsx +8 -15
- package/src/sdk/components/statusline.tsx +4 -6
- package/src/sdk/define-workflow.test.ts +71 -0
- package/src/sdk/define-workflow.ts +42 -39
- package/src/sdk/errors.ts +1 -1
- package/src/sdk/index.ts +4 -1
- package/src/sdk/providers/claude.ts +1 -1
- package/src/sdk/providers/copilot.ts +5 -3
- package/src/sdk/providers/opencode.ts +5 -3
- package/src/sdk/runtime/executor.ts +512 -301
- package/src/sdk/runtime/loader.ts +2 -2
- package/src/sdk/runtime/tmux.ts +31 -2
- package/src/sdk/types.ts +93 -20
- package/src/sdk/workflows.ts +7 -4
- package/src/services/config/definitions.ts +39 -2
- package/src/services/config/settings.ts +0 -6
- package/src/services/system/skills.ts +3 -7
- package/.atomic/workflows/package-lock.json +0 -31
- package/.atomic/workflows/package.json +0 -8
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ralph workflow for OpenCode — plan → orchestrate → review → debug loop.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each sub-agent invocation spawns its own visible session in the graph,
|
|
5
|
+
* so users can see each iteration's progress in real time. The loop
|
|
6
|
+
* terminates when:
|
|
7
7
|
* - {@link MAX_LOOPS} iterations have completed, OR
|
|
8
8
|
* - Two consecutive reviewer passes return zero findings.
|
|
9
9
|
*
|
|
10
|
-
* A loop is one cycle of plan → orchestrate → review. When a review returns
|
|
11
|
-
* zero findings on the FIRST pass we re-run only the reviewer (still inside
|
|
12
|
-
* the same loop iteration) to confirm; if that confirmation pass is also
|
|
13
|
-
* clean we stop. The debugger only runs when findings remain, and its
|
|
14
|
-
* markdown report is fed back into the next iteration's planner.
|
|
15
|
-
*
|
|
16
10
|
* Run: atomic workflow -n ralph -a opencode "<your spec>"
|
|
17
11
|
*/
|
|
18
12
|
|
|
19
13
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
20
|
-
import {
|
|
21
|
-
createOpencodeClient,
|
|
22
|
-
type SessionPromptResponse,
|
|
23
|
-
} from "@opencode-ai/sdk/v2";
|
|
14
|
+
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
24
15
|
|
|
25
16
|
import {
|
|
26
17
|
buildPlannerPrompt,
|
|
@@ -51,114 +42,186 @@ export default defineWorkflow({
|
|
|
51
42
|
description:
|
|
52
43
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
53
44
|
})
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
45
|
+
.run(async (ctx) => {
|
|
46
|
+
let consecutiveClean = 0;
|
|
47
|
+
let debuggerReport = "";
|
|
48
|
+
// Track the most recent session so the next stage can declare it as a
|
|
49
|
+
// dependency — this chains planner → orchestrator → reviewer → [confirm]
|
|
50
|
+
// → [debugger] → next planner in the graph instead of showing every
|
|
51
|
+
// stage as an independent sibling under the root.
|
|
52
|
+
let prevStage: string | undefined;
|
|
53
|
+
const depsOn = (): string[] | undefined =>
|
|
54
|
+
prevStage ? [prevStage] : undefined;
|
|
55
|
+
|
|
56
|
+
for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
|
|
57
|
+
// ── Plan ────────────────────────────────────────────────────────────
|
|
58
|
+
const plannerName = `planner-${iteration}`;
|
|
59
|
+
const planner = await ctx.session(
|
|
60
|
+
{ name: plannerName, dependsOn: depsOn() },
|
|
61
|
+
async (s) => {
|
|
62
|
+
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
63
|
+
const session = await client.session.create({
|
|
64
|
+
title: `planner-${iteration}`,
|
|
65
|
+
});
|
|
66
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
67
|
+
const result = await client.session.prompt({
|
|
68
|
+
sessionID: session.data!.id,
|
|
69
|
+
parts: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: buildPlannerPrompt(s.userPrompt, {
|
|
73
|
+
iteration,
|
|
74
|
+
debuggerReport: debuggerReport || undefined,
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
agent: "planner",
|
|
79
|
+
});
|
|
80
|
+
s.save(result.data!);
|
|
81
|
+
return extractResponseText(result.data!.parts);
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
prevStage = plannerName;
|
|
85
|
+
|
|
86
|
+
// ── Orchestrate ─────────────────────────────────────────────────────
|
|
87
|
+
const orchName = `orchestrator-${iteration}`;
|
|
88
|
+
await ctx.session(
|
|
89
|
+
{ name: orchName, dependsOn: depsOn() },
|
|
90
|
+
async (s) => {
|
|
91
|
+
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
92
|
+
const session = await client.session.create({
|
|
93
|
+
title: `orchestrator-${iteration}`,
|
|
94
|
+
});
|
|
95
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
96
|
+
const result = await client.session.prompt({
|
|
97
|
+
sessionID: session.data!.id,
|
|
98
|
+
parts: [
|
|
99
|
+
{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: buildOrchestratorPrompt(s.userPrompt, {
|
|
102
|
+
plannerNotes: planner.result,
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
agent: "orchestrator",
|
|
107
|
+
});
|
|
108
|
+
s.save(result.data!);
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
prevStage = orchName;
|
|
112
|
+
|
|
113
|
+
// ── Review (first pass) ─────────────────────────────────────────────
|
|
114
|
+
let gitStatus = await safeGitStatusS();
|
|
115
|
+
const reviewerName = `reviewer-${iteration}`;
|
|
116
|
+
const review = await ctx.session(
|
|
117
|
+
{ name: reviewerName, dependsOn: depsOn() },
|
|
118
|
+
async (s) => {
|
|
119
|
+
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
120
|
+
const session = await client.session.create({
|
|
121
|
+
title: `reviewer-${iteration}`,
|
|
122
|
+
});
|
|
123
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
124
|
+
const result = await client.session.prompt({
|
|
125
|
+
sessionID: session.data!.id,
|
|
126
|
+
parts: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: buildReviewPrompt(s.userPrompt, {
|
|
130
|
+
gitStatus,
|
|
131
|
+
iteration,
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
agent: "reviewer",
|
|
136
|
+
});
|
|
137
|
+
s.save(result.data!);
|
|
138
|
+
return extractResponseText(result.data!.parts);
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
prevStage = reviewerName;
|
|
142
|
+
|
|
143
|
+
let reviewRaw = review.result;
|
|
144
|
+
let parsed = parseReviewResult(reviewRaw);
|
|
145
|
+
|
|
146
|
+
if (!hasActionableFindings(parsed, reviewRaw)) {
|
|
147
|
+
consecutiveClean += 1;
|
|
148
|
+
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) break;
|
|
149
|
+
|
|
150
|
+
// Confirmation pass — re-run reviewer only
|
|
151
|
+
gitStatus = await safeGitStatusS();
|
|
152
|
+
const confirmName = `reviewer-${iteration}-confirm`;
|
|
153
|
+
const confirm = await ctx.session(
|
|
154
|
+
{ name: confirmName, dependsOn: depsOn() },
|
|
155
|
+
async (s) => {
|
|
156
|
+
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
157
|
+
const session = await client.session.create({
|
|
158
|
+
title: `reviewer-${iteration}-confirm`,
|
|
159
|
+
});
|
|
160
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
161
|
+
const result = await client.session.prompt({
|
|
162
|
+
sessionID: session.data!.id,
|
|
163
|
+
parts: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: buildReviewPrompt(s.userPrompt, {
|
|
167
|
+
gitStatus,
|
|
168
|
+
iteration,
|
|
169
|
+
isConfirmationPass: true,
|
|
170
|
+
}),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
agent: "reviewer",
|
|
174
|
+
});
|
|
175
|
+
s.save(result.data!);
|
|
176
|
+
return extractResponseText(result.data!.parts);
|
|
177
|
+
},
|
|
92
178
|
);
|
|
179
|
+
prevStage = confirmName;
|
|
93
180
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`orchestrator-${iteration}`,
|
|
97
|
-
"orchestrator",
|
|
98
|
-
buildOrchestratorPrompt(),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// ── Review (first pass) ─────────────────────────────────────────────
|
|
102
|
-
let gitStatus = await safeGitStatusS();
|
|
103
|
-
let reviewRaw = await runAgent(
|
|
104
|
-
`reviewer-${iteration}-1`,
|
|
105
|
-
"reviewer",
|
|
106
|
-
buildReviewPrompt(ctx.userPrompt, { gitStatus, iteration }),
|
|
107
|
-
);
|
|
108
|
-
let parsed = parseReviewResult(reviewRaw);
|
|
181
|
+
reviewRaw = confirm.result;
|
|
182
|
+
parsed = parseReviewResult(reviewRaw);
|
|
109
183
|
|
|
110
184
|
if (!hasActionableFindings(parsed, reviewRaw)) {
|
|
111
185
|
consecutiveClean += 1;
|
|
112
|
-
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD)
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Confirmation pass — re-run reviewer only, NOT plan/orchestrate.
|
|
117
|
-
gitStatus = await safeGitStatusS();
|
|
118
|
-
reviewRaw = await runAgent(
|
|
119
|
-
`reviewer-${iteration}-2`,
|
|
120
|
-
"reviewer",
|
|
121
|
-
buildReviewPrompt(ctx.userPrompt, {
|
|
122
|
-
gitStatus,
|
|
123
|
-
iteration,
|
|
124
|
-
isConfirmationPass: true,
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
127
|
-
parsed = parseReviewResult(reviewRaw);
|
|
128
|
-
|
|
129
|
-
if (!hasActionableFindings(parsed, reviewRaw)) {
|
|
130
|
-
consecutiveClean += 1;
|
|
131
|
-
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) {
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
consecutiveClean = 0;
|
|
136
|
-
// fall through to debugger
|
|
137
|
-
}
|
|
186
|
+
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) break;
|
|
138
187
|
} else {
|
|
139
188
|
consecutiveClean = 0;
|
|
140
189
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
hasActionableFindings(parsed, reviewRaw) &&
|
|
145
|
-
iteration < MAX_LOOPS
|
|
146
|
-
) {
|
|
147
|
-
const debuggerRaw = await runAgent(
|
|
148
|
-
`debugger-${iteration}`,
|
|
149
|
-
"debugger",
|
|
150
|
-
buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
151
|
-
iteration,
|
|
152
|
-
gitStatus,
|
|
153
|
-
}),
|
|
154
|
-
);
|
|
155
|
-
debuggerReport = extractMarkdownBlock(debuggerRaw);
|
|
156
|
-
}
|
|
190
|
+
} else {
|
|
191
|
+
consecutiveClean = 0;
|
|
157
192
|
}
|
|
158
193
|
|
|
159
|
-
|
|
160
|
-
|
|
194
|
+
// ── Debug (only if findings remain AND another iteration is allowed) ─
|
|
195
|
+
if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
|
|
196
|
+
const debuggerName = `debugger-${iteration}`;
|
|
197
|
+
const debugger_ = await ctx.session(
|
|
198
|
+
{ name: debuggerName, dependsOn: depsOn() },
|
|
199
|
+
async (s) => {
|
|
200
|
+
const client = createOpencodeClient({ baseUrl: s.serverUrl });
|
|
201
|
+
const session = await client.session.create({
|
|
202
|
+
title: `debugger-${iteration}`,
|
|
203
|
+
});
|
|
204
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
205
|
+
const result = await client.session.prompt({
|
|
206
|
+
sessionID: session.data!.id,
|
|
207
|
+
parts: [
|
|
208
|
+
{
|
|
209
|
+
type: "text",
|
|
210
|
+
text: buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
211
|
+
iteration,
|
|
212
|
+
gitStatus,
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
agent: "debugger",
|
|
217
|
+
});
|
|
218
|
+
s.save(result.data!);
|
|
219
|
+
return extractResponseText(result.data!.parts);
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
prevStage = debuggerName;
|
|
223
|
+
debuggerReport = extractMarkdownBlock(debugger_.result);
|
|
161
224
|
}
|
|
162
|
-
}
|
|
225
|
+
}
|
|
163
226
|
})
|
|
164
227
|
.compile();
|
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ Atomic is an open-source **multi-agent harness** that orchestrates **Claude Code
|
|
|
31
31
|
- [Workflow SDK — Build Your Own Harness](#workflow-sdk--build-your-own-harness)
|
|
32
32
|
- [Builder API](#builder-api)
|
|
33
33
|
- [Session Context (`ctx`)](#session-context-ctx)
|
|
34
|
+
- [Session Options (`SessionRunOptions`)](#session-options-sessionrunoptions)
|
|
34
35
|
- [Saving Transcripts](#saving-transcripts)
|
|
35
36
|
- [Provider Helpers](#provider-helpers)
|
|
36
37
|
- [Key Rules](#key-rules)
|
|
@@ -44,6 +45,8 @@ Atomic is an open-source **multi-agent harness** that orchestrates **Claude Code
|
|
|
44
45
|
- [Why Research → Plan → Implement → Verify Works](#why-research--plan--implement--verify-works)
|
|
45
46
|
- [Commands Reference](#commands-reference)
|
|
46
47
|
- [CLI Commands](#cli-commands)
|
|
48
|
+
- [Global Flags](#global-flags)
|
|
49
|
+
- [`atomic init` Flags](#atomic-init-flags)
|
|
47
50
|
- [`atomic chat` Flags](#atomic-chat-flags)
|
|
48
51
|
- [`atomic workflow` Flags](#atomic-workflow-flags)
|
|
49
52
|
- [Atomic-Provided Skills (invokable from any agent chat)](#atomic-provided-skills-invokable-from-any-agent-chat)
|
|
@@ -220,7 +223,7 @@ Each agent gets its own configuration directory (`.claude/`, `.opencode/`, `.git
|
|
|
220
223
|
|
|
221
224
|
### Workflow SDK — Build Your Own Harness
|
|
222
225
|
|
|
223
|
-
Every team has a process — triage bugs this way, ship features that way, review PRs with these checks. Most of it lives in a wiki nobody reads or in one senior engineer's head. The **Workflow SDK** (`@bastani/atomic/workflows`) lets you encode that process as
|
|
226
|
+
Every team has a process — triage bugs this way, ship features that way, review PRs with these checks. Most of it lives in a wiki nobody reads or in one senior engineer's head. The **Workflow SDK** (`@bastani/atomic/workflows`) lets you encode that process as TypeScript — spawn agent sessions dynamically with native control flow (`for`, `if`, `Promise.all()`), and watch them appear in a live graph as they execute.
|
|
224
227
|
|
|
225
228
|
Drop a `.ts` file in `.atomic/workflows/<name>/<agent>/index.ts` and run it:
|
|
226
229
|
|
|
@@ -239,31 +242,28 @@ export default defineWorkflow({
|
|
|
239
242
|
name: "hello",
|
|
240
243
|
description: "Two-session Claude demo: describe → summarize",
|
|
241
244
|
})
|
|
242
|
-
.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
});
|
|
265
|
-
ctx.save(ctx.sessionId);
|
|
266
|
-
},
|
|
245
|
+
.run(async (ctx) => {
|
|
246
|
+
const describe = await ctx.session(
|
|
247
|
+
{ name: "describe", description: "Ask Claude to describe the project" },
|
|
248
|
+
async (s) => {
|
|
249
|
+
await createClaudeSession({ paneId: s.paneId });
|
|
250
|
+
await claudeQuery({ paneId: s.paneId, prompt: s.userPrompt });
|
|
251
|
+
s.save(s.sessionId);
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await ctx.session(
|
|
256
|
+
{ name: "summarize", description: "Summarize the previous session's output" },
|
|
257
|
+
async (s) => {
|
|
258
|
+
const research = await s.transcript(describe);
|
|
259
|
+
await createClaudeSession({ paneId: s.paneId });
|
|
260
|
+
await claudeQuery({
|
|
261
|
+
paneId: s.paneId,
|
|
262
|
+
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
263
|
+
});
|
|
264
|
+
s.save(s.sessionId);
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
267
|
})
|
|
268
268
|
.compile();
|
|
269
269
|
```
|
|
@@ -272,13 +272,16 @@ export default defineWorkflow({
|
|
|
272
272
|
|
|
273
273
|
**Key capabilities:**
|
|
274
274
|
|
|
275
|
-
| Capability
|
|
276
|
-
|
|
|
277
|
-
| **
|
|
278
|
-
| **
|
|
279
|
-
| **
|
|
280
|
-
| **
|
|
281
|
-
| **
|
|
275
|
+
| Capability | Description |
|
|
276
|
+
| ---------------------------- | ------------------------------------------------------------------------------------ |
|
|
277
|
+
| **Dynamic session spawning** | Call `ctx.session()` to spawn sessions at runtime — each gets its own tmux window and graph node |
|
|
278
|
+
| **Native TypeScript control flow** | Use `for`, `if/else`, `Promise.all()`, `try/catch` — no framework DSL needed |
|
|
279
|
+
| **Session return values** | Session callbacks can return data: `const h = await ctx.session(...); h.result` |
|
|
280
|
+
| **Transcript passing** | Access prior session output via handle (`s.transcript(handle)`) or name (`s.transcript("name")`) |
|
|
281
|
+
| **Nested sub-sessions** | Call `s.session()` inside a session callback to spawn child sessions — visible as nested nodes in the graph |
|
|
282
|
+
| **Dependency tracking** | Use `dependsOn: ["name"]` to declare session ordering — the runtime waits and the graph shows the edges |
|
|
283
|
+
| **Provider-agnostic** | Write raw SDK code for Claude, Copilot, or OpenCode inside each session callback |
|
|
284
|
+
| **Live graph visualization** | Sessions appear in the TUI graph as they're spawned — loops and conditionals are visible in real time |
|
|
282
285
|
|
|
283
286
|
Drop a `.ts` file in `.atomic/workflows/<name>/<agent>/` (project-local) or `~/.atomic/workflows/` (global). You can also ask Atomic to create workflows for you:
|
|
284
287
|
|
|
@@ -294,22 +297,51 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
294
297
|
| Method | Purpose |
|
|
295
298
|
| --------------------------------------- | ----------------------------------------------------------------- |
|
|
296
299
|
| `defineWorkflow({ name, description })` | Entry point — returns a `WorkflowBuilder` |
|
|
297
|
-
| `.
|
|
300
|
+
| `.run(async (ctx) => { ... })` | Set the workflow's entry point — `ctx` is a `WorkflowContext` |
|
|
298
301
|
| `.compile()` | **Required** — terminal method that seals the workflow definition |
|
|
299
302
|
|
|
300
|
-
####
|
|
303
|
+
#### WorkflowContext (`ctx`) — top-level orchestrator
|
|
301
304
|
|
|
302
305
|
| Property | Type | Description |
|
|
303
306
|
| ----------------------- | ------------------------- | -------------------------------------------------------------- |
|
|
304
307
|
| `ctx.userPrompt` | `string` | Original user prompt from the CLI invocation |
|
|
305
308
|
| `ctx.agent` | `AgentType` | Which agent is running (`"claude"`, `"copilot"`, `"opencode"`) |
|
|
306
|
-
| `ctx.
|
|
307
|
-
| `ctx.
|
|
308
|
-
| `ctx.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
|
309
|
+
| `ctx.session(opts, fn)` | `Promise<SessionHandle<T>>` | Spawn a session — returns handle with `name`, `id`, `result` |
|
|
310
|
+
| `ctx.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript (`{ path, content }`) |
|
|
311
|
+
| `ctx.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
312
|
+
|
|
313
|
+
#### SessionContext (`s`) — inside each session callback
|
|
314
|
+
|
|
315
|
+
| Property | Type | Description |
|
|
316
|
+
| ----------------------- | ------------------------- | -------------------------------------------------------------- |
|
|
317
|
+
| `s.serverUrl` | `string` | The agent's server URL |
|
|
318
|
+
| `s.userPrompt` | `string` | Original user prompt from the CLI invocation |
|
|
319
|
+
| `s.agent` | `AgentType` | Which agent is running |
|
|
320
|
+
| `s.paneId` | `string` | tmux pane ID for this session |
|
|
321
|
+
| `s.sessionId` | `string` | Session UUID |
|
|
322
|
+
| `s.sessionDir` | `string` | Path to this session's storage directory on disk |
|
|
323
|
+
| `s.save(messages)` | `SaveTranscript` | Save this session's output for subsequent sessions |
|
|
324
|
+
| `s.transcript(ref)` | `Promise<Transcript>` | Get a completed session's transcript |
|
|
325
|
+
| `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
326
|
+
| `s.session(opts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph) |
|
|
327
|
+
|
|
328
|
+
#### Session Options (`SessionRunOptions`)
|
|
329
|
+
|
|
330
|
+
| Property | Type | Description |
|
|
331
|
+
| ------------- | ---------- | ----------------------------------------------------------------------------- |
|
|
332
|
+
| `name` | `string` | Unique session name within the workflow run |
|
|
333
|
+
| `description` | `string?` | Human-readable description shown in the graph |
|
|
334
|
+
| `dependsOn` | `string[]?`| Names of sessions that must complete before this one starts (creates graph edges) |
|
|
335
|
+
|
|
336
|
+
`dependsOn` is useful when spawning sessions with `Promise.all()` — it lets the runtime enforce ordering while still allowing parallel spawning of independent sessions:
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
await Promise.all([
|
|
340
|
+
ctx.session({ name: "migrate-db" }, async (s) => { /* ... */ }),
|
|
341
|
+
ctx.session({ name: "seed-data", dependsOn: ["migrate-db"] }, async (s) => { /* ... */ }),
|
|
342
|
+
ctx.session({ name: "gen-types", dependsOn: ["migrate-db"] }, async (s) => { /* ... */ }),
|
|
343
|
+
]);
|
|
344
|
+
```
|
|
313
345
|
|
|
314
346
|
#### Saving Transcripts
|
|
315
347
|
|
|
@@ -317,27 +349,49 @@ Each provider saves transcripts differently:
|
|
|
317
349
|
|
|
318
350
|
| Provider | How to Save |
|
|
319
351
|
| ------------ | ------------------------------------------------------------------ |
|
|
320
|
-
| **Claude** | `
|
|
321
|
-
| **Copilot** | `
|
|
322
|
-
| **OpenCode** | `
|
|
352
|
+
| **Claude** | `s.save(s.sessionId)` — auto-reads via `getSessionMessages()` |
|
|
353
|
+
| **Copilot** | `s.save(await session.getMessages())` — pass `SessionEvent[]` |
|
|
354
|
+
| **OpenCode** | `s.save(result.data!)` — pass the full `{ info, parts }` response |
|
|
323
355
|
|
|
324
356
|
#### Provider Helpers
|
|
325
357
|
|
|
326
358
|
| Export | Purpose |
|
|
327
359
|
| --------------------------------- | --------------------------------------------------- |
|
|
328
|
-
| `createClaudeSession(
|
|
329
|
-
| `claudeQuery(
|
|
330
|
-
| `clearClaudeSession(
|
|
331
|
-
| `validateClaudeWorkflow()` | Validate a Claude workflow
|
|
332
|
-
| `validateCopilotWorkflow()` | Validate a Copilot workflow
|
|
333
|
-
| `validateOpenCodeWorkflow()` | Validate an OpenCode workflow
|
|
360
|
+
| `createClaudeSession(options)` | Start a Claude TUI in a tmux pane |
|
|
361
|
+
| `claudeQuery(options)` | Send a prompt to Claude and wait for the response |
|
|
362
|
+
| `clearClaudeSession(paneId)` | Free memory for a killed/finished Claude session |
|
|
363
|
+
| `validateClaudeWorkflow()` | Validate a Claude workflow source before run |
|
|
364
|
+
| `validateCopilotWorkflow()` | Validate a Copilot workflow source before run |
|
|
365
|
+
| `validateOpenCodeWorkflow()` | Validate an OpenCode workflow source before run |
|
|
366
|
+
|
|
367
|
+
`createClaudeSession` accepts:
|
|
368
|
+
|
|
369
|
+
| Option | Type | Default | Description |
|
|
370
|
+
| ----------------- | ---------- | ----------------------------------------------------- | ---------------------------------- |
|
|
371
|
+
| `paneId` | `string` | — | tmux pane ID (required) |
|
|
372
|
+
| `chatFlags` | `string[]` | `["--dangerously-skip-permissions"]` | CLI flags passed to `claude` |
|
|
373
|
+
| `readyTimeoutMs` | `number` | `30000` | Timeout waiting for TUI readiness |
|
|
374
|
+
|
|
375
|
+
`claudeQuery` accepts:
|
|
376
|
+
|
|
377
|
+
| Option | Type | Default | Description |
|
|
378
|
+
| ----------------- | -------- | -------- | ------------------------------------------------ |
|
|
379
|
+
| `paneId` | `string` | — | tmux pane ID (required) |
|
|
380
|
+
| `prompt` | `string` | — | The prompt to send (required) |
|
|
381
|
+
| `timeoutMs` | `number` | `300000` | Response timeout (5 min) |
|
|
382
|
+
| `pollIntervalMs` | `number` | `2000` | Polling interval for output stabilization |
|
|
383
|
+
| `submitPresses` | `number` | `1` | C-m presses per submit round |
|
|
384
|
+
| `maxSubmitRounds` | `number` | `6` | Max retry rounds for delivery confirmation |
|
|
385
|
+
| `readyTimeoutMs` | `number` | `30000` | Pane readiness timeout before sending |
|
|
386
|
+
|
|
387
|
+
Returns `{ output: string; delivered: boolean }` — `delivered` confirms the prompt was accepted by the agent.
|
|
334
388
|
|
|
335
389
|
#### Key Rules
|
|
336
390
|
|
|
337
|
-
1. Every workflow file must use `export default` with `.
|
|
338
|
-
2. Session names must be unique within a workflow
|
|
339
|
-
3.
|
|
340
|
-
4. Each session runs in its own tmux
|
|
391
|
+
1. Every workflow file must use `export default` with `.run()` and `.compile()`
|
|
392
|
+
2. Session names must be unique within a workflow run
|
|
393
|
+
3. `transcript()` / `getMessages()` only access completed sessions (callback returned + saves flushed)
|
|
394
|
+
4. Each session runs in its own tmux window with the chosen agent
|
|
341
395
|
5. Workflows are organized per-workflow: `.atomic/workflows/<name>/<agent>/index.ts`
|
|
342
396
|
|
|
343
397
|
Workflow files need no `package.json` or `node_modules` of their own — the Atomic loader rewrites `@bastani/atomic/*` and atomic's transitive deps (`@github/copilot-sdk`, `@opencode-ai/sdk`, `@anthropic-ai/claude-agent-sdk`, `zod`, etc.) to absolute paths inside the installed atomic package at load time. Drop a `.ts` file and it runs.
|
|
@@ -410,8 +464,10 @@ The [Ralph Wiggum Method](https://ghuntley.com/ralph/) enables **multi-hour auto
|
|
|
410
464
|
**How Ralph works:**
|
|
411
465
|
|
|
412
466
|
1. **Task Decomposition** — A `planner` sub-agent breaks your spec into a structured task list with dependency tracking, stored in a SQLite database with WAL mode for parallel access
|
|
413
|
-
2. **
|
|
414
|
-
3. **Review & Debug** — A `reviewer` sub-agent audits the implementation; if
|
|
467
|
+
2. **Orchestration** — An `orchestrator` sub-agent retrieves the task list, validates the dependency graph, and dispatches `worker` sub-agents for ready tasks with concurrent execution of independent tasks
|
|
468
|
+
3. **Review & Debug** — A `reviewer` sub-agent audits the implementation with structured JSON output; if actionable findings exist (P0–P2 severity), a `debugger` sub-agent investigates root causes and produces a markdown report that feeds back to the planner on the next iteration
|
|
469
|
+
|
|
470
|
+
**Loop configuration:** Ralph runs up to **10 iterations** and exits early after **2 consecutive clean reviews** (zero actionable findings). P3 (minor) findings are filtered as non-actionable.
|
|
415
471
|
|
|
416
472
|
```bash
|
|
417
473
|
# From a prompt
|
|
@@ -601,7 +657,7 @@ During `atomic workflow` execution, Atomic renders a live orchestrator panel bui
|
|
|
601
657
|
- **Session graph** — Nodes for each `.session()` call with status (pending, running, completed, failed) and edges for sequential / parallel dependencies
|
|
602
658
|
- **Task list tracking** — Ralph's decomposed task list with dependency arrows, updated in real time as workers complete tasks
|
|
603
659
|
- **Pane previews** — Thumbnail of each tmux pane so you can see what every agent is doing without switching contexts
|
|
604
|
-
- **Transcript passing visibility** — Highlights `
|
|
660
|
+
- **Transcript passing visibility** — Highlights `s.save()` / `s.transcript()` handoffs as they happen between sessions
|
|
605
661
|
|
|
606
662
|
During `atomic chat`, there is no Atomic-owned TUI — `atomic chat -a <agent>` spawns the native agent CLI inside a tmux/psmux session, so all chat features (streaming, `@` mentions, `/slash-commands`, model selection, theme switching, keyboard shortcuts) come from the agent CLI itself. Atomic's role in chat mode is to handle config sync, tmux session management, and argument passthrough.
|
|
607
663
|
|
|
@@ -659,6 +715,29 @@ This is also why the cycle is iterative. Research and specs become persistent co
|
|
|
659
715
|
| `atomic workflow` | Run a multi-session agent workflow with the Atomic orchestrator panel |
|
|
660
716
|
| `atomic config set <k> <v>` | Set configuration values (currently supports `telemetry`) |
|
|
661
717
|
|
|
718
|
+
#### Global Flags
|
|
719
|
+
|
|
720
|
+
These flags are available on all commands:
|
|
721
|
+
|
|
722
|
+
| Flag | Description |
|
|
723
|
+
| --------------- | -------------------------------------------- |
|
|
724
|
+
| `-y, --yes` | Auto-confirm all prompts (non-interactive) |
|
|
725
|
+
| `--no-banner` | Skip ASCII banner display |
|
|
726
|
+
| `-v, --version` | Show version number |
|
|
727
|
+
|
|
728
|
+
#### `atomic init` Flags
|
|
729
|
+
|
|
730
|
+
| Flag | Description |
|
|
731
|
+
| -------------------- | ---------------------------------------------- |
|
|
732
|
+
| `-a, --agent <name>` | Pre-select agent: `claude`, `opencode`, `copilot` |
|
|
733
|
+
| `-s, --scm <name>` | Pre-select SCM: `github`, `sapling` |
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
atomic init # Interactive setup
|
|
737
|
+
atomic init -a claude -s github # Pre-select agent and SCM
|
|
738
|
+
atomic init --yes # Auto-confirm all prompts
|
|
739
|
+
```
|
|
740
|
+
|
|
662
741
|
#### `atomic chat` Flags
|
|
663
742
|
|
|
664
743
|
| Flag | Description |
|
|
@@ -751,7 +830,7 @@ Created automatically during `atomic init`. Resolution order:
|
|
|
751
830
|
<summary>Install a specific version</summary>
|
|
752
831
|
|
|
753
832
|
```bash
|
|
754
|
-
bun install -g @bastani/atomic@0.5.0-
|
|
833
|
+
bun install -g @bastani/atomic@0.5.0-4 # replace with desired version
|
|
755
834
|
```
|
|
756
835
|
|
|
757
836
|
List all published versions with `npm view @bastani/atomic versions`.
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -30,7 +30,6 @@ export function createProgram() {
|
|
|
30
30
|
.version(VERSION, "-v, --version", "Show version number")
|
|
31
31
|
|
|
32
32
|
// Global options available to all commands
|
|
33
|
-
.option("-f, --force", "Overwrite all config files")
|
|
34
33
|
.option("-y, --yes", "Auto-confirm all prompts (non-interactive mode)")
|
|
35
34
|
.option("--no-banner", "Skip ASCII banner display")
|
|
36
35
|
|
|
@@ -68,7 +67,6 @@ export function createProgram() {
|
|
|
68
67
|
showBanner: globalOpts.banner !== false,
|
|
69
68
|
preSelectedAgent: localOpts.agent as AgentKey | undefined,
|
|
70
69
|
preSelectedScm: localOpts.scm as SourceControlType | undefined,
|
|
71
|
-
force: globalOpts.force,
|
|
72
70
|
yes: globalOpts.yes,
|
|
73
71
|
});
|
|
74
72
|
});
|