Workflow Definitions
Workflows are the heart of agentcmd - multi-step processes that orchestrate AI agents, CLI commands, git operations, and more.
What is a Workflow?
A workflow is a TypeScript function that defines a sequence of steps to automate a task. Think of it as a recipe:
defineWorkflow({
id: "deploy-feature",
name: "Deploy Feature",
phases: ["plan", "code", "test", "ship"]
}, async ({ event, step }) => {
// Your automation logic here
});Anatomy of a Workflow
import { defineWorkflow } from "agentcmd-workflows";
export default defineWorkflow(
// 1. Configuration
{
id: "my-workflow", // Unique identifier
name: "My Workflow", // Display name
description: "What it does", // Shown in library
phases: ["setup", "work", "cleanup"], // Optional phases
},
// 2. Implementation
async ({ event, step }) => {
// event.data contains trigger data (projectPath, args, etc.)
const { projectPath } = event.data;
// 3. Steps
await step.cli("install-deps", {
command: "pnpm install",
cwd: projectPath,
});
await step.agent("generate-code", {
agent: "claude",
prompt: "Write a feature",
});
// 4. Return value
return { success: true };
}
);1. Configuration
Required:
id- Unique workflow identifier (kebab-case)name- Human-readable name for UI
Optional:
description- Explain what the workflow doesphases- Array of phase names (or objects with{ id, label })argsSchema- JSON schema for type-safe arguments
2. Implementation Function
Receives two parameters:
event - Trigger event data:
{
data: {
projectPath: string; // Project directory
projectId: string; // Database ID
userId: string; // User who triggered
args?: Record<string, unknown>; // Custom arguments
}
}step - Step execution API:
step.agent()- Run AI CLI toolsstep.ai()- Non-interactive AI generationstep.cli()- Shell commandsstep.git()- Git operationsstep.artifact()- Upload filesstep.annotation()- Progress notesstep.phase()- Group stepsstep.log()- Logging
3. Steps
Steps run sequentially by default:
await step.cli("step-1", { ... }); // Runs first
await step.cli("step-2", { ... }); // Then this
await step.cli("step-3", { ... }); // Finally thisParallel execution with Promise.all():
await Promise.all([
step.cli("lint", { command: "pnpm lint" }),
step.cli("test", { command: "pnpm test" }),
step.cli("build", { command: "pnpm build" }),
]);4. Return Value
Return an object indicating success/failure:
return {
success: true,
summary: { /* optional metadata */ }
};Why Inngest?
agentcmd uses Inngest for workflow execution:
- Durable - Steps are checkpointed, failures can retry from last step
- Observable - Inngest Dev UI shows step-by-step execution
- Scalable - Handles long-running workflows (hours/days)
- Type-safe - Full TypeScript support
This means you can write workflows that run for hours without worrying about crashes, and users can monitor execution in real-time.
Want to understand how workflows execute at runtime? See Workflow Runs.
Workflow Discovery
agentcmd auto-discovers workflows on startup:
.agent/workflows/definitions/
├── my-workflow.ts ✓ Loaded
├── another-workflow.ts ✓ Loaded
├── disabled.ts.bak ✗ Skipped (not .ts)
└── example-*.ts ✓ Loaded (examples)Requirements:
- File must export
default defineWorkflow(...) - Must be in
.agent/workflows/definitions/ - Must have
.tsextension
Best Practices
Keep Steps Focused
✅ Good - Each step does one thing:
await step.cli("install", { command: "pnpm install" });
await step.cli("build", { command: "pnpm build" });
await step.cli("test", { command: "pnpm test" });❌ Bad - Kitchen sink step:
await step.cli("everything", {
command: "pnpm install && pnpm build && pnpm test"
});Use Annotations
Help users understand progress:
await step.annotation("planning-complete", {
message: "Feature design complete. Starting implementation..."
});Handle Errors Gracefully
try {
await step.cli("risky-operation", { ... });
} catch (error) {
await step.annotation("error-recovery", {
message: `Operation failed, trying alternative: ${error}`
});
await step.cli("fallback", { ... });
}Share Context via Closures
const ctx: { specFile?: string } = {};
await step.phase("plan", async () => {
const result = await step.agent("plan", { ... });
ctx.specFile = result.data.specPath; // Save for later
});
await step.phase("implement", async () => {
// Use saved context
await step.agent("code", {
prompt: `Implement ${ctx.specFile}`
});
});Common Patterns
Spec → Implement → Review
defineWorkflow({
phases: ["spec", "implement", "review", "ship"]
}, async ({ event, step }) => {
const ctx: { specId?: string; branch?: string } = {};
await step.phase("spec", async () => {
const result = await step.agent("generate-spec", { ... });
ctx.specId = result.data.specId;
});
await step.phase("implement", async () => {
const result = await step.agent("implement", {
prompt: `Implement spec ${ctx.specId}`
});
ctx.branch = result.data.branch;
});
await step.phase("review", async () => {
await step.agent("review", {
prompt: `Review implementation on ${ctx.branch}`
});
});
await step.phase("ship", async () => {
await step.git("create-pr", {
operation: "pr",
branch: ctx.branch,
title: `feat: ${ctx.specId}`,
});
});
});Multi-Agent Collaboration
await step.phase("plan", async () => {
// Claude excels at planning
const plan = await step.agent("architect", {
agent: "claude",
prompt: "Design authentication system",
permissionMode: "plan", // Read-only
});
ctx.sessionId = plan.data.sessionId;
});
await step.phase("implement", async () => {
// Codex excels at coding
await step.agent("code", {
agent: "codex",
prompt: "Implement the authentication plan from previous session",
resume: ctx.sessionId, // Continue Claude's conversation
});
});