agentcmd
ConceptsWorkflows

Workflow Steps

Steps are the building blocks of workflows. agentcmd provides 9 step types to handle different automation tasks.

Step Types Overview

Step TypePurposeTimeoutReference
agentRun AI CLI tools (Claude, Codex, Gemini)30 minLearn more →
aiNon-interactive AI text/structured generation5 minLearn more →
cliExecute shell commands5 minLearn more →
gitGit operations (commit, branch, PR)2 minLearn more →
previewDocker-based preview containers5 minLearn more →
artifactUpload files/images/directories5 minLearn more →
annotationProgress notes for timelineInstantLearn more →
phaseGroup related stepsN/ALearn more →
logConsole logging and debug messagesInstantLearn more →

Note: The quick examples below show common uses. Click "Learn more" for complete API documentation.

Quick Examples

Agent Step

Run AI CLI tools with full file access:

await step.agent("implement-feature", {
  agent: "claude",
  prompt: "Implement user authentication system",
  workingDir: "/path/to/project",
  permissionMode: "acceptEdits", // Auto-approve file edits
});

When to use: Complex tasks requiring file editing, multiple iterations, tool use

AI Step

Fast AI text or structured generation:

const analysis = await step.ai("analyze-code", {
  prompt: "Analyze this codebase for security issues",
  provider: "anthropic",
  schema: z.object({
    issues: z.array(z.object({
      severity: z.enum(["high", "medium", "low"]),
      description: z.string(),
    })),
  }),
});

When to use: Quick AI responses, structured data extraction, no file editing needed

CLI Step

Execute shell commands:

await step.cli("run-tests", {
  command: "pnpm test --coverage",
  cwd: projectPath,
  env: { NODE_ENV: "test" },
});

When to use: Build, test, deploy, any shell command

Git Step

Git operations:

await step.git("create-feature-branch", {
  operation: "commit-and-branch",
  commitMessage: "feat: Add user authentication",
  branch: "feat/auth-system",
});

When to use: Commit changes, create branches, open PRs

Preview Step

Start Docker preview containers:

const preview = await step.preview("deploy", {
  ports: { SERVER: 4100, CLIENT: 4101 },
  env: { NODE_ENV: "preview" },
});

console.log(preview.data.urls);
// { SERVER: "http://localhost:5000", CLIENT: "http://localhost:5001" }

When to use: Test changes in isolated Docker environments, review PRs with live previews

Artifact Step

Upload outputs for review:

await step.artifact("test-results", {
  name: "coverage-report.html",
  type: "file",
  file: "./coverage/index.html",
  description: "Test coverage report",
});

When to use: Save build outputs, reports, images, generated files

Annotation Step

Mark progress in timeline:

await step.annotation("planning-complete", {
  message: "Feature design complete ✓ Starting implementation...",
});

When to use: Explain what's happening, mark phase transitions, celebrate wins

Phase Step

Organize related steps:

await step.phase("test", async () => {
  await step.cli("unit", { command: "pnpm test:unit" });
  await step.cli("e2e", { command: "pnpm test:e2e" });
});

When to use: Group steps into logical stages (plan, build, test, deploy)

Log Step

Debug and tracking:

step.log("Starting deployment...");
step.log.warn("API rate limit approaching");
step.log.error("Deployment failed", error);

When to use: Development, debugging, monitoring

Step Execution Model

Sequential by Default

Steps run one after another:

await step.cli("step-1", { ... });  // Runs first
await step.cli("step-2", { ... });  // Then this
await step.cli("step-3", { ... });  // Finally this

Parallel with Promise.all()

Run steps concurrently:

await Promise.all([
  step.cli("lint", { command: "pnpm lint" }),
  step.cli("test", { command: "pnpm test" }),
  step.cli("typecheck", { command: "pnpm check-types" }),
]);

All three run simultaneously

Conditional Execution

Skip steps based on logic:

if (needsSetup) {
  await step.cli("setup", { command: "pnpm install" });
}

await step.cli("build", { command: "pnpm build" });

if (shouldDeploy) {
  await step.git("deploy", { operation: "commit" });
}

Step Return Values

Most steps return results:

Agent Step Returns

const result = await step.agent("code", {
  agent: "claude",
  json: true, // Extract JSON from response
  prompt: "Generate API spec",
});

// result.data contains extracted JSON
console.log(result.data.endpoints);

AI Step Returns

const analysis = await step.ai("analyze", {
  prompt: "Analyze code quality",
  schema: mySchema,
});

// Typed result based on schema
analysis.data.score; // Type-safe!

CLI Step Returns

const output = await step.cli("version", {
  command: "node --version",
});

console.log(output.stdout); // "v18.17.0"
console.log(output.exitCode); // 0

Error Handling

Try/Catch

Handle step failures:

try {
  await step.cli("deploy", {
    command: "deploy-to-prod",
  });
} catch (error) {
  await step.annotation("deploy-failed", {
    message: `Deployment failed: ${error}. Rolling back...`,
  });

  await step.cli("rollback", {
    command: "rollback-deployment",
  });
}

Timeout Configuration

Override default timeouts:

await step.cli("long-build", {
  command: "pnpm build",
}, {
  timeout: 600000, // 10 minutes
});

Step Naming

Each step must have a unique name. Use descriptive, action-oriented names:

Good:

await step.cli("install-dependencies", { ... });
await step.agent("implement-authentication", { ... });
await step.git("create-feature-branch", { ... });

Bad - Generic/duplicate names:

await step.cli("step1", { ... });
await step.agent("do-stuff", { ... });
await step.cli("run-command", { ... }); // Too generic
await step.cli("run-command", { ... }); // Duplicate!

Why unique names matter:

  • Steps are tracked by name in Inngest
  • Duplicate names cause workflow errors
  • Names appear in Timeline UI, logs, error messages
  • Make them clear and searchable

Next Steps