Loading...
Loading...
Create custom multi-agent workflows for Atomic CLI using the defineWorkflow() session-based API with programmatic SDK code. Use this skill whenever the user wants to create a workflow, build an agent pipeline, define a multi-stage automation, set up a review loop, or connect multiple coding agents together. Also trigger when they mention workflow files, .atomic/workflows/, defineWorkflow, or ask how to automate a sequence of agent tasks — even if they don't use the word "workflow" explicitly.
npx skill4agent add flora131/atomic workflow-creatordefineWorkflow()references/getting-started.md| File | When to load |
|---|---|
| Always — quick-start example, SDK exports, and |
| Creating agent sessions with SDK calls: Claude |
| Deterministic computation, response parsing, validation, file I/O inside |
| Collecting user input: Claude |
| Loops ( |
| Data flow between sessions: |
| Per-SDK configuration: model, tools, permissions, hooks, structured output |
| File discovery, |
.session()run(ctx)import { defineWorkflow } from "@bastani/atomic-workflows";
export default defineWorkflow({ name: "my-workflow", description: "..." })
.session({ name: "step-1", run: async (ctx) => { /* SDK code here */ } })
.session({ name: "step-2", run: async (ctx) => { /* SDK code here */ } })
.compile();.compile()WorkflowDefinition.atomic/workflows/<agent>/<workflow-name>/index.ts.atomic/workflows/claude/<name>/index.ts.atomic/workflows/copilot/<name>/index.ts.atomic/workflows/opencode/<name>/index.ts~/.atomic/workflows/<agent>/<name>/index.tsrun()| Workflow Concept | Programmatic Pattern |
|---|---|
| Agent session (send prompt, get response) | |
| Deterministic computation (no LLM) | Plain TypeScript inside |
| User input mid-workflow | Claude: |
| Conditional branching | Plain |
| Bounded loops | Plain |
| Data flow between sessions | |
| Per-session configuration | SDK-specific: Claude |
| Response data extraction | Parse SDK responses directly: Claude result messages, Copilot |
| Subagent orchestration | Claude: |
| Runtime validation | Plain TypeScript or import Zod directly in |
| Question | Maps to |
|---|---|
| What are the distinct steps? | Each step → |
| Does any step need deterministic computation (no LLM)? | Plain TypeScript inside |
| Do any steps need to repeat? | |
| Are there conditional paths? | |
| What data flows between steps? | |
| Does the workflow need user input? | SDK-specific user input APIs (see |
| Do any steps need a specific model? | SDK-specific session config (see |
| Does a step need structured output? | Claude: |
| Agent | SDK Import | Primary API |
|---|---|---|
| Claude | | |
| Copilot | | |
| OpenCode | | |
.atomic/workflows/<agent>/<name>/index.ts.session()| Field | Purpose |
|---|---|
| Unique identifier. Used as the key in |
| Short label for logging and the orchestrator UI. |
| Async callback receiving |
// .atomic/workflows/claude/my-workflow/index.ts
import { defineWorkflow, claudeQuery } from "@bastani/atomic-workflows";
export default defineWorkflow({
name: "my-workflow",
description: "Two-step pipeline",
})
.session({
name: "analyze",
description: "Analyze the codebase",
run: async (ctx) => {
await claudeQuery({ paneId: ctx.paneId, prompt: ctx.userPrompt });
ctx.save(ctx.sessionId);
},
})
.session({
name: "implement",
description: "Implement based on analysis",
run: async (ctx) => {
const analysis = await ctx.transcript("analyze");
await claudeQuery({
paneId: ctx.paneId,
prompt: `Based on this analysis:\n${analysis.content}\n\nImplement the changes.`,
});
ctx.save(ctx.sessionId);
},
})
.compile();// .atomic/workflows/copilot/my-workflow/index.ts
import { defineWorkflow } from "@bastani/atomic-workflows";
import { CopilotClient, approveAll } from "@github/copilot-sdk";
export default defineWorkflow({
name: "my-workflow",
description: "Two-step pipeline",
})
.session({
name: "analyze",
description: "Analyze the codebase",
run: async (ctx) => {
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
await client.start();
const session = await client.createSession({ onPermissionRequest: approveAll });
await client.setForegroundSessionId(session.sessionId);
await session.sendAndWait({ prompt: ctx.userPrompt });
ctx.save(await session.getMessages());
await session.disconnect();
await client.stop();
},
})
.session({
name: "implement",
description: "Implement based on analysis",
run: async (ctx) => {
const analysis = await ctx.transcript("analyze");
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
await client.start();
const session = await client.createSession({ onPermissionRequest: approveAll });
await client.setForegroundSessionId(session.sessionId);
await session.sendAndWait({
prompt: `Based on this analysis:\n${analysis.content}\n\nImplement the changes.`,
});
ctx.save(await session.getMessages());
await session.disconnect();
await client.stop();
},
})
.compile();// .atomic/workflows/opencode/my-workflow/index.ts
import { defineWorkflow } from "@bastani/atomic-workflows";
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
export default defineWorkflow({
name: "my-workflow",
description: "Two-step pipeline",
})
.session({
name: "analyze",
description: "Analyze the codebase",
run: async (ctx) => {
const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
const session = await client.session.create({ title: "analyze" });
await client.tui.selectSession({ sessionID: session.data!.id });
const result = await client.session.prompt({
sessionID: session.data!.id,
parts: [{ type: "text", text: ctx.userPrompt }],
});
ctx.save(result.data!);
},
})
.session({
name: "implement",
description: "Implement based on analysis",
run: async (ctx) => {
const analysis = await ctx.transcript("analyze");
const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
const session = await client.session.create({ title: "implement" });
await client.tui.selectSession({ sessionID: session.data!.id });
const result = await client.session.prompt({
sessionID: session.data!.id,
parts: [{
type: "text",
text: `Based on this analysis:\n${analysis.content}\n\nImplement the changes.`,
}],
});
ctx.save(result.data!);
},
})
.compile();bunx tsc --noEmit --pretty falseatomic workflow -n <workflow-name> -a <agent> "<your prompt>"defineWorkflow({ name: "pipeline", description: "Sequential pipeline" })
.session({ name: "plan", run: async (ctx) => { /* plan */ } })
.session({ name: "execute", run: async (ctx) => { /* execute */ } })
.session({ name: "verify", run: async (ctx) => { /* verify */ } })
.compile();run().session({
name: "review-fix",
description: "Iterative review and fix",
run: async (ctx) => {
const MAX_CYCLES = 10;
let consecutiveClean = 0;
for (let cycle = 0; cycle < MAX_CYCLES; cycle++) {
// Step 1: Ask the agent to review
const reviewResult = await claudeQuery({
paneId: ctx.paneId,
prompt: buildReviewPrompt(ctx.userPrompt),
});
// Step 2: Parse and check findings (deterministic computation)
const review = parseReviewResult(reviewResult.output);
if (!hasActionableFindings(review, reviewResult.output)) {
consecutiveClean++;
if (consecutiveClean >= 2) break; // Two clean passes → done
continue;
}
consecutiveClean = 0;
// Step 3: Apply fixes
const fixPrompt = buildFixSpecFromReview(review, ctx.userPrompt);
await claudeQuery({ paneId: ctx.paneId, prompt: fixPrompt });
}
ctx.save(ctx.sessionId);
},
})run().session({
name: "triage-and-act",
description: "Triage, then branch based on result",
run: async (ctx) => {
// Step 1: Triage
const triageResult = await claudeQuery({
paneId: ctx.paneId,
prompt: `Classify this request as "bug", "feature", or "question":\n${ctx.userPrompt}`,
});
// Step 2: Branch based on classification
if (triageResult.output.includes("bug")) {
await claudeQuery({ paneId: ctx.paneId, prompt: "Fix the bug described above." });
} else if (triageResult.output.includes("feature")) {
await claudeQuery({ paneId: ctx.paneId, prompt: "Implement the feature described above." });
} else {
await claudeQuery({ paneId: ctx.paneId, prompt: "Research and answer the question above." });
}
ctx.save(ctx.sessionId);
},
})defineWorkflow({ name: "data-flow", description: "Pass data between sessions" })
.session({
name: "research",
run: async (ctx) => {
// ... perform research ...
ctx.save(ctx.sessionId); // Save transcript
},
})
.session({
name: "synthesize",
run: async (ctx) => {
// Read prior session's output
const research = await ctx.transcript("research");
// Use as rendered text:
const prompt = `Synthesize this research:\n${research.content}`;
// Or reference the file path:
const altPrompt = `Read ${research.path} and synthesize the findings.`;
// ... use the data ...
ctx.save(ctx.sessionId);
},
})
.compile();.atomic/workflows/
├── claude/my-workflow/index.ts # Claude-specific SDK code
├── copilot/my-workflow/index.ts # Copilot-specific SDK code
├── opencode/my-workflow/index.ts # OpenCode-specific SDK code
└── my-workflow/helpers/
├── prompts.ts # Prompt builders (SDK-agnostic)
├── parsers.ts # Response parsers (SDK-agnostic)
└── validation.ts # Validation logic (SDK-agnostic)// .atomic/workflows/my-workflow/helpers/prompts.ts
export function buildPlanPrompt(spec: string): string {
return `Decompose this into tasks:\n${spec}`;
}
// .atomic/workflows/claude/my-workflow/index.ts
import { buildPlanPrompt } from "../../my-workflow/helpers/prompts.ts";
// ...
await claudeQuery({ paneId: ctx.paneId, prompt: buildPlanPrompt(ctx.userPrompt) });SessionContextSessionContextrun()| Field | Type | Description |
|---|---|---|
| | The agent's server URL (Copilot |
| | The original user prompt from the CLI invocation |
| | Which agent is running ( |
| | Get a prior session's transcript as |
| | Get a prior session's raw native messages |
| | Save this session's output for subsequent sessions |
| | Path to this session's storage directory on disk |
| | tmux pane ID for this session |
| | Session UUID |
ctx.save()ctx.save(ctx.sessionId)ctx.save(await session.getMessages())SessionEvent[]ctx.save(result.data!){ info, parts }ctx.transcript(name){ path: string, content: string }contentpathname.session().compile().compile()compile()export defaultexport defaultctx.transcript("<name>")