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/provider_agnostic.exs
# Provider-agnostic: ONE handler, ONE loop — the provider is a parameter.
#
# This is the library's core claim, demonstrated: `ReqManagedAgents.Session.run/2`
# drives ANY provider to completion and returns the same
# `%ReqManagedAgents.SessionResult{}`. Between backends, only two things
# change:
#
# * the provider module (first argument)
# * its connection opts (`:agent_id`/`:environment_id` for Claude;
# `:harness_arn`/`:runtime_session_id` for AgentCore)
#
# Your tool handler, the result shape, the telemetry, and everything you build
# on top do not change. `result.terminal` is the uniform signal to branch on
# (`:end_turn` — finished normally; `:requires_action` — waiting on you;
# `:terminated` — stopped abnormally). `result.stop_reason` keeps each
# provider's raw native value (a map for Claude, a string for Bedrock) for
# when you need provider-specific detail.
#
# Prerequisites: resources provisioned on each backend you want to exercise —
# run examples/claude_managed_agents.exs and examples/bedrock_agent_core.exs
# first (or reuse existing ids), then:
#
# ANTHROPIC_API_KEY=... AGENT_ID=... ENVIRONMENT_ID=... \
# AWS_REGION=us-east-1 HARNESS_ARN=... \
# mix run examples/provider_agnostic.exs
alias ReqManagedAgents.Session
alias ReqManagedAgents.Providers.{ClaudeManagedAgents, BedrockAgentCore}
defmodule Demo.Handler do
@moduledoc false
@behaviour ReqManagedAgents.Handler
# Your private code + data; either provider only ever sees the text you
# return. THIS EXACT MODULE serves both backends below, unchanged.
@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)
prompt = "What plan is jane@acme.com on, and when was she last billed?"
# ── Backend A: Anthropic Claude Managed Agents (:streaming) ──────────────────
# A long-lived SSE stream pushes events; the client posts events to drive the
# loop. Connection opts are the agent + environment ids you provisioned.
claude_result =
Session.run(ClaudeManagedAgents,
client: ReqManagedAgents.new(),
agent_id: System.fetch_env!("AGENT_ID"),
environment_id: System.fetch_env!("ENVIRONMENT_ID"),
prompt: prompt,
handler: Demo.Handler
)
# ── Backend B: AWS Bedrock AgentCore Harness (:request_response) ─────────────
# Each turn is one synchronous SigV4-signed invoke; the resume turn re-sends
# the assistant toolUse + your toolResult. Connection opts are the harness ARN
# (from provisioning) + a fresh session id (33–100 chars, [a-zA-Z0-9-_]).
# The model was fixed at provision time; pass `model:` only to override it.
bedrock_result =
Session.run(BedrockAgentCore,
harness_arn: System.fetch_env!("HARNESS_ARN"),
runtime_session_id:
"example-" <> Base.url_encode64(:crypto.strong_rand_bytes(24), padding: false),
prompt: prompt,
handler: Demo.Handler
)
# ── Same canonical result from both backends ─────────────────────────────────
for {label, result} <- [{"claude ", claude_result}, {"bedrock", bedrock_result}] do
case result do
{:ok, %ReqManagedAgents.SessionResult{} = r} ->
IO.puts(
"#{label}: #{r.terminal} in #{r.turns} turn(s), " <>
"#{r.usage.input_tokens}/#{r.usage.output_tokens} tokens — #{String.slice(r.text, 0, 60)}"
)
{:error, reason} ->
IO.puts("#{label}: error #{inspect(reason)}")
end
end