@bastani/atomic 0.5.0-4 → 0.5.0
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/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)
|
|
@@ -275,6 +278,8 @@ export default defineWorkflow({
|
|
|
275
278
|
| **Native TypeScript control flow** | Use `for`, `if/else`, `Promise.all()`, `try/catch` — no framework DSL needed |
|
|
276
279
|
| **Session return values** | Session callbacks can return data: `const h = await ctx.session(...); h.result` |
|
|
277
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 |
|
|
278
283
|
| **Provider-agnostic** | Write raw SDK code for Claude, Copilot, or OpenCode inside each session callback |
|
|
279
284
|
| **Live graph visualization** | Sessions appear in the TUI graph as they're spawned — loops and conditionals are visible in real time |
|
|
280
285
|
|
|
@@ -320,6 +325,24 @@ Use your workflow-creator skill to create a workflow that plans, implements, and
|
|
|
320
325
|
| `s.getMessages(ref)` | `Promise<SavedMessage[]>` | Get a completed session's raw native messages |
|
|
321
326
|
| `s.session(opts, fn)` | `Promise<SessionHandle<T>>` | Spawn a nested sub-session (child in the graph) |
|
|
322
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
|
+
```
|
|
345
|
+
|
|
323
346
|
#### Saving Transcripts
|
|
324
347
|
|
|
325
348
|
Each provider saves transcripts differently:
|
|
@@ -334,13 +357,35 @@ Each provider saves transcripts differently:
|
|
|
334
357
|
|
|
335
358
|
| Export | Purpose |
|
|
336
359
|
| --------------------------------- | --------------------------------------------------- |
|
|
337
|
-
| `createClaudeSession(
|
|
338
|
-
| `claudeQuery(
|
|
339
|
-
| `clearClaudeSession(paneId)` |
|
|
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 |
|
|
340
363
|
| `validateClaudeWorkflow()` | Validate a Claude workflow source before run |
|
|
341
364
|
| `validateCopilotWorkflow()` | Validate a Copilot workflow source before run |
|
|
342
365
|
| `validateOpenCodeWorkflow()` | Validate an OpenCode workflow source before run |
|
|
343
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.
|
|
388
|
+
|
|
344
389
|
#### Key Rules
|
|
345
390
|
|
|
346
391
|
1. Every workflow file must use `export default` with `.run()` and `.compile()`
|
|
@@ -419,8 +464,10 @@ The [Ralph Wiggum Method](https://ghuntley.com/ralph/) enables **multi-hour auto
|
|
|
419
464
|
**How Ralph works:**
|
|
420
465
|
|
|
421
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
|
|
422
|
-
2. **
|
|
423
|
-
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.
|
|
424
471
|
|
|
425
472
|
```bash
|
|
426
473
|
# From a prompt
|
|
@@ -610,7 +657,7 @@ During `atomic workflow` execution, Atomic renders a live orchestrator panel bui
|
|
|
610
657
|
- **Session graph** — Nodes for each `.session()` call with status (pending, running, completed, failed) and edges for sequential / parallel dependencies
|
|
611
658
|
- **Task list tracking** — Ralph's decomposed task list with dependency arrows, updated in real time as workers complete tasks
|
|
612
659
|
- **Pane previews** — Thumbnail of each tmux pane so you can see what every agent is doing without switching contexts
|
|
613
|
-
- **Transcript passing visibility** — Highlights `
|
|
660
|
+
- **Transcript passing visibility** — Highlights `s.save()` / `s.transcript()` handoffs as they happen between sessions
|
|
614
661
|
|
|
615
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.
|
|
616
663
|
|
|
@@ -668,6 +715,29 @@ This is also why the cycle is iterative. Research and specs become persistent co
|
|
|
668
715
|
| `atomic workflow` | Run a multi-session agent workflow with the Atomic orchestrator panel |
|
|
669
716
|
| `atomic config set <k> <v>` | Set configuration values (currently supports `telemetry`) |
|
|
670
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
|
+
|
|
671
741
|
#### `atomic chat` Flags
|
|
672
742
|
|
|
673
743
|
| Flag | Description |
|
|
@@ -760,7 +830,7 @@ Created automatically during `atomic init`. Resolution order:
|
|
|
760
830
|
<summary>Install a specific version</summary>
|
|
761
831
|
|
|
762
832
|
```bash
|
|
763
|
-
bun install -g @bastani/atomic@0.5.0-
|
|
833
|
+
bun install -g @bastani/atomic@0.5.0-4 # replace with desired version
|
|
764
834
|
```
|
|
765
835
|
|
|
766
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
|
});
|
|
@@ -167,8 +167,6 @@ interface InitOptions {
|
|
|
167
167
|
/** Pre-selected source control type (skip SCM selection prompt) */
|
|
168
168
|
preSelectedScm?: SourceControlType;
|
|
169
169
|
configNotFoundMessage?: string;
|
|
170
|
-
/** Force overwrite of preserved files (bypass preservation/merge logic) */
|
|
171
|
-
force?: boolean;
|
|
172
170
|
/** Auto-confirm all prompts (non-interactive mode for CI/testing) */
|
|
173
171
|
yes?: boolean;
|
|
174
172
|
/**
|
|
@@ -330,10 +328,7 @@ export async function initCommand(options: InitOptions = {}): Promise<void> {
|
|
|
330
328
|
const targetFolder = join(targetDir, agent.folder);
|
|
331
329
|
const folderExists = await pathExists(targetFolder);
|
|
332
330
|
|
|
333
|
-
|
|
334
|
-
const shouldForce = options.force ?? false;
|
|
335
|
-
|
|
336
|
-
if (folderExists && !shouldForce && !autoConfirm) {
|
|
331
|
+
if (folderExists && !autoConfirm) {
|
|
337
332
|
const update = await confirm({
|
|
338
333
|
message: `${agent.folder} already exists. Update source control skills?`,
|
|
339
334
|
initialValue: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ─── React Contexts & Hooks ───────────────────────
|
|
2
2
|
|
|
3
|
-
import { createContext, useContext,
|
|
3
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
4
4
|
import type { PanelStore } from "./orchestrator-panel-store.ts";
|
|
5
5
|
import type { GraphTheme } from "./graph-theme.ts";
|
|
6
6
|
|
|
@@ -20,7 +20,16 @@ export function useGraphTheme(): GraphTheme {
|
|
|
20
20
|
return ctx;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to the store and return its current version.
|
|
25
|
+
*
|
|
26
|
+
* Uses `useSyncExternalStore` so the subscription is active from the
|
|
27
|
+
* very first render — no `useEffect` timing gap that could cause a
|
|
28
|
+
* missed `addSession` update.
|
|
29
|
+
*/
|
|
30
|
+
export function useStoreVersion(store: PanelStore): number {
|
|
31
|
+
return useSyncExternalStore(
|
|
32
|
+
store.subscribe,
|
|
33
|
+
() => store.version,
|
|
34
|
+
);
|
|
26
35
|
}
|
|
@@ -22,7 +22,7 @@ import { tmuxRun } from "../runtime/tmux.ts";
|
|
|
22
22
|
import {
|
|
23
23
|
useStore,
|
|
24
24
|
useGraphTheme,
|
|
25
|
-
|
|
25
|
+
useStoreVersion,
|
|
26
26
|
TmuxSessionContext,
|
|
27
27
|
} from "./orchestrator-panel-contexts.ts";
|
|
28
28
|
import { computeLayout } from "./layout.ts";
|
|
@@ -51,10 +51,10 @@ export function SessionGraphPanel() {
|
|
|
51
51
|
useRenderer();
|
|
52
52
|
const { width: termW, height: termH } = useTerminalDimensions();
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const storeVersion = useStoreVersion(store);
|
|
55
55
|
|
|
56
56
|
// Compute layout from current session data
|
|
57
|
-
const layout = useMemo(() => computeLayout(store.sessions), [
|
|
57
|
+
const layout = useMemo(() => computeLayout(store.sessions), [storeVersion]);
|
|
58
58
|
const nodeList = useMemo(() => Object.values(layout.map), [layout]);
|
|
59
59
|
|
|
60
60
|
const connectors = useMemo(() => {
|
|
@@ -82,7 +82,7 @@ export function SessionGraphPanel() {
|
|
|
82
82
|
if (store.sessions.length > 0 && !layout.map[focusedId]) {
|
|
83
83
|
setFocusedId(store.sessions[0]!.name);
|
|
84
84
|
}
|
|
85
|
-
}, [
|
|
85
|
+
}, [storeVersion]);
|
|
86
86
|
|
|
87
87
|
// Pulse animation for running nodes — paused when nothing is running
|
|
88
88
|
const hasRunning = store.sessions.some((s) => s.status === "running");
|
|
@@ -99,11 +99,10 @@ export function SessionGraphPanel() {
|
|
|
99
99
|
// Live timer refresh — re-render every second while any session is running
|
|
100
100
|
const [, setTick] = useState(0);
|
|
101
101
|
useEffect(() => {
|
|
102
|
-
const hasRunning = store.sessions.some((s) => s.status === "running");
|
|
103
102
|
if (!hasRunning) return;
|
|
104
103
|
const id = setInterval(() => setTick((t) => t + 1), 1000);
|
|
105
104
|
return () => clearInterval(id);
|
|
106
|
-
}, [
|
|
105
|
+
}, [hasRunning]);
|
|
107
106
|
|
|
108
107
|
// Attach flash message
|
|
109
108
|
const [attachMsg, setAttachMsg] = useState("");
|