Packages

Provider-agnostic Elixir client for agent runtimes — one Session loop, any loop host: server-side (Anthropic Claude Managed Agents, AWS Bedrock AgentCore) or in-process (Local, over any OpenAI-compatible chat endpoint). Your tools run locally.

Current section

Files

Jump to
req_managed_agents examples bedrock_agent_core.exs
Raw

examples/bedrock_agent_core.exs

# AWS Bedrock AgentCore Harness: provision → run → tear down.
#
# Same thesis as the Claude example — the provider runs the loop, your tools
# run here — but on AWS's managed runtime. AgentCore Harness is
# `:request_response`: each turn is one SigV4-signed `InvokeHarness` call, and
# a tool round-trip resumes the loop by re-sending the assistant `toolUse` +
# your `toolResult`.
#
# Requires the optional AWS deps (see the README install section):
#
# {:ex_aws_auth, "~> 1.4"},
# {:aws_event_stream, "~> 0.1"}
#
# Run it with AWS credentials in the environment (standard AWS_* vars; STS
# temporary credentials work — signing is session-token aware) plus the ARN of
# an execution role the harness will assume:
#
# AWS_REGION=us-east-1 \
# HARNESS_EXECUTION_ROLE_ARN=arn:aws:iam::<acct>:role/<your-exec-role> \
# mix run examples/bedrock_agent_core.exs
#
# The execution role needs bedrock:InvokeModel* on your chosen model, plus the
# AgentCore logging/identity permissions — and note that the CALLER (you)
# needs CreateHarness/InvokeHarness AND the permissions AgentCore exercises on
# your behalf during creation (endpoint, workload identity, memory). Consult
# your AWS admin or the AgentCore docs for the full matrix.
defmodule Demo.Handler do
@moduledoc false
@behaviour ReqManagedAgents.Handler
@impl true
def handle_tool_call("lookup_customer", %{"email" => email}, _ctx),
do: {:ok, "Customer #{email}: Pro plan, active, last invoice $49.00 on 2026-05-01."}
@impl true
def handle_event(_ev, _ctx), do: :ok
end
{:ok, _} = Application.ensure_all_started(:req_managed_agents)
alias ReqManagedAgents.Providers.BedrockAgentCore
# ── 1. Provision the harness (idempotent; cached per {provider, spec}) ──────
#
# The spec is the provider-agnostic shape from `ReqManagedAgents.Provider`:
# system prompt, tool schemas, and a provider-native model config. The tool
# entries use AgentCore's wire format (`inline_function`); only the SCHEMA
# ships — the implementation stays in your Handler.
#
# `provision/3` is create-or-reuse: it survives name collisions with an
# existing READY harness and polls until the harness is READY (creation takes
# a minute or two). Store the returned handle in a real app.
spec = %{
system_prompt: "You are a concise billing-support agent. Use tools for customer data.",
terminal_tool: nil,
model_config: %{
# Anthropic models on Bedrock must use the cross-region inference profile
# id (`us.`-prefixed) — the bare model id has no in-region support.
"bedrockModelConfig" => %{"modelId" => "us.anthropic.claude-haiku-4-5-20251001-v1:0"}
},
tools: [
%{
"type" => "inline_function",
"name" => "lookup_customer",
"config" => %{
"inlineFunction" => %{
"description" =>
"Look up a customer by email and return plan, status, and last invoice. " <>
"Always call this for customer data; never guess.",
"inputSchema" => %{
"type" => "object",
"properties" => %{"email" => %{"type" => "string"}},
"required" => ["email"]
}
}
}
}
]
}
{:ok, handle} =
ReqManagedAgents.provision(BedrockAgentCore, spec,
execution_role_arn: System.fetch_env!("HARNESS_EXECUTION_ROLE_ARN"),
name_prefix: "example"
)
IO.puts("provisioned harness #{handle.harness_id}")
# ── 2. Run a session — the provider-agnostic entrypoint ─────────────────────
#
# Note the AgentCore session-id contract: 33–100 chars, [a-zA-Z0-9-_].
{:ok, result} =
ReqManagedAgents.Session.run(BedrockAgentCore,
harness_arn: handle.harness_arn,
runtime_session_id:
"example-" <> Base.url_encode64(:crypto.strong_rand_bytes(24), padding: false),
prompt: "What plan is jane@acme.com on, and when was she last billed?",
handler: Demo.Handler
)
IO.puts("""
terminal: #{result.terminal}
turns: #{result.turns}
tokens: #{result.usage.input_tokens} in / #{result.usage.output_tokens} out
#{result.text}
""")
# ── 3. Tear down (AgentCore deletion is async and takes minutes) ────────────
#
# In a real app you'd keep the harness and reuse it — provisioning is the
# expensive part. This example cleans up after itself. Note: re-provisioning
# the same spec immediately after teardown can hit a name conflict while the
# old harness is still DELETING.
:ok = ReqManagedAgents.teardown(BedrockAgentCore, handle)
IO.puts("teardown requested (deletion completes asynchronously)")