Bedrock Agents
Bedrock Agents is the managed orchestrator that turns a foundation model into a goal-oriented agent: it parses user input, plans a sequence of steps, calls your APIs (action groups), queries your knowledge bases, and produces a final answer — all without you writing the orchestration loop. The trade-off vs hand-rolling agent loops in Converse is that you give up fine-grained control over the orchestration prompt in exchange for not having to maintain it. Bedrock manages the planning prompt, the tool-use parsing, the session state, the memory, and the traces. Since re:Invent 2024, multi-agent collaboration lets you compose a “supervisor” agent that delegates to “collaborator” sub-agents.
See also
- bedrock-api-and-converse — the lower-level tool-use API you’d use if you wanted to write your own agent loop
- bedrock-knowledge-bases-rag — knowledge bases are an optional component of an agent
- bedrock-guardrails — agents can have a guardrail attached
- agents-and-orchestration-patterns — vendor-neutral agent orchestration patterns
- claude-agent-sdk — Anthropic’s competing agent orchestration SDK
1. Components of a Bedrock Agent
Per docs.aws.amazon.com/bedrock/latest/userguide/agents.html:
| Component | Purpose | Required |
|---|---|---|
| Foundation model | The underlying LLM (Claude, Nova, Llama, etc.) | Yes |
| Instructions | System prompt — tells the agent its role, scope, tone | Yes |
| Action groups | Sets of related “tools” the agent can invoke. Backed by Lambda or a function-schema-only stub. | At least one of: action groups or knowledge base |
| Knowledge bases | RAG sources attached to the agent | At least one of: action groups or knowledge base |
| Advanced prompt templates | Override the pre-processing, orchestration, KB-response-generation, or post-processing prompts | No |
| Guardrail | Apply a Bedrock Guardrail to all inputs and outputs | No |
| Memory | Long-term + session memory configuration | No |
| Collaborators | Sub-agents (for supervisor agents) | No |
An agent has versions (immutable snapshots) and aliases (mutable pointers to versions). Your application invokes InvokeAgent against an alias ARN.
2. Creating an agent — boto3 walkthrough
import boto3
agent_control = boto3.client("bedrock-agent", region_name="us-east-1")
# 1. Create the agent
resp = agent_control.create_agent(
agentName="travel-booker",
foundationModel="anthropic.claude-sonnet-4-6-20260119-v1:0",
instruction=(
"You are a travel assistant. Help users book flights and hotels. "
"When you have enough information, use the booking action group. "
"Always confirm before charging the user."
),
agentResourceRoleArn="arn:aws:iam::123456789012:role/AmazonBedrockExecutionRoleForAgents",
idleSessionTTLInSeconds=1800, # 30 min idle timeout
promptOverrideConfiguration={...}, # optional advanced prompts
guardrailConfiguration={
"guardrailIdentifier": "abc-123",
"guardrailVersion": "1",
},
memoryConfiguration={
"enabledMemoryTypes": ["SESSION_SUMMARY"],
"storageDays": 30,
},
)
agent_id = resp["agent"]["agentId"]
# 2. Add an action group (backed by Lambda)
agent_control.create_agent_action_group(
agentId=agent_id,
agentVersion="DRAFT",
actionGroupName="booking",
actionGroupExecutor={
"lambda": "arn:aws:lambda:us-east-1:123456789012:function:travel-booking-handler"
},
apiSchema={
# OpenAPI 3.0 schema describing the action group's operations
"s3": {
"s3BucketName": "my-agent-schemas",
"s3ObjectKey": "booking-api.yaml",
}
},
description="Book flights and hotels.",
)
# 3. Attach a knowledge base
agent_control.associate_agent_knowledge_base(
agentId=agent_id,
agentVersion="DRAFT",
knowledgeBaseId="ABC123KB",
description="Loyalty program rules and FAQs.",
knowledgeBaseState="ENABLED",
)
# 4. Prepare the agent (build the DRAFT version)
agent_control.prepare_agent(agentId=agent_id)
# 5. Create a version + alias
v_resp = agent_control.create_agent_version(agentId=agent_id)
version_number = v_resp["agentVersion"]["version"]
alias_resp = agent_control.create_agent_alias(
agentId=agent_id,
agentAliasName="prod",
routingConfiguration=[{"agentVersion": version_number}],
)
alias_id = alias_resp["agentAlias"]["agentAliasId"]3. Invoking an agent
agent_runtime = boto3.client("bedrock-agent-runtime", region_name="us-east-1")
response = agent_runtime.invoke_agent(
agentId=agent_id,
agentAliasId=alias_id,
sessionId="user-7281-session-abc", # any string; same session = continued conversation
inputText="Book me a flight to Paris next Friday under $800.",
enableTrace=True, # include the orchestration trace
sessionState={ # optional — pre-populate session attrs
"sessionAttributes": {"userId": "u_7281", "tier": "platinum"},
"promptSessionAttributes": {"loyaltyNumber": "AB1234"},
},
)
# Response is an event stream
for event in response["completion"]:
if "chunk" in event:
text = event["chunk"]["bytes"].decode("utf-8")
print(text, end="", flush=True)
elif "trace" in event:
# Inspect the orchestration trace (model reasoning + tool calls)
trace = event["trace"]["trace"]
if "orchestrationTrace" in trace:
ot = trace["orchestrationTrace"]
if "rationale" in ot:
print(f"\n[REASONING] {ot['rationale']['text']}", file=sys.stderr)
if "invocationInput" in ot:
print(f"\n[TOOL CALL] {ot['invocationInput']}", file=sys.stderr)
if "observation" in ot:
print(f"\n[OBSERVATION] {ot['observation']}", file=sys.stderr)4. Action groups — two backends
4.1 Lambda action group
The default. You write a Lambda function that handles the invocation. The function receives the agent’s chosen API operation + parameters and returns a JSON response. Bedrock takes care of the model ⇄ function plumbing.
# Lambda handler (Python)
def lambda_handler(event, context):
api_path = event["apiPath"]
parameters = {p["name"]: p["value"] for p in event["parameters"]}
if api_path == "/bookFlight":
result = book_flight(
origin=parameters["origin"],
destination=parameters["destination"],
date=parameters["date"],
max_price=float(parameters.get("max_price", 0)),
)
elif api_path == "/getFlightStatus":
result = get_flight_status(parameters["flightNumber"])
else:
result = {"error": "unknown operation"}
return {
"messageVersion": "1.0",
"response": {
"actionGroup": event["actionGroup"],
"apiPath": api_path,
"httpMethod": event["httpMethod"],
"httpStatusCode": 200,
"responseBody": {
"application/json": {"body": json.dumps(result)},
},
},
"sessionAttributes": event.get("sessionAttributes", {}),
"promptSessionAttributes": event.get("promptSessionAttributes", {}),
}The API schema (OpenAPI 3.0 YAML) tells the agent what operations are available and what parameters each takes. Example:
openapi: 3.0.0
info:
title: Travel Booking API
version: 1.0.0
paths:
/bookFlight:
post:
summary: Book a flight
description: Books a flight given origin, destination, date, and max price.
operationId: bookFlight
parameters:
- name: origin
in: query
required: true
schema: { type: string }
description: IATA airport code of origin
- name: destination
in: query
required: true
schema: { type: string }
- name: date
in: query
required: true
schema: { type: string, format: date }
- name: max_price
in: query
required: false
schema: { type: number }
responses:
'200':
description: Booking confirmation4.2 Function-schema action group (no Lambda)
Per re:Invent 2024: you can define an action group with just a function schema and have the agent emit the tool call as a RETURN_CONTROL event, which your application code handles directly. Skip Lambda entirely.
agent_control.create_agent_action_group(
agentId=agent_id,
agentVersion="DRAFT",
actionGroupName="booking",
actionGroupExecutor={
"customControl": "RETURN_CONTROL" # ← no Lambda
},
functionSchema={
"functions": [
{
"name": "book_flight",
"description": "Book a flight",
"parameters": {
"origin": {"type": "string", "required": True},
"destination": {"type": "string", "required": True},
"date": {"type": "string", "required": True},
"max_price": {"type": "number", "required": False},
},
}
]
},
)
# In your invoke loop, handle RETURN_CONTROL events
for event in response["completion"]:
if "returnControl" in event:
invocation_inputs = event["returnControl"]["invocationInputs"]
for inp in invocation_inputs:
fn = inp["functionInvocationInput"]
result = call_local_function(fn["function"], fn["parameters"])
# Send the result back via continueInvokeAgentUseful when your tools call services not reachable from Lambda (on-prem systems via VPN, customer-managed credentials, etc.).
5. Memory — long-term context
Per docs.aws.amazon.com/bedrock/latest/userguide/agents-memory.html.
| Memory type | What it is | Default retention |
|---|---|---|
| Session state | In-session sessionAttributes (visible only to your app) + promptSessionAttributes (visible to the model in every turn) | Session TTL (default 30 min idle) |
| Session summary | Auto-generated summary of past sessions, attached as system context on new sessions for the same memoryId | Configurable: 1–365 days |
# Enable memory
agent_control.update_agent(
agentId=agent_id,
agentName="travel-booker",
foundationModel="anthropic.claude-sonnet-4-6-20260119-v1:0",
instruction="...",
agentResourceRoleArn="...",
memoryConfiguration={
"enabledMemoryTypes": ["SESSION_SUMMARY"],
"storageDays": 30,
},
)
# Invoke with memoryId — agent will pull prior session summaries
agent_runtime.invoke_agent(
agentId=agent_id,
agentAliasId=alias_id,
sessionId="new-session",
memoryId="user-7281", # ← stable across sessions for this user
inputText="What was my last booking?",
)Limits: session summaries are model-generated text capped at ~3-5k tokens; only the most recent N sessions are summarized. Don’t use this as primary durable storage — write to DynamoDB or similar from your action groups.
6. Multi-agent collaboration
Per re:Invent 2024 + docs.aws.amazon.com/bedrock/latest/userguide/agents-multi-agent-collaboration.html. A supervisor agent can delegate to other agents (collaborators). Two routing modes:
SUPERVISOR— supervisor decides which collaborator to invoke based on the user’s request (LLM-as-router).SUPERVISOR_ROUTER— supervisor uses a deterministic classifier (cheaper, faster) and only falls back to LLM routing on ambiguous inputs.
# Create the supervisor
sup = agent_control.create_agent(
agentName="travel-supervisor",
foundationModel="anthropic.claude-sonnet-4-6-20260119-v1:0",
instruction="Delegate to the appropriate specialist agent for the user's request.",
agentResourceRoleArn="...",
agentCollaboration="SUPERVISOR_ROUTER", # ← enable multi-agent
)
sup_id = sup["agent"]["agentId"]
# Associate collaborators (which must already exist as agents)
agent_control.associate_agent_collaborator(
agentId=sup_id,
agentVersion="DRAFT",
collaboratorName="flight-specialist",
agentDescriptor={
"aliasArn": "arn:aws:bedrock:us-east-1:...:agent-alias/FLIGHT-AGENT/prod"
},
collaborationInstruction=(
"Delegate flight booking, change, and status queries to this agent."
),
relayConversationHistory="TO_COLLABORATOR", # collaborator sees prior turns
)
agent_control.associate_agent_collaborator(
agentId=sup_id,
agentVersion="DRAFT",
collaboratorName="hotel-specialist",
agentDescriptor={"aliasArn": "arn:aws:bedrock:us-east-1:...:agent-alias/HOTEL-AGENT/prod"},
collaborationInstruction="Delegate hotel-related queries to this agent.",
)
agent_control.prepare_agent(agentId=sup_id)When invoked, the supervisor decides whether to handle the request itself or pass it to a collaborator. Each turn is independently traceable via enableTrace=True; the trace shows which collaborator handled which sub-task.
7. Advanced prompt overrides
Bedrock Agents uses four hidden prompts under the hood:
PRE_PROCESSING— checks if the user query is safe / on-topic. Default model: Claude Haiku.ORCHESTRATION— the main agent loop: decides whether to invoke action groups, query knowledge bases, or respond.KNOWLEDGE_BASE_RESPONSE_GENERATION— generates a response from KB retrieval results.POST_PROCESSING— optional final pass to clean up or format the response.
You can override any of these with a custom template (with Bedrock variables like $conversation_history$, $question$, $prompt_session_attributes$):
agent_control.update_agent(
agentId=agent_id,
...,
promptOverrideConfiguration={
"promptConfigurations": [
{
"promptType": "ORCHESTRATION",
"promptCreationMode": "OVERRIDDEN",
"basePromptTemplate": "<my custom orchestration prompt with $variables$>",
"inferenceConfiguration": {
"temperature": 0.0,
"topP": 1.0,
"topK": 250,
"maximumLength": 2048,
"stopSequences": ["</response>"],
},
"promptState": "ENABLED",
"parserMode": "DEFAULT", # or "OVERRIDDEN" with a Lambda parser ARN
}
]
},
)Useful for: tightening orchestration to your domain, forcing JSON output, suppressing tool-use altogether in certain contexts. Risk: you take ownership of the planning loop — when models change behavior subtly, your override may stop working.
8. Tracing and observability
Per docs.aws.amazon.com/bedrock/latest/userguide/trace-events.html. The enableTrace=True flag turns on detailed event emission:
| Trace section | Contents |
|---|---|
preProcessingTrace | Pre-processing model input + output + validation result |
orchestrationTrace | Main loop: rationale, invocationInput (tool calls), observation (tool results) |
knowledgeBaseLookupInput / Output | KB queries + retrieved docs |
postProcessingTrace | Post-processing model input + output |
failureTrace | Errors encountered during orchestration |
guardrailTrace | Guardrail evaluations and which filter fired |
The trace is the only way to debug an agent — without it, you see only the final answer. Always enable it during development.
For production, write traces to CloudWatch via the agent’s IAM role permissions on logs:PutLogEvents. Don’t dump to stdout in Lambda action groups — use structured logging tagged with the session ID.
9. Costs
Bedrock Agents pricing has four components:
- Foundation model inference — billed at the underlying model’s per-token rate.
- Action group Lambda invocations — standard Lambda pricing.
- Knowledge Base queries — $0.001 per query if reranking enabled.
- Intelligent prompt routing — $1 / 1k requests for the
SUPERVISOR_ROUTERclassifier (re:Invent 2024 launch).
A typical multi-turn agent session burns 20–100k tokens of model inference + 0–5 Lambda invocations + 0–10 KB queries. Per-session cost on Sonnet 4.6 is typically $0.05–$0.50. The orchestration prompt itself is ~1.5–2k tokens — cached automatically.
10. When to use Bedrock Agents vs build your own
Use Bedrock Agents when:
- You want a managed orchestration loop with built-in tracing.
- Your team is already in the AWS ecosystem (IAM, Lambda, CloudWatch).
- Action groups map cleanly to existing internal APIs / Lambda functions.
- You need session memory but don’t want to build it.
Build your own (with bedrock-api-and-converse tool use) when:
- You need fine control over the orchestration prompt.
- Latency matters more than ops overhead — the agent loop adds 200-500ms per turn vs raw Converse.
- You want to switch models mid-conversation (Agents pin to one model per version).
- You’re outside AWS and don’t want the IAM/Lambda overhead.