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
Current section
Files
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."}
# Fires LIVE while the turn streams — one call per Converse event as it
# arrives (observational; at-least-once across retried attempts). The
# canonical exactly-once record is `SessionResult.events`.
@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-_].
#
# Long-run posture: the turn streams incrementally with NO client wall clock —
# only silence fails it (`idle_timeout`, default 300_000 ms between chunks).
# Total cost is bounded SERVER-side; the budgets below override the harness
# defaults (timeoutSeconds 3600, maxIterations 75) for this invocation only.
# Two things to know for long runs:
# * `Session.run/2`'s own `:timeout` (default 600_000 ms) must be ≥ your
# server budget — a client timeout returns {:error, :timeout} but does NOT
# cancel the in-flight turn; the harness keeps executing (and billing)
# server-side up to its `timeoutSeconds`.
# * Events reach `handle_event/2` live as the turn runs — you can watch a
# multi-minute turn progress instead of waiting for it to finish.
{: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,
timeout_seconds: 900,
max_iterations: 40
)
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)")