A behaviour and supervision framework for long-running LLM agent processes, modeled as OTP state machines.
Each agent is a :gen_statem process wrapping a persistent LLM session.
Every interaction is a prompt-response turn, and the implementation decides
what happens between turns.
It is a GenServer but every call is a prompt.
GenAgent handles the mechanics of turns. Implementations handle the semantics of turns.
Public client functions address agents by their registered :name. The pid
returned by start_agent/2 is for monitoring and supervision, not a client
address. Synchronous calls return {:error, :not_found} when that name is
absent at lookup. A concurrent stop or agent death after lookup can still
exit the call, and a caller-supplied timeout exits on expiry. Casts are best
effort and return :ok even when no agent is registered.
Installation
def deps do
[
{:gen_agent, "~> 0.7.0"}, # x-release-please-version
# Plus at least one backend:
{:gen_agent_claude, "~> 0.2.0"},
{:gen_agent_codex, "~> 0.4.0"},
{:gen_agent_anthropic, "~> 0.3.0"},
{:gen_agent_openai, "~> 0.3.0"}
]
endQuick start
defmodule MyApp.Coder do
use GenAgent
defmodule State do
defstruct [:path, responses: []]
end
@impl true
def init_agent(opts) do
path = Keyword.fetch!(opts, :cwd)
backend_opts = [
cwd: path,
system_prompt: "You are a coding assistant."
]
{:ok, backend_opts, %State{path: path}}
end
@impl true
def handle_response(_ref, response, state) do
{:noreply, %{state | responses: state.responses ++ [response.text]}}
end
end
# Start the agent under the GenAgent supervision tree.
{:ok, _pid} = GenAgent.start_agent(MyApp.Coder,
name: "my-coder",
backend: GenAgent.Backends.Claude,
cwd: "/path/to/project"
)
# Synchronous prompt.
{:ok, response} = GenAgent.ask("my-coder", "What does lib/foo.ex do?")
IO.puts(response.text)
# Async prompt.
{:ok, ref} = GenAgent.tell("my-coder", "Add tests for lib/foo.ex")
GenAgent.poll("my-coder", ref)
#=> {:ok, :pending} while the turn runs, then {:ok, :completed, response}
# Or have the outcome sent to you as a message.
{:ok, ref} = GenAgent.tell_with_completion("my-coder", "Run the tests")
receive do
{:gen_agent, :completion, "my-coder", ^ref, {:ok, response}} -> IO.puts(response.text)
{:gen_agent, :completion, "my-coder", ^ref, {:error, reason}} -> IO.inspect(reason)
end
# External event.
GenAgent.notify("my-coder", {:ci_failed, "test_auth"})
GenAgent.stop("my-coder")State model
An agent is a state machine with two states:
idle --- ask/tell/notify ---> processing
|
v
idle <--- handle_response --- processing (turn done)Self-chaining: handle_response/3 can return {:prompt, text, state}
to immediately dispatch another turn without a caller, useful for
multi-step work that the agent drives itself.
Halting: handle_response/3, handle_error/3, handle_event/2,
or pre_turn/2 can return {:halt, state} to go idle but freeze the
mailbox. A halted agent ignores queued prompts until resume/1 is called.
Backends
Backends implement GenAgent.Backend and translate the LLM-specific wire
protocol into the normalized GenAgent.Event stream the state machine
consumes. Available backends (in sibling packages):
GenAgent.Backends.Claude(package:gen_agent_claude) -- wraps the AnthropicclaudeCLI viaClaudeWrapper.GenAgent.Backends.Codex(package:gen_agent_codex) -- wraps the OpenAIcodexCLI viaCodexWrapper.GenAgent.Backends.Anthropic(package:gen_agent_anthropic) -- calls the Anthropic HTTP API viaReq.GenAgent.Backends.OpenAI(package:gen_agent_openai) -- calls the OpenAI Responses API viaReq.
A backend owns its session lifecycle, translates events, and carries any state it needs (session id, message history) in an opaque session term.
Callbacks
init_agent/1-- set up backend options and initial agent state.handle_response/3-- a turn completed, decide what to do next.handle_error/3(optional) -- a turn failed, decide what to do next.handle_event/2(optional) -- an external event arrived vianotify/2.handle_stream_event/2(optional) -- a backend event arrived mid-turn. Runs inside the prompt task, not the agent process.terminate_agent/2(optional) -- the agent is shutting down.
Lifecycle hooks (all optional):
pre_run/1-- one-time setup afterinit_agent, before the first turn.pre_turn/2-- before each dispatch. Can rewrite the prompt, skip, or halt.post_turn/3-- after each turn, post-decision. For state-mutating side effects.post_run/1-- on clean{:halt, state}from a decision callback orpre_turn/2. For completion side effects.
The use GenAgent macro provides default implementations of the optional
callbacks and lifecycle hooks.
Where callbacks run
| Callback or step | Runs in |
|---|---|
init_agent/1, pre_run/1, pre_turn/2 | agent process |
handle_response/3, handle_error/3, handle_event/2 | agent process |
post_turn/3, post_run/1, terminate_agent/2 | agent process |
handle_stream_event/2 | prompt task |
Backend prompt/2,3, its event stream, update_session/2 | prompt task |
Backend start_session/1, terminate_session/1, checkpoint_session/2 | agent process |
Anything that runs in the agent process blocks the agent from handling
other messages while it runs. Synchronous calls such as status/2,
poll/3, tell/3, runtime_snapshot/2 and notify_ack/3 are handled
by that process, so they wait until the callback returns. Their default
timeout is :infinity, so a caller waits as long as the callback takes
unless it passes a shorter timeout. Asynchronous casts such as
notify/2 return immediately (the event is handled after the callback),
and whereis/1 reads the registry without messaging the agent. Work in
the prompt task does not block the agent process, with one exception: a
backend's prompt/3 can request a session checkpoint through the
:checkpoint function in its options. That call is synchronous, so the
prompt task waits while the agent process runs checkpoint_session/2,
and synchronous agent calls wait for it too. The agent also runs
checkpoint_session/2 when restoring a stored checkpoint after a prompt
task returns a success or error. If the task crashes, the agent keeps the
earlier checkpoint without invoking the callback again.
Process lifecycle
Agents use restart: :temporary. A crashed or stopped agent must be
started explicitly; GenAgent does not restore its previous state.
Each turn runs in a supervised prompt task. Task failures are delivered
to handle_error/3 without taking down the agent. When the agent exits,
its active prompt task is stopped, including when an abrupt exit bypasses
termination callbacks.
Stopping the BEAM task does not establish that a provider's subprocess or remote request has stopped. The backend and its transport own external cancellation and resource cleanup.
Public API
start_agent/2-- start an agent under the supervision tree.child_spec/2-- build a child spec for caller-owned supervision.ask/3-- synchronous prompt, blocks until the turn finishes.tell/3-- async prompt, returns a ref forpoll/3.tell_with_completion/4-- async prompt with request-scoped completion delivery;tell_with_completion/5addsstream_to:for ref-tagged stream events.poll/3-- check on a previously-issuedtell/3.notify/2-- push an external event intohandle_event/2.notify_ack/3-- acknowledge in-memory notification admission.interrupt/1-- cancel an in-flight turn.interrupt_request/3-- acknowledge cancellation for a matching request ref.cancel_request/3-- remove a queued tell by its request ref.resume/1-- unhalt an agent and drain its mailbox.status/2-- read the agent's current state.runtime_snapshot/2-- read bounded runtime metadata.stop/1-- terminate the agent.stop/2-- terminate an agent under its caller-owned supervisor.whereis/1-- look up an agent's pid.
Data types
GenAgent.Event-- a normalized event emitted by a backend during a turn.GenAgent.Response-- the result of a completed turn delivered tohandle_response/3.
Telemetry
GenAgent emits telemetry events for observability:
[:gen_agent, :turn, :start | :stop | :error | :rejected][:gen_agent, :prompt, :start | :stop | :error][:gen_agent, :event, :received][:gen_agent, :state, :changed][:gen_agent, :mailbox, :queued][:gen_agent, :input, :rejected][:gen_agent, :halted]
Use the content-free turn events for metrics. The older prompt and
event telemetry can include prompts, raw errors, and agent state.
See GenAgent.Telemetry for measurements, units, correlation, and
ordering.
What GenAgent does not do
- It does not prescribe agent behavior (no retry logic, no summary format).
- It does not prescribe inter-agent communication (agents can
notify/2each other but the message format is up to you). - It does not manage persistence across restarts.
- It does not manage cost tracking or budgets.
See GenAgent.Backend for the backend behaviour, GenAgent.Event and
GenAgent.Response for the data types delivered to callbacks.
Summary
Types
Opaque term owned by the implementation module, carried across callbacks.
Return value of callbacks that may request a follow-up action.
Name under which an agent is registered in GenAgent.Registry.
Return value of pre_turn/2. The hook can pass the prompt through
(optionally rewritten), skip the turn, or halt the agent.
Reference returned for tell/2 requests.
Bounded, metadata-only observation of one agent's runtime state.
Callbacks
A prompt->response turn failed. Optional. Decide what to do next.
An external event arrived via notify/2. Optional.
A prompt->response turn completed successfully. Decide what to do next.
A streaming event arrived mid-turn. Optional.
Initialize the agent. Return backend options and the initial agent state.
Clean-completion hook. Optional.
Per-turn post-dispatch hook. Optional.
One-time setup hook, fires after init_agent/1 and before the
first turn. Optional.
Per-turn pre-dispatch hook. Optional.
The agent is shutting down. Optional. Clean up resources.
Functions
Send a synchronous prompt to an agent.
Cancel a queued tell/3 or tell_with_completion/4 request by exact ref.
Build a temporary agent child spec for a caller-owned supervisor.
Interrupt an in-flight turn.
Interrupt the active turn only if its request reference matches ref.
Return an unordered list of registered agent names.
Push an external event into the agent.
Send an event and wait for the agent to acknowledge admission.
Check the status of a previously-issued tell/2 request.
Resume a halted agent.
Read a bounded, metadata-only runtime snapshot of an agent.
Start an agent under the GenAgent supervision tree.
Read an agent's current status.
Stop an agent.
Send an asynchronous prompt to an agent.
Submit an asynchronous prompt and opt into one completion message.
Look up the pid of a registered agent, or nil if not found. Pass the name,
not this pid, to public client functions.
Types
@type agent_state() :: term()
Opaque term owned by the implementation module, carried across callbacks.
@type callback_return() :: {:noreply, agent_state()} | {:prompt, String.t(), agent_state()} | {:halt, agent_state()}
Return value of callbacks that may request a follow-up action.
@type name() :: term()
Name under which an agent is registered in GenAgent.Registry.
@type pre_turn_return() :: {:ok, prompt :: String.t(), agent_state()} | {:skip, agent_state()} | {:halt, agent_state()}
Return value of pre_turn/2. The hook can pass the prompt through
(optionally rewritten), skip the turn, or halt the agent.
@type request_ref() :: reference()
Reference returned for tell/2 requests.
@type runtime_snapshot() :: %{ phase: :idle | :processing, halted: boolean(), pending_prompts: non_neg_integer(), pending_notifications: non_neg_integer(), self_chain_pending: boolean(), current_request: nil | %{ ref: request_ref(), origin: :ask | :tell | :event | :self_chain, elapsed_ms: non_neg_integer(), watchdog_ms: non_neg_integer() | :infinity } }
Bounded, metadata-only observation of one agent's runtime state.
Callbacks
@callback handle_error(request_ref :: reference(), reason :: term(), agent_state()) :: callback_return()
A prompt->response turn failed. Optional. Decide what to do next.
Called when the turn could not complete successfully. Covers:
- The backend returned a synchronous
{:error, reason}fromGenAgent.Backend.prompt/2. - The event stream ended without a terminal
:resultor:errorevent. - The backend's event stream emitted a terminal
:errorevent. - The prompt task crashed (delivered as
{:task_crashed, reason}). - The configured task supervisor was unavailable before the turn started
(
:task_supervisor_unavailable). An immediate retry returned here is discarded to avoid a loop; a later request may retry after recovery. - The watchdog fired (
:timeout). - The in-flight request was interrupted by
interrupt/1(:interrupted).
Returns the same value shape as handle_response/3, so the callback
can go idle, self-chain a follow-up prompt (useful for retry), or halt
the agent. The default implementation provided by use GenAgent is
{:noreply, state}. Exceptions and malformed returns are logged and
treated as {:noreply, previous_state} so the original turn error still
reaches its caller.
@callback handle_event(event :: term(), agent_state()) :: callback_return()
An external event arrived via notify/2. Optional.
Exceptions and malformed returns are logged and treated as
{:noreply, previous_state}, including when the notification was buffered
during a turn.
@callback handle_response( request_ref :: reference(), response :: GenAgent.Response.t(), agent_state() ) :: callback_return()
A prompt->response turn completed successfully. Decide what to do next.
An exception or malformed return stops the agent. Use one of the three
callback_return/0 shapes; in particular, a follow-up prompt must be a
binary.
@callback handle_stream_event(GenAgent.Event.t(), agent_state()) :: agent_state()
A streaming event arrived mid-turn. Optional.
Runs inside the task that is driving the prompt, not the agent process.
Returns the updated agent state, which is threaded through subsequent
stream events. On normal stream completion, including a terminal
:error event or EOF without a terminal event, the final state is
passed to handle_response/3 or handle_error/3 and then
post_turn/3. A task crash, interruption, or watchdog kill cannot
recover task-local callback state; those paths use the agent state
from before stream consumption. Callback state is volatile and is not
a durable record of provider activity.
@callback init_agent(opts :: keyword()) :: {:ok, backend_opts :: keyword(), agent_state()} | {:error, reason :: term()}
Initialize the agent. Return backend options and the initial agent state.
opts is the keyword list passed to start_agent/2 minus the reserved
keys consumed by GenAgent itself (:name, :backend, etc.).
@callback post_run(agent_state()) :: :ok
Clean-completion hook. Optional.
Fires when handle_response/3, handle_error/3,
handle_event/2, or pre_turn/2 returns
{:halt, state}. Runs before the [:gen_agent, :halted] telemetry
event is emitted.
On turn completion, post_turn/3 runs first, then notifications
buffered during the turn are applied in order, then this hook and
halted telemetry receive the resulting state. A buffered notification
that halts does not skip later buffered notifications or finalize
completion partway through the batch. No further prompt is dispatched
during this drain.
Notifications arriving after completion may still update a halted agent's state. They do not retroactively change this completion snapshot or rerun the hook.
Runs once per transition to halted. Further halt decisions while the
agent is already halted do not rerun the hook. After resume/1, a new
halt transition runs it again.
Does NOT fire on crashes, stop/1, supervisor shutdown, or any
abnormal exit -- terminate_agent/2 covers those paths.
Use cases: create a PR, post a completion summary, mark a task done
in an external tracker. The semantic distinction from
terminate_agent/2 is "completion" vs "termination."
Crashes are caught: a warning is logged and the halt transition still completes normally. A failing last-chance hook does not keep a dead agent alive.
Default implementation: :ok.
@callback post_turn( outcome :: {:ok, GenAgent.Response.t()} | {:error, reason :: term()}, request_ref :: reference(), agent_state() ) :: {:ok, agent_state()}
Per-turn post-dispatch hook. Optional.
Fires after each turn, AFTER handle_response/3 or
handle_error/3 has returned its decision. The hook sees the
post-decision state. Runs regardless of which decision callback ran
or what it returned.
The outcome is {:ok, response} for a successful turn or
{:error, reason} for a failed one -- the same data delivered to
the decision callbacks. The hook cannot override the decision
callback's transition ({:noreply, ...}, {:prompt, ...},
{:halt, ...}); it only updates state.
Use cases: commit-per-turn (stateful side effect), persist a turn
record, update a per-turn metric that needs to live on agent state.
For pure observation, prefer telemetry handlers on
[:gen_agent, :prompt, :stop].
Crashes or malformed returns are logged and the server continues with the transition the decision callback chose. The turn is not unwound.
Default implementation: {:ok, state}.
@callback pre_run(agent_state()) :: {:ok, agent_state()} | {:error, reason :: term()}
One-time setup hook, fires after init_agent/1 and before the
first turn. Optional.
Runs in the agent process, so it blocks the first turn until it
returns -- but does NOT block start_agent/2 from returning to the
caller. Every synchronous call to the agent (status/2, poll/3,
tell/3, runtime_snapshot/2, and so on) waits until the hook
returns, and their default timeout is :infinity. notify/2 and
whereis/1 do not wait. This is the right home for slow async setup that would
otherwise freeze the starter: cloning a repo, creating a worktree,
spinning up a sandbox, fetching secrets.
Return {:ok, state} to continue, or {:error, reason} to halt the
agent before any turn runs. On error, terminate_agent/2 is called
with {:pre_run_failed, reason}.
Crashes are wrapped: the agent halts with
{:pre_run_crashed, exception} and terminate_agent/2 is called
with that reason. A malformed return stops the agent with
:pre_run_invalid.
Default implementation: {:ok, state}.
@callback pre_turn(prompt :: String.t(), agent_state()) :: pre_turn_return()
Per-turn pre-dispatch hook. Optional.
Fires before each prompt is dispatched to the backend, inside the
agent process. Can observe, mutate state, rewrite the prompt (for
augmentation or templating), skip the turn with :skip, or halt the
agent entirely with :halt.
Use cases: prompt templating (inject context) and gating (halt if an
external signal says stop). For rate limiting, use a timer and a
handle_event/2 callback to start work later, as in the Retry guide.
The hook runs synchronously in the agent process before the prompt task
is started. While it runs, synchronous calls to the agent (status/2,
poll/3, tell/3, runtime_snapshot/2, and so on) wait, with a
default timeout of :infinity. Sleeping here therefore delays those
calls on every dispatch and may prevent orderly shutdown before the
child spec's :shutdown timeout. notify/2 and whereis/1 do not wait.
When the prompt is rewritten, [:gen_agent, :prompt, :start]
telemetry carries both the original and rewritten prompt plus a
rewritten: true flag so the transformation is traceable.
Crashes are caught: an external ask or tell is skipped with
:pre_turn_skipped and the agent returns to :idle. For prompts generated
by handle_response/3 or handle_event/2, a skip, crash, or malformed
return emits prompt-error telemetry and calls handle_error/3 so the
agent can retry or halt. Crashes use {:pre_turn_crashed, exception_kind}
on that generated-prompt path. Immediate generated-prompt retries are
paced so agent calls remain responsive even if the hook keeps rejecting.
Default implementation: {:ok, prompt, state}.
@callback terminate_agent(reason :: term(), agent_state()) :: term()
The agent is shutting down. Optional. Clean up resources.
Functions
@spec ask(name(), String.t(), timeout()) :: {:ok, GenAgent.Response.t()} | {:error, term()}
Send a synchronous prompt to an agent.
Blocks until the turn completes and returns {:ok, response} or
{:error, reason}. If the agent is currently processing another
prompt, the caller is queued transparently and unblocks when its
queued turn finishes.
If admission to the pending prompt queue fails, returns
{:error, {:overloaded, info}} immediately; no turn is accepted.
info includes the queue, count or byte limit reached, current count
and bytes, incoming bytes, and configured maxima.
An orderly agent stop replies to an active or queued ask with
{:error, {:agent_terminated, reason}}. A call the agent has not yet
handled, an abrupt agent death, or a forced kill can still exit the
caller; catch :exit if the caller must survive those cases.
The default timeout is :infinity. The agent's own watchdog is the
primary timeout mechanism -- callers generally should not need to set
their own. Supplying a shorter timeout here will exit on expiry
without affecting the agent. A queued ask is dropped if its calling
process dies. If that ask is already active, it keeps running. A live
caller's timeout leaves its ask queued or running. For cancellable queued
work, use tell/3 or tell_with_completion/4 and cancel_request/3.
Returns {:error, :not_found} if the agent name is not registered.
@spec cancel_request(name(), request_ref(), timeout()) :: {:ok, :cancelled} | {:error, :current | :already_finished | :not_found}
Cancel a queued tell/3 or tell_with_completion/4 request by exact ref.
Returns {:ok, :cancelled} when the queued request is removed, or when
its cancellation result is still cached. The cancellation releases its
queue count and bytes immediately. poll/3 then returns
{:error, :cancelled}. A completion recipient receives exactly one
{:gen_agent, :completion, name, ref, {:error, :cancelled}} message.
Returns {:error, :current} for an active tell turn; use
interrupt_request/3 to interrupt it. Returns
{:error, :already_finished} for a cached terminal result other than
cancellation. Returns {:error, :not_found} for an unknown ref, a
pruned result, or an internal ask or event ref. Cancellation results
count toward the bounded max_tell_results cache. Once pruned, a
cancelled ref also returns {:error, :not_found}.
A queued turn has not started, so cancellation does not call
handle_response/3, handle_error/3, or post_turn/3. The agent
processes cancellation and dispatch in order: if dispatch happened
first, this returns {:error, :current} and leaves the turn alone.
The default call timeout is :infinity.
Returns {:error, :not_found} if the agent name is not registered.
@spec child_spec(module(), keyword()) :: Supervisor.child_spec()
Build a temporary agent child spec for a caller-owned supervisor.
Requires :name, :backend, and :task_supervisor. The selected
Task.Supervisor must already be running; prompt tasks never fall back
to GenAgent.TaskSupervisor. Other options have the same meaning as in
start_agent/2.
Start the spec with DynamicSupervisor.start_child/2. The agent is
registered in GenAgent.Registry, so the regular name-based APIs work.
Names must be unique across both caller-owned and global agents. Use
stop/2 with the owning supervisor to stop an individual agent.
Put the task supervisor before the agent supervisor in the caller's
supervision tree so agents shut down before their prompt-task supervisor.
Use :rest_for_one if loss of the task supervisor should also stop the
agents. See the README for an example.
@spec interrupt(name()) :: :ok
Interrupt an in-flight turn.
Kills the prompt task and delivers {:error, :interrupted} to the
waiting caller (if any). No-op if the agent is idle.
Asynchronous. Returns :ok immediately.
@spec interrupt_request(name(), request_ref(), timeout()) :: {:ok, :accepted} | {:error, :not_current | :idle | :not_found}
Interrupt the active turn only if its request reference matches ref.
Returns {:ok, :accepted} when the agent has cancelled that turn,
{:error, :not_current} when another turn is active, or
{:error, :idle} when no turn is active. A queued request is not
interruptible through this API; use cancel_request/3 for a queued tell.
Unlike interrupt/1, this operation
is acknowledged by the agent and cannot cancel a successor turn after
the observed request finishes. It is also safe against a replacement
agent registered under the same name, because request references are
unique.
The acknowledgement describes the agent's decision and BEAM task
cancellation. It does not establish provider or OS process settlement.
The default call timeout is :infinity.
Returns {:error, :not_found} if the agent name is not registered.
@spec list() :: [name()]
Return an unordered list of registered agent names.
This is a point-in-time view of GenAgent.Registry; agents running without
registration are excluded. Returned agents are not guaranteed to remain
alive, and registry cleanup may briefly lag agent termination.
Push an external event into the agent.
The event is delivered to handle_event/2. If the callback
returns {:prompt, text, state} the prompt is dispatched (or
queued, if the agent is busy).
Asynchronous. Returns :ok immediately, including when the agent later
rejects the event because its pending queue is full. Rejections emit
[:gen_agent, :input, :rejected] telemetry. Use notify_ack/3 when the
sender needs an in-memory admission result.
Send an event and wait for the agent to acknowledge admission.
Returns :ok when the event was handled immediately or retained for
delivery after the current turn. Returns {:error, {:overloaded, info}}
when the pending notification queue cannot hold it, or an event
handled while halted generates a prompt that cannot be queued. This
acknowledges in-memory handling or retention, not durable delivery or
a guarantee that a callback-generated prompt will run. A deferred
callback-generated prompt can be rejected later if the prompt queue is
full; the agent's handle_error/3 receives that overload. The legacy
notify/2 remains a best-effort asynchronous cast and always returns
:ok.
Returns {:error, :not_found} if the agent name is not registered.
@spec poll(name(), request_ref(), timeout()) :: {:ok, :pending} | {:ok, :completed, GenAgent.Response.t()} | {:error, term()}
Check the status of a previously-issued tell/2 request.
Returns:
{:ok, :pending}if the request is queued or in-flight.{:ok, :completed, response}if the turn finished successfully.{:error, reason}if the turn failed, including{:error, :cancelled}for a cancelled queued request.{:error, :not_found}if the ref is unknown (never issued, or pruned from the bounded result cache).
Only refs returned from tell/2 are pollable. Refs from ask/2 are
internal and reply directly to the caller.
Returns {:error, :not_found} if the agent name is not registered.
@spec resume(name()) :: :ok
Resume a halted agent.
Clears the halted flag and re-drains the mailbox. No-op if the
agent is not halted.
Asynchronous. Returns :ok immediately.
@spec runtime_snapshot(name(), timeout()) :: runtime_snapshot() | {:error, :not_found}
Read a bounded, metadata-only runtime snapshot of an agent.
pending_prompts counts the prompt mailbox; pending_notifications
counts notifications buffered during a turn; self_chain_pending
reports a separately held callback-generated follow-up prompt.
current_request is nil when idle and otherwise contains the
volatile request ref, its origin (:event and :self_chain are
callback-origin turns), elapsed monotonic milliseconds since dispatch,
and the configured watchdog duration. It excludes prompts, caller
identities, callback state, backend sessions, events, and queued
payloads. Elapsed time is an observation, not an exact countdown to
the watchdog firing.
The snapshot is a point-in-time view of this BEAM coordinator, not
durable application state, admission authority, or proof that external
provider work has settled. The default call timeout is :infinity.
Returns {:error, :not_found} if the agent name is not registered.
@spec start_agent(module(), keyword()) :: DynamicSupervisor.on_start_child()
Start an agent under the GenAgent supervision tree.
module is the implementation module (the one that use GenAgent).
opts must include:
:name-- the name the agent will register under inGenAgent.Registry.:backend-- the backend module implementingGenAgent.Backend.
Event evidence is bounded by :max_events_per_turn (default 1_000)
and :max_event_bytes_per_turn (default 1_048_576), measured as the
sum of :erlang.external_size/1 for retained GenAgent.Event values.
Both limits must be positive integers. With the default
event_retention: :compact, an oversized turn succeeds with a bounded
prefix in Response.events and Response.event_coverage reports omissions.
All normalized events still reach handle_stream_event/2, and the
terminal result, full response text, and usage remain available separately.
Use event_retention: :lossless to require a complete event list; an event
that exceeds either limit then fails the turn with
{:event_capture_overflow, diagnostics} before entering the callback.
Pending prompt and deferred notification queues have independent count
and payload-byte limits: :max_pending_prompts and
:max_pending_notifications (both default 1_000), and
:max_pending_prompt_bytes and :max_pending_notification_bytes
(both default 1_048_576). Limits are non-negative integers; zero
disables that pending queue. Bytes are the sum of
:erlang.external_size/1 of each queued prompt or notification payload,
not the entire process memory. The active prompt and notifications
handled immediately while idle are not pending. Self-chaining has one
reserved slot outside the prompt count limit, but its payload must fit
:max_pending_prompt_bytes.
:watchdog_ms is the per-turn deadline in milliseconds (default
600_000). It must be a positive integer or :infinity, which disables
the watchdog. :max_tell_results is the number of completed tell/2
results kept for poll/2 (default 100). It must be a non-negative
integer; the oldest results are evicted first and zero retains none.
:shutdown is the supervisor's graceful shutdown timeout in milliseconds
(default 5_000), or :infinity. A callback still running when this
timeout expires is killed and termination callbacks cannot run. Choose a
value longer than any bounded callback or cleanup operation; :infinity
can block the owning supervisor indefinitely. An explicit nil uses the
default.
Invalid values for the watchdog and the capture and pending limits make
start_agent/2 return {:error, {:init_failed, :error, ArgumentError}}.
An invalid :shutdown raises ArgumentError while building the child spec.
A missing :name or :backend raises KeyError in the caller.
An explicit nil for a limit is treated as unset and uses the default.
The reserved keys :name, :backend, :watchdog_ms, :shutdown,
:max_tell_results, :max_events_per_turn,
:max_event_bytes_per_turn, :event_retention, :max_pending_prompts,
:max_pending_prompt_bytes, :max_pending_notifications, and
:max_pending_notification_bytes are consumed and not forwarded. Any
other option, including :task_supervisor, is forwarded to
init_agent/1. start_agent/2 always uses GenAgent.TaskSupervisor;
use child_spec/2 to select a different task supervisor.
The child uses restart: :temporary. If it exits, call start_agent/2
explicitly to create another agent; its previous state is not restored.
An agent also exits if its Registry partition is lost, so it cannot remain
alive but unreachable by name after a Registry restart.
The returned pid is for monitoring or supervision. Pass the registered
:name to the public client functions, including stop/1.
@spec status(name(), timeout()) :: %{ state: :idle | :processing, name: term(), queued: non_neg_integer(), current_request: request_ref() | nil, halted: boolean(), agent_state: term() } | {:error, :not_found}
Read an agent's current status.
This compatibility API includes the full callback-maintained
agent_state. While a turn is processing, that value is the server's
latest retained state, not a live read of state inside the prompt task.
Use runtime_snapshot/2 for a bounded metadata-only view.
Returns {:error, :not_found} if the agent name is not registered.
@spec stop(name(), GenServer.server()) :: :ok | {:error, :not_found}
Stop an agent.
Terminates the agent process via its owning DynamicSupervisor.
Pass the supervisor as the second argument for an agent started with
child_spec/2; the default is GenAgent.AgentSupervisor.
Active and queued ask/3 callers receive an :agent_terminated error
when the agent processes the orderly shutdown. Accepted
tell_with_completion/4 requests do not receive a synthetic completion;
recipients should monitor the agent to detect that uncertainty.
A callback runs inside the agent process and can delay shutdown. If it
exceeds the child spec's :shutdown timeout (default 5_000 ms), the
supervisor kills the agent and terminate_agent/2 and backend
terminate_session/1 cannot run. Configure :shutdown when starting an
agent whose callbacks or cleanup can take longer. A blocked callback can
also delay unrelated operations on the same DynamicSupervisor during
stop/2.
Returns :ok or {:error, :not_found}.
@spec tell(name(), String.t(), timeout()) :: {:ok, request_ref()} | {:error, term()}
Send an asynchronous prompt to an agent.
Returns {:ok, ref} when the agent accepts the prompt. A pre_turn/2
callback runs before acceptance for an idle agent, so this call waits for
that callback even though it does not wait for the backend result. Use
poll/2 to check on the result. The same queueing semantics as ask/2
apply. When a pending queue limit is reached, returns
{:error, {:overloaded, info}} without
an accepted ref.
Returns {:error, :not_found} if the agent name is not registered.
@spec tell_with_completion(name(), String.t(), pid(), timeout()) :: {:ok, request_ref()} | {:error, term()}
Submit an asynchronous prompt and opt into one completion message.
recipient is a pid (defaults to the calling process). On an accepted
request this returns {:ok, ref} and sends the recipient
{:gen_agent, :completion, name, ref, {:ok, response}} or
{:gen_agent, :completion, name, ref, {:error, reason}} when the logical
request finishes. The recipient is registered within the same agent call
that accepts the request, so even an immediate pre_turn/2 skip or fast
backend response can be delivered before this function returns. Match on
the returned ref to correlate the message.
Admission failure returns {:error, {:overloaded, info}} with no ref or
completion message. Accepted queued requests deliver after their turn
completes, unless cancelled with cancel_request/3. A cancelled queued
request delivers {:gen_agent, :completion, name, ref, {:error, :cancelled}}
without running turn lifecycle callbacks. pre_turn/2 skip, halt and
invalid results deliver their corresponding error without starting a
turn. Successful and failed turns deliver after their decision and
post_turn/3 callbacks. An interrupt, watchdog timeout or backend
failure delivers an error. A crashing handle_response/3 callback stops
the agent before completion; monitor the agent when its death matters.
Delivery is a single BEAM message sent at most once per accepted request.
It is independent of the bounded poll/3 result cache. A dead recipient
does not receive the message, and any agent exit, including an orderly
stop, can leave accepted requests without a completion message; a monitor
reports that uncertainty.
This does not prove that an external provider process has settled.
The request ref remains suitable for interrupt_request/3 while active
or cancel_request/3 while queued. A new agent under the same name never
reuses it.
The five-argument form accepts on_halt: :fail. It rejects a new request
with {:error, :halted} when the agent is already halted, and completes
an opted-in queued request with {:error, :halted} if the agent halts
before dispatch. The default preserves the ordinary queue-until-resume
behavior. This is useful for callers, such as ensembles, that cannot
resume a halted agent themselves.
The five-argument form also accepts stream_to: pid. The process
receives {:gen_agent, :event, name, ref, %GenAgent.Event{}} for each
event the backend streams during that request's turn, in stream order.
Events are relayed through the agent after handle_stream_event/2
runs, so for one ref they arrive before the completion message when
stream_to and recipient are the same process. Separate processes
have no shared arrival order. Streaming is off by default and
independent of event_retention: compact mode relays events that the
retained history omits, and lossless mode does not relay the event it
rejects for exceeding the capture limits.
Delivery has no backpressure and is best effort. A dead stream_to
process is ignored. An interrupt or watchdog timeout can drop events
that were generated but not yet relayed; nothing is relayed for a ref
after its request has finished. A request that never starts a turn
(cancelled, rejected by pre_turn/2, halted or failed dispatch)
produces no events.
Returns {:error, :not_found} if the agent name is not registered.
Look up the pid of a registered agent, or nil if not found. Pass the name,
not this pid, to public client functions.