@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.
Files changed (42) hide show
  1. package/.atomic/workflows/hello/claude/index.ts +22 -25
  2. package/.atomic/workflows/hello/copilot/index.ts +41 -31
  3. package/.atomic/workflows/hello/opencode/index.ts +40 -40
  4. package/.atomic/workflows/hello-parallel/claude/index.ts +54 -54
  5. package/.atomic/workflows/hello-parallel/copilot/index.ts +89 -70
  6. package/.atomic/workflows/hello-parallel/opencode/index.ts +77 -77
  7. package/.atomic/workflows/ralph/claude/index.ts +128 -93
  8. package/.atomic/workflows/ralph/copilot/index.ts +212 -112
  9. package/.atomic/workflows/ralph/helpers/prompts.ts +45 -2
  10. package/.atomic/workflows/ralph/opencode/index.ts +174 -111
  11. package/README.md +138 -59
  12. package/package.json +1 -1
  13. package/src/cli.ts +0 -2
  14. package/src/commands/cli/chat/index.ts +28 -8
  15. package/src/commands/cli/init/index.ts +7 -10
  16. package/src/commands/cli/init/scm.ts +27 -10
  17. package/src/sdk/components/connectors.test.ts +45 -0
  18. package/src/sdk/components/layout.test.ts +321 -0
  19. package/src/sdk/components/layout.ts +51 -15
  20. package/src/sdk/components/orchestrator-panel-contexts.ts +13 -4
  21. package/src/sdk/components/orchestrator-panel-store.test.ts +156 -0
  22. package/src/sdk/components/orchestrator-panel-store.ts +24 -0
  23. package/src/sdk/components/orchestrator-panel.tsx +21 -0
  24. package/src/sdk/components/session-graph-panel.tsx +8 -15
  25. package/src/sdk/components/statusline.tsx +4 -6
  26. package/src/sdk/define-workflow.test.ts +71 -0
  27. package/src/sdk/define-workflow.ts +42 -39
  28. package/src/sdk/errors.ts +1 -1
  29. package/src/sdk/index.ts +4 -1
  30. package/src/sdk/providers/claude.ts +1 -1
  31. package/src/sdk/providers/copilot.ts +5 -3
  32. package/src/sdk/providers/opencode.ts +5 -3
  33. package/src/sdk/runtime/executor.ts +512 -301
  34. package/src/sdk/runtime/loader.ts +2 -2
  35. package/src/sdk/runtime/tmux.ts +31 -2
  36. package/src/sdk/types.ts +93 -20
  37. package/src/sdk/workflows.ts +7 -4
  38. package/src/services/config/definitions.ts +39 -2
  39. package/src/services/config/settings.ts +0 -6
  40. package/src/services/system/skills.ts +3 -7
  41. package/.atomic/workflows/package-lock.json +0 -31
  42. 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
- * One OpenCode client backs every iteration; each loop step creates a fresh
5
- * sub-session bound to the appropriate sub-agent (planner, orchestrator,
6
- * reviewer, debugger). The loop terminates when:
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
- .session({
55
- name: "ralph-loop",
56
- description:
57
- "Drive plan/orchestrate/review/debug iterations until clean or capped",
58
- run: async (ctx) => {
59
- const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
60
-
61
- let lastResultData: SessionPromptResponse | null = null;
62
-
63
- /** Run a sub-agent in a fresh session and return its concatenated text. */
64
- async function runAgent(
65
- title: string,
66
- agent: string,
67
- text: string,
68
- ): Promise<string> {
69
- const session = await client.session.create({ title });
70
- await client.tui.selectSession({ sessionID: session.data!.id });
71
- const result = await client.session.prompt({
72
- sessionID: session.data!.id,
73
- parts: [{ type: "text", text }],
74
- agent,
75
- });
76
- lastResultData = result.data ?? null;
77
- return extractResponseText(result.data!.parts);
78
- }
79
-
80
- let consecutiveClean = 0;
81
- let debuggerReport = "";
82
-
83
- for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
84
- // ── Plan ────────────────────────────────────────────────────────────
85
- await runAgent(
86
- `planner-${iteration}`,
87
- "planner",
88
- buildPlannerPrompt(ctx.userPrompt, {
89
- iteration,
90
- debuggerReport: debuggerReport || undefined,
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
- // ── Orchestrate ─────────────────────────────────────────────────────
95
- await runAgent(
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
- // ── Debug (only if findings remain AND another iteration is allowed) ─
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
- if (lastResultData !== null) {
160
- ctx.save(lastResultData);
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 a chain of named sessions with raw provider SDK code then run it from the CLI.
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
- .session({
243
- name: "describe",
244
- description: "Ask Claude to describe the project",
245
- run: async (ctx) => {
246
- await createClaudeSession({ paneId: ctx.paneId });
247
- await claudeQuery({
248
- paneId: ctx.paneId,
249
- prompt: ctx.userPrompt,
250
- });
251
- ctx.save(ctx.sessionId);
252
- },
253
- })
254
- .session({
255
- name: "summarize",
256
- description: "Summarize the previous session's output",
257
- run: async (ctx) => {
258
- await createClaudeSession({ paneId: ctx.paneId });
259
- const research = await ctx.transcript("describe");
260
-
261
- await claudeQuery({
262
- paneId: ctx.paneId,
263
- prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
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 | Description |
276
- | ------------------------ | ------------------------------------------------------------------------------------ |
277
- | **Sequential sessions** | Chain `.session()` calls that execute in order, each in its own tmux pane |
278
- | **Transcript passing** | Access previous session output via `ctx.transcript(name)` or `ctx.getMessages(name)` |
279
- | **Provider-agnostic** | Write raw SDK code for Claude, Copilot, or OpenCode inside each session's `run()` |
280
- | **tmux-based execution** | Each session runs in its own tmux pane for isolation and observability |
281
- | **Native SDK access** | Use `createClaudeSession`, `claudeQuery`, Copilot SDK, or OpenCode SDK directly |
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
- | `.session({ name, description?, run })` | Add a named session (or pass an array for parallel execution) |
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
- #### Session Context (`ctx`)
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.serverUrl` | `string` | The agent's server URL |
307
- | `ctx.paneId` | `string` | tmux pane ID for this session |
308
- | `ctx.sessionId` | `string` | Session UUID |
309
- | `ctx.sessionDir` | `string` | Path to this session's storage directory on disk |
310
- | `ctx.transcript(name)` | `Promise<Transcript>` | Get a previous session's transcript (`{ path, content }`) |
311
- | `ctx.getMessages(name)` | `Promise<SavedMessage[]>` | Get a previous session's raw native messages |
312
- | `ctx.save(messages)` | `SaveTranscript` | Save this session's output for subsequent sessions |
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** | `ctx.save(ctx.sessionId)` — auto-reads via `getSessionMessages()` |
321
- | **Copilot** | `ctx.save(await session.getMessages())` — pass `SessionEvent[]` |
322
- | **OpenCode** | `ctx.save(result.data)` — pass the full `{ info, parts }` response |
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({ paneId })` | Start a Claude TUI in a tmux pane |
329
- | `claudeQuery({ paneId, prompt })` | Send a prompt to Claude and wait for the response |
330
- | `clearClaudeSession({ paneId })` | Clear the current Claude session |
331
- | `validateClaudeWorkflow()` | Validate a Claude workflow definition before run |
332
- | `validateCopilotWorkflow()` | Validate a Copilot workflow definition before run |
333
- | `validateOpenCodeWorkflow()` | Validate an OpenCode workflow definition before run |
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 `.compile()` at the end
338
- 2. Session names must be unique within a workflow
339
- 3. Sessions execute sequentially in the order they are defined
340
- 4. Each session runs in its own tmux pane with the chosen agent
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. **Worker Loop** — Dispatches `worker` sub-agents for ready tasks, executing up to 100 iterations with concurrent execution of independent tasks
414
- 3. **Review & Debug** — A `reviewer` sub-agent audits the implementation; if issues are found, a `debugger` sub-agent generates a report that feeds back to the planner on the next iteration
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 `ctx.save()` / `ctx.transcript()` handoffs as they happen between sessions
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-1 # replace with desired version
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.5.0-3",
3
+ "version": "0.5.0-5",
4
4
  "description": "Configuration management CLI and SDK for coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
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
  });