Agents on Lambda: The 15-Minute Wall and the Three Ways Around It
Lambda suits a lot of agent work. Sessions are bursty, most of the wall-clock time is spent waiting on a model API, and paying nothing between requests matters when traffic is uneven. The trouble starts when a session is longer than a single invocation can be, and the ways Lambda fails at that boundary are worth knowing before you hit them.
The limits that matter for agents
- Invocation timeout: 15 minutes, hard. Not raisable. An agent loop that runs 16 minutes is killed mid-step, possibly between a side effect and the record that it happened.
- The front door is often shorter. API Gateway REST APIs default to a 29-second integration timeout. It can be raised for Regional and private APIs, at a cost to throttling quotas. A synchronous “chat with the agent” endpoint behind API Gateway fails well before Lambda would.
- Streaming. Lambda response streaming through a function URL sends tokens to the user as they’re produced. It’s supported natively on the Node.js managed runtime; other languages need a custom runtime or the Lambda Web Adapter.
- Memory is also CPU. Agents rarely need much CPU, but if you parse large tool outputs or run embeddings in-process, low-memory functions become slow.
A short agent session may fit a Lambda invocation but still exceed its front door’s timeout; check both. Lambda plus a function URL with streaming is a good, simple deployment for it.
Pattern 1: one invocation per step, state outside
Make each Lambda invocation run one step of the loop (one model call, plus any tool calls it asks for), then save the conversation state and exit. Something outside the function decides whether to run the next step.
That “something” can be as simple as an SQS message the step sends itself, carrying the session ID:
This pseudocode omits queue parsing, locking and recovery. The state write and next-step enqueue need a transactional outbox or equivalent recovery protocol; neither is atomic with an external tool effect.
def handler(event, context):
session = load_session(event["session_id"]) # DynamoDB or S3
if session.steps >= MAX_STEPS or session.cost >= MAX_COST:
return finish(session, reason="limit")
result = run_one_step(session) # model call + tools
save_session(session)
if result.done:
return finish(session, reason="complete")
enqueue_next_step(session.id) # SQS, same functionEach invocation stays short; recovery must reconcile any external effects of the interrupted step, and limits are enforced between steps rather than inside the model’s loop. The costs: you now own state storage and serialisation, and each step pays to load the state.
Pattern 2: Step Functions as the loop
Step Functions Standard workflows run for up to a year and record every state transition. Model the agent loop as a state machine: a “think” task (one Lambda step), a choice state that routes on the model’s stop reason, a “use tool” task, and a loop back. Limits become explicit states, and human approvals can use the callback task pattern, where the workflow pauses until someone approves, without paying for an idle function.
The execution history is also an audit trail you get for free. Step Functions can call Bedrock directly as well, which removes one Lambda from simple steps.
The downsides are the Amazon States Language (verbose, and awkward for highly dynamic loops) and per-transition pricing on Standard workflows. A 40-step agent session is a few hundred transitions. Cheap per session, but worth a look at volume. Express workflows are cheaper but capped at five minutes, which rules them out for long agent loops.
Pattern 3: Lambda durable functions
Lambda now has durable functions: you write the loop as ordinary code, and the Durable Execution SDK checkpoints each completed operation. An execution can suspend, wait and resume. Each invocation still has the 15-minute limit, but the overall execution can run for up to a year. On resume, Lambda replays your function from the start and substitutes stored results for operations that already completed.
That replay model has one rule you can’t break: code outside checkpointed operations must be deterministic. Model calls and tool calls must go through the SDK’s step operations, so their results are recorded and replayed rather than executed again. A model call made directly in the function body would run again on every replay, with a different answer and another bill. If a team is new to durable execution, write a test that forces a replay before going to production.
When to leave Lambda
Consider a container runtime with the required capabilities when:
- sessions hold large in-memory state that’s expensive to save and reload every step;
- the agent keeps a long-lived connection, such as a WebSocket, a browser automation session or a persistent MCP server connection;
- you need a local model or heavy tools with a long warm-up; for GPU requirements use a GPU-capable host such as ECS on suitable EC2 instances, not Fargate;
- steady, high traffic makes always-on containers cheaper than per-invocation pricing.
Amazon Bedrock AgentCore Runtime is a managed middle ground built for this. Sessions run in isolated microVMs, currently for up to eight hours. There’s a separate post comparing AWS options.
Where this stops
All three patterns make long sessions possible on Lambda. None makes them cheap to reason about. Every step boundary is a place where state can be stale, a retry can repeat a side effect, or a limit check can be skipped. Whichever pattern you pick, make the side-effecting tools idempotent first. That is what makes retries and replays safe.