The situation
An overnight job takes a batch of a few thousand supplier documents, extracts structured fields from each with a foundation model, validates the extraction against a schema, writes the clean records to a data store, and, when a document fails validation twice, routes it to a human reviewer before carrying on. Some of those steps are a model call. Most of them are not. The whole thing has to run to completion even when a single model invocation throttles, a Lambda times out, or a reviewer takes two days to respond, and the operations team wants to be able to open a run afterwards and see exactly which documents took which path.
The first instinct is to reach for a Bedrock agent, because the model is doing the interesting work. That instinct is worth questioning. The model is one participant in a workflow that is mostly known in advance, mostly deterministic, and mostly about moving data reliably between AWS services with retries and a human pause in the middle.
Three engines could carry it: a model-driven Bedrock agent, a visually-defined Bedrock Flow, and an AWS Step Functions state machine. They differ in where the decision about what happens next lives, and they differ far more in what each one promises about a run that has to survive the night.
What actually matters
Who decides the control flow is the opening question, and for this job it answers itself. Extract, validate, store, escalate a second failure to a reviewer: a person can draw that in a minute, and it is the same shape for every document, so there is nothing here for a model to discover at run time. The case for drawing a known sequence rather than having the model rediscover it on every run is made at length for a pipeline whose graph a designer draws, and none of it changes for a batch job. What is left is the harder question, which runtime should own the sequence once it is drawn.
Durability settles most of it. A batch running for hours across thousands of documents will meet throttling, transient errors, and slow dependencies, and it has to survive all of them without a process of yours held open for the duration. Step Functions keeps execution state on the service side, so a Standard execution outlives whatever started it, carries its own error handling per state, and can sit idle for two days waiting on a reviewer with nothing of yours waiting alongside it. Neither an agent invocation nor a Flow invocation offers that, and wanting it around either one means writing and operating a workflow engine of your own.
Then the shape of the work. Most of this job is not model calls. It is reads and writes to a data store, schema validation, fan-out across many records, and a human approval pause, with a Bedrock call sitting inside one step of each item. Step Functions integrates directly with a large number of AWS services, fans work out with its Map state, runs branches in parallel, waits on a callback token for a human decision, and invokes Bedrock as an optimised integration where the model is genuinely needed. When the generative work is one participant in a longer business process, the orchestrator that reaches furthest across the rest of AWS should own the sequence.
Last, what the run has to prove afterwards. The operations team wants to open last night’s run and see which document took which path, which was escalated, which field was overwritten. A Step Functions execution keeps a history you can inspect state by state and query long after the run ended, so the record is a property of the service rather than something you assemble from logs. Model-chosen control flow leaves you a reasoning trace instead, which is thin evidence when the question is whether a required step ran on every document.
So the deciding line is not really a model-driven engine against a drawn one. It is whether this is a generative pipeline that happens to branch, or a durable business process that happens to call a model.
What we’ll filter on
- Who decides the control flow, the model at run time or a designer ahead of time?
- Does the run need durable, long-running, resumable execution with per-step retries and error handling?
- How much does the job reach beyond the model into other AWS services, fan-out, parallelism, and human-approval waits?
- How provable and auditable does each run need to be?
- Is the GenAI work the whole job, or one step inside a larger process?
The landscape
An agent
The foundation model runs the reason-act-observe loop over an instruction prompt, a set of tools, and optional knowledge bases, choosing each step as it goes. On Bedrock the hosting is AgentCore, with the tools reached through a gateway.
It suits a job whose path varies request to request and cannot be drawn in advance. Against an overnight batch the gap is not the nondeterminism so much as the missing machinery: an agent invocation has no durable execution model, no per-step retry policy, no fan-out primitive, and no way to hold for a two-day human decision. Every one of those becomes yours to build and operate.
The sibling piece on orchestrating multiple agents covers the multi-agent extension of this shape.
A Bedrock Flow
A drawn graph of Bedrock-native nodes, prompts, knowledge bases, agents, Lambdas, conditions and iterators, wired together with data links. The designer fixes the order and the model works inside a node. The node types, the versioning, and the alias promotion are covered in the deterministic pipeline built as a Flow.
For a single-pass pipeline over Bedrock building blocks that is the least assembly for the most predictability. This job is not that shape. A Flow executes its graph for one invocation, so hours of run time, thousands of records fanned out with independent retries, and a reviewer taking two days all sit past what the runtime carries.
An AWS Step Functions state machine
The general-purpose durable workflow orchestrator in AWS, and the only one of the three that is not a Bedrock feature. A state machine is a set of states. Task states call a service, a Lambda, or a model. Choice states branch on the data. Parallel states run several branches at once, a Map state runs one branch per item in a collection, and wait states hold for a duration or a timestamp. Success and failure states end the run.
There are two workflow types and the split bites here. A Standard execution is durable, can run for up to a year, is billed per state transition, and keeps an execution history you can query long after it finishes. An Express execution is capped at five minutes, is billed on duration and memory, and suits high-volume short-lived work where that history is not wanted.
Failure handling is declared rather than written. A state carries Retry blocks that match error names with their own interval, backoff rate, and attempt limit, and Catch blocks that route a named error to a recovery state. Waiting on something outside the workflow is declared the same way: a task invoked with the wait-for-callback pattern hands out a task token and stays in that state until something calls back with it, which turns a two-day human decision into a state rather than a queue you maintain.
Take it when the overnight qualities dominate: surviving failures across hours, fanning out over a collection, reaching across AWS, and holding for a person, with the model participating rather than conducting. The cost is that you design and maintain the state machine, and for a job that is purely model reasoning it is more scaffolding than the work needs.
Evaluation
Side by side
| Property | Bedrock agent | Bedrock Flow | Step Functions |
|---|---|---|---|
| Control flow decided by | Model, at run time | Designer, ahead of time | Designer, ahead of time |
| Longest single run | Bounded by the invocation | Bounded by the invocation | ✓ (up to 1 year, Standard) |
| Recovers a failed step for you | ✗ | ✗ | ✓ (Retry and Catch per state) |
| Fan-out across a batch | ✗ | Iterator over a set | ✓ (Map, Parallel) |
| Pause for days on a human | Build it yourself | ✗ | ✓ (wait-for-callback task token) |
| Reach across AWS services | Via action-group Lambdas | Bedrock-centric plus Lambda | ✓ (broad direct integrations) |
| Record of what happened | Model reasoning trace | Node-by-node graph | ✓ (queryable execution history) |
| Best when | Path must be discovered at run time | Single-pass Bedrock-native pipeline | The model is one step in a durable process |
Reading it for the document job: the sequence is known, so the model-driven engines are being asked the wrong question; the run is long, retry-heavy, fans out over thousands of records, and pauses for a human, which is more than a Flow is built to carry; the state machine is the fit.
How to route it
The solution
Build it as a Step Functions state machine, with Bedrock as one task among many. Everything the job actually demands, surviving a throttle, surviving a Lambda timeout, waiting two days on a reviewer, and being auditable document by document afterwards, is a first-class primitive there and code you would otherwise write and operate.
Model the document job directly. A Map state fans out across the batch. For each document, a task state invokes Bedrock to extract the fields, a choice state checks the validation result, a retry policy on the extraction state handles throttling with exponential backoff, and a catch handler routes a repeat failure to a wait-for-callback task that pauses on a task token until a reviewer decides. Success and failure states close each item out.
Pick the workflow type on run length. Standard gives durable execution up to a year with a full history you can inspect state by state, which is what the operations team is asking for when they want to open a run and see which documents took which path. Express suits short, high-volume runs and does not carry that history, so it is the wrong half of the choice here.
The cost is honest: you design and maintain the state machine. That is effort worth spending when the workflow is the product and the model is a participant, and effort wasted on a job that is purely model reasoning.
Why not an agent. The model handles branching for free, and that is worth its nondeterminism when the path has to be discovered. This path is known in advance. Asking an agent to carry it means asking it to be a durable workflow engine, and it is not one: building retries, resumability, a two-day human pause, and an audit trail around an agent is rebuilding Step Functions by hand, with none of the guarantees.
Why not a Flow. A Flow draws a fixed graph with little assembly and traces node by node, which suits a single-pass, Bedrock-native pipeline well. This job is none of those things. It runs for hours, fans out across thousands of records with independent per-item retry, and waits on a human. That is the state machine’s territory, not the Flow’s.
Where they combine. The engines layer, and the answer here stays a state machine because of it. A Step Functions task state can invoke a Bedrock model, an agent, or a Flow, so when one step in this spine later needs the model to choose its own path, that step becomes an agent invocation inside the state machine rather than a reason to abandon it.
Worked example
The job: a few thousand supplier documents, extract fields, validate, store, escalate two-time failures to a human.
Framed as an agent. One agent per document reads the file, calls an extract tool, checks the schema, and on a second failure calls a tool that notifies a reviewer. It works for a single document in isolation, but the batch has no home: nothing durably tracks three thousand in-flight runs, nothing retries a throttled model call with backoff for you, and nothing pauses cleanly for a two-day human response. You would wrap the agent in your own queue, retry logic, and state store, which is a workflow engine you are now maintaining.
Framed as a Flow. A Flow expresses the per-document happy path well: input, extract via a prompt or agent node, condition on validity, branch to store or to a notify Lambda. It falls short on the batch shape. Fanning out over thousands of records with independent retry policies, running branches in parallel, and pausing days for a reviewer are past what the Flow runtime is meant to carry.
Framed as a state machine. A Map state iterates the batch. Per document: a task state invokes Bedrock to extract, with a retry policy for throttling and a catch for hard errors; a choice state branches on the validation outcome; a failed document goes to a wait-for-callback state that holds on a task token until a reviewer resolves it, then rejoins; a clean document writes to the store. The run is durable across the whole night, every document has an inspectable history, and the model call is one state among many. This is the framing that fits the job; the other two were rebuilding a workflow engine that already exists.
What’s worth remembering
- A sequence you can draw on a whiteboard does not need a model choosing its order, so for a known workflow the real choice is between the two runtimes that walk a drawn sequence.
- A Bedrock agent has no built-in durable execution, retry policy, or human-pause primitive. Wanting those around an agent is a sign the job is really a workflow.
- Map and Parallel states give fan-out and parallelism; the callback task-token pattern gives a durable human-approval pause. None of these is native to an agent.
- The engines combine. A Step Functions state machine can invoke a model, an agent, or a Flow as a task, so a durable outer workflow can wrap a model-driven inner step where flexibility pays off.
- When the generative work is one step inside a longer business process, that process should own the sequence and the model should be a task inside it. Read that way, the state machine goes on the outside and Bedrock goes in a state.