Skip to main content

Documentation Index

Fetch the complete documentation index at: https://bastani.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

A workflow is one TypeScript file, versioned with your repo. It encodes a process — review, CI, PR, approval, merge; or research, plan, implement, debug — as code your team runs identically across machines, agents, and CI.
import { defineWorkflow } from "@bastani/atomic-sdk/workflows";

export default defineWorkflow({
  name: "review-to-merge",
  description: "Review → CI → PR → Notify → Approve → Merge",
})
  .for("claude")
  .run(async (ctx) => {
    const review = await ctx.stage({ name: "review" }, {}, {}, async (s) => {
      await s.session.query("Review uncommitted changes for correctness, security, style.");
      s.save(s.sessionId);
    });

    await Promise.all([
      ctx.stage({ name: "security-scan" }, {}, {}, async (s) => {
        await s.session.query("Run `bun audit` and scan for leaked secrets.");
        s.save(s.sessionId);
      }),
      ctx.stage({ name: "ci-checks" }, {}, {}, async (s) => {
        await s.session.query("Run `bun lint` and `bun test`. Report failures.");
        s.save(s.sessionId);
      }),
    ]);

    await ctx.stage({ name: "notify-and-merge" }, {}, {}, async (s) => {
      const t = await s.transcript(review);
      await s.session.query(`Read ${t.path}. Open a PR summarizing the changes.`);
      await s.session.query(
        "Ask the user to confirm approval, then merge with `gh pr merge --squash`.",
        { allowedTools: ["Bash", "Read", "AskUserQuestion"] },
      );
      s.save(s.sessionId);
    });
  })
  .compile();

Stages

Each ctx.stage(opts, clientOpts, sessionOpts, fn) call spawns a fresh agent session at runtime — its own tmux window, its own context window, and its own node in the execution graph. Inside the callback you write raw provider SDK code against s.session / s.client. A stage callback can return a value, which becomes handle.result on the returned SessionHandle — that’s how an LLM’s verdict drives TypeScript control flow. See stages for the full callback API.

The execution graph

The topology is auto-inferred from your await / Promise.all patterns — no annotations. Sequential await creates a chain; Promise.all creates parallel fan-out and fan-in. Use ordinary TypeScript control flow — for, if/else, try/catch — and the graph follows.
  • Branchif/else on a prior stage’s result.
  • Retry / loop — a bounded for loop with early exit when a review comes back clean (see the review-fix-loop pattern).
  • ParallelPromise.all([ctx.stage(...), ctx.stage(...)]); parallel siblings finish (or fail fast) before the next stage starts.
  • Nested — call s.stage(...) inside a callback to spawn child sessions, shown as nested graph nodes.
Atomic renders a live workflow panel over the session graph as the run executes — see Workflow panel below.

Workflow panel

During atomic workflow execution, Atomic renders a live workflow panel built on OpenTUI over the workflow’s tmux session graph:
  • A node per .stage() call with live status (pending, running, completed, failed).
  • Edges for sequential dependencies and parallel fan-out / fan-in.
  • For Ralph runs: the task list with dependency arrows, updated in real time.
  • Pane previews showing what each session is currently doing.
  • Visible s.save() / s.transcript() handoffs between stages.
atomic chat -a <agent> has no Atomic-owned UI — it spawns the native agent CLI directly inside a tmux session, so chat features (streaming, @ mentions, /slash-commands, model selection) come from the agent CLI itself. The workflow panel only appears during atomic workflow runs.

Session isolation

Every stage runs in its own session with a fresh context. There’s no shared mutable state between stages — data flows only through explicit transcript reads.

Controlled transcript hand-offs

A stage calls s.save(...) to persist its output. A later stage reads it with s.transcript(handle) (by handle) or s.transcript("stage-name") (by name), getting { path, content }. Read transcripts only come from completed upstream stages — parallel siblings can’t read each other. This is token-aware by design: save full transcripts to disk and pass paths or distilled summaries forward instead of stuffing every stage with the entire history.
const describe = await ctx.stage({ name: "describe" }, {}, {}, async (s) => {
  await s.session.query("Describe this project.");
  s.save(s.sessionId);
});

await ctx.stage({ name: "summarize" }, {}, {}, async (s) => {
  const research = await s.transcript(describe);
  await s.session.query(`Read ${research.path} and summarize in 2-3 bullets.`);
  s.save(s.sessionId);
});

Headless stages

Pass headless: true and the stage runs the provider SDK in-process instead of spawning a tmux window — invisible in the graph, tracked by a background counter in the statusline. The callback API is identical. Useful for fan-out data gathering where you don’t need a visible pane per worker.

Human-in-the-loop gates

A stage can pause for a human. Issue a query that asks the user a question (e.g. with AskUserQuestion in the allowed tools) and the stage blocks until they respond — then the workflow continues or branches on the answer. The same gate works inside a headless stage.

Determinism

.compile() freezes the workflow — step order, session names, and the execution graph become immutable. Step 2 never starts until Step 1 finishes. The same definition produces the same execution order with the same data flow, anywhere. Variance comes from the LLM’s responses, not from a changing workflow. Every stage writes its messages, transcript, and metadata to disk under ~/.atomic/sessions/<runId>/ — a complete, inspectable execution record. See reading run artifacts.

Build a workflow

The SDK walkthrough — defineWorkflow, stages, the composition root.

Built-in workflows

ralph, deep-research-codebase, open-claude-design.