
Traditional pipeline steps execute quickly. AI agents think before they answer — they reason through prompts, call tools, and generate responses. This takes time, often many seconds.
Traditional step: |--work--| (fast, predictable)
AI agent step: |--------thinking--------| (slow, variable)
When you call an agent from a Lambda function the naive way:
Lambda starts → calls agent → WAITS → agent responds → Lambda finishes
↑ ↑
Billing starts Billing continues
Key insight: Lambda bills you for every second it runs — including seconds spent doing absolutely nothing while waiting.
| Side | What You Pay For |
|---|---|
| Amazon Bedrock AgentCore | Memory during idle waits (NOT CPU) — efficient |
| Lambda (the caller) | Full compute allocation the entire time — wasteful |
The waste is NOT on the agent side. It's the caller sitting idle on an open connection.
Lambda Function
┌─────────────────────────────────────────┐
│ 1. Receive request │
│ 2. Call agent ──────────────────────┐ │
│ 3. WAIT... (billing continues) │ │
│ 4. WAIT... (billing continues) │ │
│ 5. WAIT... (billing continues) │ │
│ 6. Receive response ◄───────────────┘ │
│ 7. Return result │
└─────────────────────────────────────────┘
Billed duration ≈ entire agent processing time
Why it's common: Simple to implement, easy to understand Why it's a problem: Billed duration tracks the agent's runtime directly
The solution in all three patterns follows the same principle:
INSTEAD OF:
Caller → [waits with meter running] → gets result
DO THIS:
Caller → starts agent → returns immediately (meter stops)
[agent works independently]
agent → signals completion → pipeline resumes
Now let's look at each pattern.
Step Functions
│
▼
Lambda (dispatcher)
├── Receives task token from Step Functions
├── Starts agent (passes token along)
└── Returns in ~seconds ← BILLING STOPS HERE
[Step Functions execution PAUSES — no compute billed]
Agent works...
Agent finishes...
Agent calls SendTaskSuccess(token)
│
▼
Step Functions RESUMES execution
Think of the task token as a claim ticket:
State active: 19.6 seconds
Lambda billed: 4.8 seconds
──────────
Idle time (no billing): ~14.8 seconds ← money saved
✅ You need custom code before or after the agent call ✅ You want Lambda in the path for pre/post-processing logic ✅ You need flexibility around the agent invocation
| Aspect | Detail |
|---|---|
| Caller cost | Seconds only (dispatch) |
| Complexity | Medium-high (IAM, heartbeat, timeout setup) |
| Business logic | In Lambda (before + after agent) |
Step Functions
│
└──► Calls AgentCore directly (no Lambda at all)
[Step Functions pauses — bills per state transition only]
Agent works...
Agent finishes...
Response flows directly into next state
There is no Lambda function in the path at all. Step Functions uses its built-in AWS SDK integration to call AgentCore directly. The Validate branch becomes a single Task state.
Before (with Lambda):
Step Functions → Lambda → AgentCore
After (direct integration):
Step Functions ──────────► AgentCore
✅ Pure orchestration — no custom code needed around the agent call ✅ You want the absolute lowest complexity ✅ State transformations in Amazon States Language (ASL) are sufficient
| Aspect | Detail |
|---|---|
| Caller cost | Zero (no Lambda) |
| Complexity | Lowest |
| Business logic | Limited to ASL intrinsic functions only |
Single Lambda Function (durable)
┌────────────────────────────────────────┐
│ context.step("organize", ...) │ ← runs, suspends
│ context.parallel([...]) │ ← runs, suspends
│ context.waitForCallback(agentCall) │ ← suspends here
│ [SUSPENDED — not billed] │
│ [agent works independently] │
│ [agent calls SendDurableCallback] │
│ context.step("notify", ...) │ ← resumes, runs
└────────────────────────────────────────┘
The durable function suspends itself at the waitForCallback point. When the agent signals completion, the function replays from a checkpoint — picking up exactly where it left off.
You write the entire pipeline as sequential code in one function, but it doesn't bill for compute during suspensions.
✅ You prefer orchestration as code rather than a state machine definition ✅ Complex async workflows that are easier to express sequentially ✅ You want all pipeline logic in one place
| Aspect | Detail |
|---|---|
| Caller cost | Seconds only (dispatch bursts) |
| Complexity | Medium (checkpoint/replay model to understand) |
| Business logic | In Lambda as sequential code |
BLOCKING (anti-pattern)
├── Lambda alive entire time
├── Billed: full agent processing time
└── Use for: prototypes only
PATTERN 1: Task Token
├── Lambda returns in seconds
├── Billed: dispatch only (~seconds)
├── Custom code: yes (before + after)
└── Use for: custom pre/post-processing
PATTERN 2: Direct Integration
├── No Lambda at all
├── Billed: zero Lambda cost
├── Custom code: limited (ASL only)
└── Use for: pure orchestration
PATTERN 3: Durable Function
├── Lambda suspends during wait
├── Billed: short execution bursts only
├── Custom code: yes (sequential code)
└── Use for: complex async workflows as code
Set TimeoutSeconds on every waitForTaskToken state
→ Execution fails cleanly instead of hanging forever
Set HeartbeatSeconds if agent sends heartbeats
→ Faster detection of a dead agent
sessionId = derived from execution name
→ Retries resume the same agent session
→ Prevents starting fresh on every retry
Tracing: Active on Step Functions + Lambda
→ See exactly: agent thinking time vs. caller waiting time
→ Confirms your pattern actually released compute
The dispatcher only: serializes request + invokes endpoint
256 MB memory + 30-second timeout = usually sufficient
Heavy work happens on the agent side — not here
Do you need custom code around the agent call?
│
├── NO → Pattern 2 (Direct Integration)
│ Simplest, zero Lambda cost
│
└── YES → Do you prefer orchestration as code?
│
├── YES → Pattern 3 (Durable Function)
│ One function, sequential style
│
└── NO → Pattern 1 (Task Token)
Step Functions state machine
with Lambda dispatcher
| Concept | Key Takeaway |
|---|---|
| The problem | Blocking callers pay for idle time while agents think |
| The waste | It's on the caller side, not the agent side |
| The fix | Release the caller immediately; resume only when agent is done |
| Pattern 1 | Task token — Lambda dispatches and exits; Step Functions waits |
| Pattern 2 | Direct integration — no Lambda at all |
| Pattern 3 | Durable function — Lambda suspends itself |
| Common principle | All three patterns separate starting the agent from waiting for it |