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

1. Components of a Bedrock Agent

Per docs.aws.amazon.com/bedrock/latest/userguide/agents.html:

ComponentPurposeRequired
Foundation modelThe underlying LLM (Claude, Nova, Llama, etc.)Yes
InstructionsSystem prompt — tells the agent its role, scope, toneYes
Action groupsSets 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 basesRAG sources attached to the agentAt least one of: action groups or knowledge base
Advanced prompt templatesOverride the pre-processing, orchestration, KB-response-generation, or post-processing promptsNo
GuardrailApply a Bedrock Guardrail to all inputs and outputsNo
MemoryLong-term + session memory configurationNo
CollaboratorsSub-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 confirmation

4.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 continueInvokeAgent

Useful 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 typeWhat it isDefault retention
Session stateIn-session sessionAttributes (visible only to your app) + promptSessionAttributes (visible to the model in every turn)Session TTL (default 30 min idle)
Session summaryAuto-generated summary of past sessions, attached as system context on new sessions for the same memoryIdConfigurable: 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:

  1. PRE_PROCESSING — checks if the user query is safe / on-topic. Default model: Claude Haiku.
  2. ORCHESTRATION — the main agent loop: decides whether to invoke action groups, query knowledge bases, or respond.
  3. KNOWLEDGE_BASE_RESPONSE_GENERATION — generates a response from KB retrieval results.
  4. 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 sectionContents
preProcessingTracePre-processing model input + output + validation result
orchestrationTraceMain loop: rationale, invocationInput (tool calls), observation (tool results)
knowledgeBaseLookupInput / OutputKB queries + retrieved docs
postProcessingTracePost-processing model input + output
failureTraceErrors encountered during orchestration
guardrailTraceGuardrail 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:

  1. Foundation model inference — billed at the underlying model’s per-token rate.
  2. Action group Lambda invocations — standard Lambda pricing.
  3. Knowledge Base queries — $0.001 per query if reranking enabled.
  4. Intelligent prompt routing — $1 / 1k requests for the SUPERVISOR_ROUTER classifier (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.

Further reading