This guide covers Step Functions multi-agent with production-focused examples and trade-offs.
Multi-agent coding pipelines fail in production the same way distributed jobs fail: unbounded retries, silent partial success, and no human interrupt when the plan goes sideways. Step Functions is the right control plane—not because it “runs agents,” but because it gives you retries with backoff, Map fan-out, task tokens for human approval, and a durable execution history you can replay in a postmortem.⚡ TL;DR: Model the pipeline as Plan → Implement → Test → Review with explicit state machine transitions. Use Map for file batches, Catch/Retry on Bedrock throttles, and a Task Token human gate before any merge or infra mutate. Emit execution ARNs into PR comments. Never let an agent auto-merge. Pair with LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda and Bedrock Agents: Idempotent Tool Calls Against DynamoDB Writes.
State machine shape that seniors actually ship — Step Functions multi-agent
Keep stages coarse. Fine-grained “think” states explode cost and make approvals unusable.{
"Comment": "Multi-agent coding pipeline",
"StartAt": "PlanAgent",
"States": {
"PlanAgent": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "agent-plan",
"Payload": {
"ticket.$": "$.ticket",
"repoSha.$": "$.repoSha"
}
},
"ResultPath": "$.plan",
"Next": "HumanApprovePlan",
"Retry": [{
"ErrorEquals": ["Bedrock.ThrottlingException", "States.Timeout"],
"IntervalSeconds": 5, "MaxAttempts": 4, "BackoffRate": 2.0
}]
},
"HumanApprovePlan": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"TimeoutSeconds": 86400,
"HeartbeatSeconds": 3600,
"Parameters": {
"FunctionName": "notify-slack-approval",
"Payload": {
"taskToken.$": "$$.Task.Token",
"plan.$": "$.plan",
"executionArn.$": "$$.Execution.Id"
}
},
"ResultPath": "$.approval",
"Next": "ImplementMap"
},
"ImplementMap": {
"Type": "Map",
"ItemsPath": "$.plan.Payload.fileBatches",
"MaxConcurrency": 3,
"Iterator": {
"StartAt": "ImplementBatch",
"States": {
"ImplementBatch": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "agent-implement",
"Payload.$": "$"
},
"End": true
}
}
},
"ResultPath": "$.patches",
"Next": "TestAgent"
},
"TestAgent": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "agent-test",
"Payload": { "patches.$": "$.patches", "repoSha.$": "$.repoSha" }
},
"ResultPath": "$.test",
"Next": "ReviewGate"
},
"ReviewGate": {
"Type": "Choice",
"Choices": [{
"Variable": "$.test.Payload.passed",
"BooleanEquals": true,
"Next": "OpenPrOnly"
}],
"Default": "FailClosed"
},
"OpenPrOnly": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "open-draft-pr",
"Payload": {
"patches.$": "$.patches",
"neverMerge": true
}
},
"End": true
},
"FailClosed": {
"Type": "Fail",
"Error": "TestsFailed",
"Cause": "Agent tests failed — no PR opened"
}
}
}
gh pr merge from the state machine.❌ Auto-merge on green tests from an unattended agent.
Human approval that is interruptible
// notify-slack-approval.ts
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
export async function handler(event: {
taskToken: string;
plan: { Payload: { summary: string; blastRadius: string[] } };
executionArn: string;
}) {
// ✅ Park the token; humans resume via Slack action → SendTaskSuccess
await new SQSClient({}).send(new SendMessageCommand({
QueueUrl: process.env.APPROVAL_QUEUE!,
MessageBody: JSON.stringify({
taskToken: event.taskToken,
executionArn: event.executionArn,
summary: event.plan.Payload.summary,
files: event.plan.Payload.blastRadius,
expiresAt: Date.now() + 86_400_000,
}),
}));
}
// resume.ts — Slack interactivity handler
import { SFNClient, SendTaskSuccessCommand, SendTaskFailureCommand } from "@aws-sdk/client-sfn";
export async function resume(decision: "approve" | "reject", taskToken: string, actor: string) {
const sfn = new SFNClient({});
if (decision === "reject") {
await sfn.send(new SendTaskFailureCommand({
taskToken,
error: "HumanRejected",
cause: `rejected_by:${actor}`,
}));
return;
}
await sfn.send(new SendTaskSuccessCommand({
taskToken,
output: JSON.stringify({ approvedBy: actor, at: new Date().toISOString() }),
}));
}
Observability: execution ARN is the correlation ID
// open-draft-pr.ts — stamp the PR with the Step Functions execution
const body = [
`## Agent pipeline`,
`- Execution: \`${executionArn}\``,
`- Plan approval: ${approval.approvedBy}`,
`- Tests: ${test.Payload.summary}`,
``,
`Do **not** merge until CODEOWNERS review. See [Agentic Git Workflows](https://cheatcoders.net/agentic-git-workflows-atomic-commits-from-noisy-llm-diffs/).`,
].join("\n");
ExecutionsFailed, ExecutionsTimedOut, human-gate age > 24h, Map concurrency saturation. Trace Bedrock hops with the patterns in OpenTelemetry for LLMs.
Cost and throttle controls inside the Map
# Cap fan-out and budget tokens per batch
MaxConcurrency: 3
# Inside agent-implement Lambda:
# - application inference profile
# - per-execution token budget from $.plan.Payload.tokenBudget
Closing checklist
- [ ] State machine has Plan → HumanApprove → Implement(Map) → Test → DraftPR only
- [ ]
waitForTaskTokenwith 24h timeout and heartbeat; reject path callsSendTaskFailure - [ ] Bedrock throttles use Retry with backoff; no infinite loops
- [ ] Map
MaxConcurrencycapped; per-execution token budget enforced - [ ] PR body includes execution ARN; merge is human-only
- [ ] Alarms on failed/timed-out executions and stale approvals
Related reading
- LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda
- Bedrock Agents: Idempotent Tool Calls Against DynamoDB Writes
- Agentic Git Workflows: Atomic Commits From Noisy LLM Diffs
- LLM Cost Controls: Token Budgets Per PR and Per Engineer
Related: Day 33: Agents on Step Functions, Not Infinite Loops · Lambda Durable Patterns: Avoid Step Functions Until Complexity Earns It · Idempotency Keys End-to-End: API Gateway Through Step Functions Safely
Related: Step Functions Express Workflows: Orchestrate Lambda at 100K Events Per Second · How to Build a Real-Time Fraud Detection and Decisioning Architecture on AWS with EventBridge, Step Functions, and DynamoDB · AWS Multi-Tenant SaaS Control Plane Architecture: API Gateway, ECS, DynamoDB, Step Functions, and EventBridge
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
