,

Amazon Bedrock Agents, Action Groups, and the AgentCore Shift (AWS Gen AI Series, Part 13)

How Bedrock agents turn one question into several model calls, how action groups and return of control work, what a request really costs, and why new builds now start on AgentCore.

AWS Gen AI Series · Part 13 of 30

Try to create your first Bedrock agent after July 30, 2026 and the console will not let you. Amazon Bedrock Agents, the managed service this part is named for, is now Bedrock Agents Classic, and it closed to new customers on that date. Agents already running on it keep working. Anyone starting today gets pointed at a different door called AgentCore.

So this part does two jobs. It teaches the agent primitive that Classic made popular, because the ideas carry straight over, and it shows where that primitive lives in 2026. An agent, in one clause, is a model given instructions, a set of actions it can call, and a loop that lets it decide which action to run before it answers. Everything below builds on that sentence.

Who this is for: You have called a model with the Converse API in Part 11 and grounded it on your own documents with a knowledge base in Part 12. You now want the model to take an action, not just answer. No agent framework background assumed. I define action groups, orchestration, return of control, and memory as they come up.
TL;DR: A Bedrock agent is a foundation model wrapped in a loop that can call tools. You give it instructions, one or more action groups that define what it can do, optional knowledge bases for retrieval, and memory to carry context across sessions. At run time InvokeAgent runs an orchestration loop: the model reads the request, decides which action to call, invokes a Lambda or hands control back to you, reads the result, and repeats until it can answer. The catch is cost. One user question becomes three or four model calls, so token spend multiplies against a single Converse call. Bedrock Agents Classic closed to new customers on July 30, 2026, and AgentCore is the successor, a set of modular services you assemble around any framework and any model. Learn the primitive here; build new work on AgentCore.

What a Bedrock agent actually does

A plain model call answers from what sits in its context window and stops. It cannot look up an order, issue a refund, or query a database, because those are actions in the world and a model only produces text. An agent closes that gap. It keeps the model in charge of reasoning but gives it a menu of actions it can trigger, and a runtime that carries out whichever one the model picks, feeds the result back, and lets the model decide what to do next. The model plans. The runtime executes. That division is the whole idea.

Bedrock Agents Classic is the managed version of that runtime. You describe the agent once at build time, and Bedrock assembles the prompts, runs the loop, and manages the conversation. There are two sets of API operations behind it. Build-time operations such as CreateAgent, CreateAgentActionGroup, and PrepareAgent define and package the agent. One runtime operation, InvokeAgent, sends user input and drives the loop until the agent returns an answer. You configure the pieces; Bedrock runs the machinery between them.

How the orchestration loop turns one question into several model calls

Orchestration is the loop InvokeAgent runs, and understanding it is the difference between an agent that feels magical and a bill that surprises you. A request moves through three stages. Preprocessing validates and categorizes the input and can reject something malformed before any real work happens. Orchestration is the core: the model reads the augmented prompt, writes out a rationale for its next step, and predicts either an action to invoke or a knowledge base to query. If it picks an action, Bedrock runs the associated Lambda or returns control to you, then feeds the result back as an observation and asks the model again. That inner cycle repeats until the model has enough to answer. Postprocessing formats the final response and is turned off by default.

Here is the part people miss. Every pass through that loop is a separate model invocation with its own input and output tokens, and the input grows each pass because the prompt now carries the previous rationale and observation. A question that needs one tool call and a final answer is not one model call. It is preprocessing, at least two orchestration turns, and possibly a knowledge base generation step on top. Turn on a trace at run time and you can watch each step, its rationale, and the full prompt sent to the model, which is how you see the multiplication for yourself.

How one question becomes several model callsThe orchestration loop repeats until the agent can answerUser inputthe questionPreprocessingvalidate inputOrchestrationFM picks next stepPostprocessoff by defaultResponseAction groupLambda or your APIKnowledge baseretrieve contextobservationloops backEach loop pass is a separate model call, and the input grows every pass.
Preprocessing runs once. Orchestration cycles through actions and observations until the model answers.

Action groups, knowledge bases, and memory

An agent is only as capable as the actions you give it. An action group is a named bundle of actions the agent can take, and you define it one of two ways. An OpenAPI schema describes REST operations the agent can call, with the parameters it needs to gather from the user. A function detail schema is lighter: you list functions and their parameters without a full API spec. Either way you attach a Lambda function that receives the operation and parameters the model chose and runs the real work, or you tell the action group to return control and run that work yourself.

Knowledge bases plug in as a second kind of tool. Associate the knowledge base you built in Part 12, and the agent can query it for context mid-loop instead of guessing, then fold the retrieved passages into its answer. Memory is the third piece. Enable memoryConfiguration and the agent retains a summary of prior sessions, so a returning user does not start from nothing. Prompt templates sit underneath all of it: Bedrock exposes the base prompts used at each loop stage, and you can edit them, but on a first build you leave them alone.

ComponentWhat it isWhat you provide
Foundation modelThe model that reasons at each stepPick a Bedrock model
InstructionsPlain text describing the agent jobA few clear sentences
Action groupsThe actions the agent can takeOpenAPI or function schema, optional Lambda
Knowledge basesRetrieval sources the agent can queryAttach a Part 12 knowledge base
MemoryContext kept across sessionsEnable memoryConfiguration
Prompt templatesBase prompts per loop stageEdit only if you must

The model and instructions are required. You need at least one action group or one knowledge base for the agent to do anything useful.

In practice: I start with a function detail schema, not an OpenAPI spec. It is faster to write, it keeps the parameter list in front of me, and I can bolt on the full OpenAPI schema later when the action group needs to describe a real REST surface. The one thing I never skip is tight instructions. A vague instruction string is the single biggest cause of an agent calling the wrong action or looping longer than it should.

Build and invoke an agent in code

Building an agent is a two-client job. The bedrock-agent client covers build time: create the agent, add action groups, then prepare it, which packages everything into a testable state. The bedrock-agent-runtime client covers run time with InvokeAgent. The call below assumes the agent is already prepared and an alias exists, and it drives one turn end to end. InvokeAgent does not return a plain string. It returns an event stream you iterate, with the answer arriving in chunk events.

import boto3
agent_rt = boto3.client('bedrock-agent-runtime', region_name='us-east-1')

resp = agent_rt.invoke_agent(
    agentId='AGENT123ABC',
    agentAliasId='TSTALIASID',      # test alias; use a real alias in prod
    sessionId='session-01',
    inputText='Refund order 4021, it arrived damaged.',
)

for event in resp['completion']:
    if 'chunk' in event:
        print(event['chunk']['bytes'].decode('utf-8'), end='')
Expected output: the final answer text streams to the console, such as a confirmation that a refund was started for order 4021. Failure mode: a ResourceNotFoundException on the agent id usually means the agent was never prepared, so run PrepareAgent first. Using the built-in TSTALIASID draft alias in production is the other common trap: it points at the working draft, not a versioned alias, so create a real alias and pin your app to it.

The sessionId is what ties turns together. Reuse the same value across calls and the agent keeps the conversation; change it and you start fresh. That single string is the seam between a stateless call and a stateful conversation, and forgetting to hold it steady is why a second question sometimes acts like the first never happened.

Before you change production

Preparing an agent and pointing an alias at a new version changes what real users hit on the next request. Test against the draft alias, cut a numbered version, move the production alias deliberately, and keep the previous version so you can roll the alias back in one call. This is general guidance, not a substitute for your own change review.

Return of control when you will not run a Lambda

Sometimes you do not want Bedrock invoking a Lambda on your behalf. The action touches a system that lives behind your own network, or you want a human to approve it, or the code already runs somewhere you control. Set the action group to return control and the loop changes shape. Instead of calling a Lambda, InvokeAgent stops and hands you the action it wants to run, with the parameters the model elicited, in a returnControl block on the stream. You execute it however you like, then call InvokeAgent again with the result in sessionState so the loop can continue.

# Pull the requested action out of the returnControl event
rc = event['returnControl']
call = rc['invocationInputs'][0]['functionInvocationInput']
result = run_refund(call['parameters'])          # your own code runs it

# Send the result back so the agent can finish the loop
agent_rt.invoke_agent(
    agentId='AGENT123ABC', agentAliasId='PRODALIAS1', sessionId='session-01',
    sessionState={
        'invocationId': rc['invocationId'],
        'returnControlInvocationResults': [{'functionResult': {
            'actionGroup': call['actionGroup'],
            'function': call['function'],
            'responseBody': {'TEXT': {'body': result}}}}],
    },
)
Expected output: the second InvokeAgent resumes the loop and streams the final answer, now grounded in the result your code produced. Failure mode: a validation error on invocationId means it did not match the one from the returnControl event, so pass it back verbatim rather than generating a new session id. Field names in the returnControl and result payloads are precise; confirm them against the current API reference before you ship. [VERIFY]

What an agent request costs

Bedrock Agents Classic does not add a service fee on top of the model. You pay for the model invocations the loop makes, plus Lambda for any action group that runs one, plus any knowledge base you attached [VERIFY current pricing]. That sounds cheap until you remember the loop multiplies the model calls. The cost of an agent request is not the cost of one answer. It is the sum of every orchestration turn, each with input that grew as the loop went on.

Worked example

A support agent on Amazon Nova Pro handles one refund request. Preprocessing reads 1,400 input tokens and writes 60. Orchestration turn one reads 2,100 and writes 220 as it decides to call the refund action. After the observation, orchestration turn two reads 2,900 and writes 320 to compose the answer. That is 6,400 input and 600 output tokens for a single user question. At Nova Pro list rates of 0.0008 dollars per 1,000 input tokens and 0.0032 per 1,000 output, the request costs about 0.0070 dollars.

Fractions of a cent, until you scale. Fifty thousand of these a month is about 352 dollars in model spend alone, before Lambda and any knowledge base. A single Converse call answering the same question would cost roughly a third as much, because it makes one model call, not three.

Loop stageInput tokensOutput tokensCost, Nova Pro
Preprocessing1,400600.0013
Orchestration 12,1002200.0024
Orchestration 22,9003200.0033
Total per request6,4006000.0070

Three model calls for one question. The input column climbs because each turn carries the last rationale and observation forward.

Input tokens per model call, one requestThe prompt grows every orchestration turn01000200030001400preprocessing2100orchestrate 12900orchestrate 2
Three calls, rising input. This is why an agent costs more than a single Converse call.

Plot that per-request cost against volume and the line is simply linear, because there is no fixed floor here the way there was with the knowledge base store in Part 12. Model spend scales one for one with requests, so the lever is the number of loop turns, not a capacity choice. Cut an orchestration turn by writing tighter instructions and you cut a model call off every request in the month.

Monthly model spend as requests growNova Pro, three calls per request, US dollars020040060080010k50k100k70352704
No fixed floor, a straight line. Fewer loop turns is the only real cost lever.

Classic Agents vs AgentCore, and why AWS split them

Classic bundles the whole agent into one managed object: a model, a loop, action groups, memory, all wired together and all inside Bedrock. That is a gift when your design fits the shape and a wall when it does not. You are on Bedrock models only. You cannot swap in a LangGraph or CrewAI loop you already run. The managed loop is the loop you get. AWS heard that limit often enough to build the other thing.

AgentCore unbundles it. Rather than one managed agent, it is a set of services you assemble around whatever framework and model you already use: Runtime for serverless execution with session isolation and long-running support, Memory for short and long term context, Gateway to turn APIs and Lambdas into tools an agent can call, Identity for auth against your existing provider, plus Code Interpreter, Browser, and Observability. You bring the agent logic, in Strands or LangGraph or the OpenAI Agents SDK, and use only the services you need. That flexibility is why new customers land here, and why AWS closed Classic to new signups rather than growing two managed loops in parallel.

DimensionBedrock Agents ClassicAgentCore
Model choiceBedrock models onlyAny model, in or outside Bedrock
FrameworkAWS managed loopLangGraph, CrewAI, Strands, ADK, others
New customersClosed after Jul 30, 2026Open, the path forward
Building blocksOne managed agentModular: Runtime, Memory, Gateway, Identity, more
Pricing shapeModel tokens plus Lambda and KBConsumption per service, vCPU-hour and GB-hour
Best forAgents already shipped on itAnything new

AgentCore Runtime bills roughly 0.0895 dollars per vCPU-hour and 0.00945 per GB-hour on active consumption [VERIFY current rate], a different meter than Classic token spend.

One bundle, or parts you assembleClassic wraps everything. AgentCore hands you the pieces.CLASSICOne managed agentBedrock modelOrchestration loopAction groupsKnowledge basesMemoryall inside Bedrock, one boxAGENTCORERuntimeMemoryGatewayIdentityCode InterpBrowserObserv.Policyyourframeworkpick only the services you need
Same job, two philosophies. Classic decides the shape for you; AgentCore lets you build it.

Where agents break in production

The failure I see most is a runaway loop. Give an agent loose instructions and an ambiguous request and it will take extra orchestration turns to decide what to do, and each turn is a model call you pay for and a second of latency the user feels. Tight instructions and a small, sharply described action group cut turns more than any model change. The trace is how you catch it: read the rationale at each step and you can usually see the exact turn where the model dithered.

The second is the alias trap from the code section, at scale. Ship against the draft test alias and every prepare of your working draft instantly changes production behavior, with no version to roll back to. The third is silent action failure: when an action group Lambda throws, the agent often narrates a plausible answer around the gap rather than surfacing the error, so a broken tool looks like a slightly-off response instead of a failure. Watch the Lambda error rate next to the agent, not just the agent output.

Gotcha: An agent that answers confidently is not proof its tools ran. If an action group Lambda fails, the model can still compose a fluent reply that hides the failure. Alarm on the Lambda error rate and on unusually high orchestration-turn counts, because both go wrong quietly while the text output still looks fine. The pattern to fear is an action group Lambda that throws, the agent catches nothing useful, and the model fills the gap with a fluent, confident answer built from no real data. The text reads fine to a reviewer, so the only reliable signal is the Lambda error rate sitting next to the agent metrics.

My take

If you already run a real agent on Classic, leave it. It keeps working, it is stable, and a rewrite for its own sake is wasted quarters. But do not start anything new there, and not only because signups are closed. AgentCore lets me keep the framework and model I want and pay for isolated runtime by the second, which fits how agents actually behave, bursty and idle between requests. The primitive in this part is the thing to understand. AgentCore is where I now build it.

Build new agents on AgentCore, keep Classic for what already ships

Here is the posture I ship. Understand the agent primitive first, because it is the same everywhere: a model that reasons, a set of actions it can call, and a loop that runs those actions and feeds the results back. On Classic that means a foundation model, action groups defined by a function or OpenAPI schema, optional knowledge bases, and memory, all driven by InvokeAgent through preprocessing, orchestration, and postprocessing. Design the instructions and the action group tightly, because the loop turns they save are the cost you actually control. Prefer return of control when the action touches your own systems or needs a human in the path. And never point production at the draft alias.

For anything new, build on AgentCore. Keep the framework and model you like, assemble only the services you need, and treat Classic as the place your existing agents live out their lives, not where the next one starts. If you also run on Azure, its equivalent is Azure AI Agent Service, covered in the Azure Gen AI guide. Next in this series I put a fence around all of this with Bedrock Guardrails, so the agent you just built can act without saying or doing something it should not. Before you read on, open one agent trace in your account and count the model calls behind a single answer. That number is your real per-request cost.

AWS Gen AI Series · Part 13 of 30
« Previous: Part 12  |  Guide  |  Next: Part 14 »

Related reading: the vendor-neutral GenAI guide explains agents and tool use at the concept level, without the Bedrock specifics.

References

How Amazon Bedrock Agents works
Return control to the agent developer
Amazon Bedrock AgentCore overview
Amazon Bedrock AgentCore pricing

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading