Jido.AgentServer (Jido v2.0.0-rc.0)
View SourceGenServer runtime for Jido agents.
AgentServer is the "Act" side of the Jido framework: while Agents "think"
(pure decision logic via cmd/2), AgentServer "acts" by executing the
directives they emit. Signal routing happens in AgentServer, keeping
Agents purely action-oriented.
Architecture
- Single GenServer per agent under
Jido.AgentSupervisor - Internal directive queue with drain loop for non-blocking processing
- Registry-based naming via
Jido.Registry - Logical parent-child hierarchy via state tracking + monitors
Public API
start/1- Start under DynamicSupervisorstart_link/1- Start linked to callercall/3- Synchronous signal processingcast/2- Asynchronous signal processingstate/1- Get full State structwhereis/1- Registry lookup by ID (default registry)whereis/2- Registry lookup by ID (specific registry)
Signal Flow
Signal → AgentServer.call/cast
→ route_signal_to_action (via strategy.signal_routes or default)
→ Agent.cmd/2
→ {agent, directives}
→ Directives queued
→ Drain loop executes via DirectiveExec protocolSignal routing is owned by AgentServer, not the Agent. Strategies can define
signal_routes/1 to map signal types to strategy commands. Unmatched signals
fall back to {signal.type, signal.data} as the action.
Options
:agent- Agent module or struct (required):id- Instance ID (auto-generated if not provided):initial_state- Initial state map for agent:registry- Registry module (default:Jido.Registry):default_dispatch- Default dispatch config for Emit directives:error_policy- Error handling policy:max_queue_size- Max directive queue size (default: 10_000):parent- Parent reference for hierarchy:on_parent_death- Behavior when parent dies (:stop,:continue,:emit_orphan):spawn_fun- Custom function for spawning children
Agent Resolution
The :agent option accepts:
- Module name - Must implement
new/0ornew/1new/1receives[id: id, state: initial_state]as keyword optionsnew/0creates agent with defaults;:idand:initial_stateoptions are ignored
- Agent struct - Used directly
- Provide
:agent_moduleoption to specify the module if it differs fromagent.__struct__ - The struct's ID takes precedence over the
:idoption
- Provide
The :agent_module option is only used when :agent is a struct. It tells AgentServer which module implements the agent behavior (for calling cmd/2, lifecycle hooks, etc.).
Examples
# Module with new/1 - receives id and state
{:ok, pid} = AgentServer.start_link(
agent: MyAgent,
id: "my-id",
initial_state: %{counter: 42}
)
# Module with new/0 - id and state ignored
{:ok, pid} = AgentServer.start_link(agent: SimpleAgent)
# Pre-built struct - requires agent_module
agent = MyAgent.new(id: "prebuilt", state: %{value: 99})
{:ok, pid} = AgentServer.start_link(
agent: agent,
agent_module: MyAgent
)Completion Detection
Agents signal completion via state, not process death:
# In your strategy/agent, set terminal status:
agent = put_in(agent.state.status, :completed)
agent = put_in(agent.state.last_answer, answer)
# External code polls for completion:
{:ok, state} = AgentServer.state(server)
case state.agent.state.status do
:completed -> state.agent.state.last_answer
:failed -> {:error, state.agent.state.error}
_ -> :still_running
endThis follows Elm/Redux semantics where completion is a state concern. The process stays alive until explicitly stopped or supervised.
Do NOT use {:stop, ...} from DirectiveExec for normal completion—this
causes race conditions with async work and skips lifecycle hooks.
See Jido.AgentServer.DirectiveExec for details.
Summary
Functions
Check if the agent server process is alive.
Attaches a process to this agent, tracking it as an active consumer.
Wait for an agent to reach a terminal status (:completed or :failed).
Synchronously sends a signal and waits for processing.
Asynchronously sends a signal for processing.
Returns a child_spec for supervision.
Detaches a process from this agent.
Starts an AgentServer under Jido.AgentSupervisor.
Starts an AgentServer linked to the calling process.
Gets the full State struct for an agent.
Gets runtime status for an agent process.
Streams status updates by polling at regular intervals.
Touches the agent to reset the idle timer.
Returns a via tuple for Registry-based naming.
Looks up an agent by ID in a specific registry.
Types
Functions
Check if the agent server process is alive.
Attaches a process to this agent, tracking it as an active consumer.
When attached, the agent will not idle-timeout. The agent monitors the attached process and automatically detaches it on exit.
Used by Jido.Agent.InstanceManager to track LiveView sockets, WebSocket handlers,
or any process that needs the agent to stay alive.
Examples
{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, key)
:ok = Jido.AgentServer.attach(pid)
# With explicit owner
:ok = Jido.AgentServer.attach(pid, socket_pid)
Wait for an agent to reach a terminal status (:completed or :failed).
This is an event-driven wait - the caller blocks until the agent's state transitions to a terminal status, then receives the result immediately. No polling is involved.
Options
:status_path- Path to status field in agent.state (default:[:status]):result_path- Path to result field (default:[:last_answer]):error_path- Path to error field (default:[:error])
Returns
{:ok, %{status: :completed | :failed, result: any()}}- Agent reached terminal status{:error, :not_found}- Server not found- Exits with
{:timeout, ...}if GenServer.call times out
Examples
{:ok, result} = AgentServer.await_completion(pid, timeout: 10_000)
@spec call(server(), Jido.Signal.t(), timeout()) :: {:ok, struct()} | {:error, term()}
Synchronously sends a signal and waits for processing.
Returns the updated agent struct after signal processing. Directives are still executed asynchronously via the drain loop.
Returns
{:ok, agent}- Signal processed successfully{:error, :not_found}- Server not found via registry{:error, :invalid_server}- Unsupported server reference- Exits with
{:noproc, ...}if process dies during call
Examples
{:ok, agent} = Jido.AgentServer.call(pid, signal)
{:ok, agent} = Jido.AgentServer.call("agent-id", signal, 10_000)
@spec cast(server(), Jido.Signal.t()) :: :ok | {:error, term()}
Asynchronously sends a signal for processing.
Returns immediately. The signal is processed in the background.
Returns
:ok- Signal queued successfully{:error, :not_found}- Server not found via registry{:error, :invalid_server}- Unsupported server reference
Examples
:ok = Jido.AgentServer.cast(pid, signal)
:ok = Jido.AgentServer.cast("agent-id", signal)
@spec child_spec(keyword() | map()) :: Supervisor.child_spec()
Returns a child_spec for supervision.
Detaches a process from this agent.
If this was the last attachment and idle_timeout is configured,
the idle timer starts.
Note: You don't need to call this explicitly if the attached process exits normally — the monitor will handle cleanup automatically.
Examples
:ok = Jido.AgentServer.detach(pid)
@spec start(keyword() | map()) :: DynamicSupervisor.on_start_child()
Starts an AgentServer under Jido.AgentSupervisor.
Examples
{:ok, pid} = Jido.AgentServer.start(agent: MyAgent)
{:ok, pid} = Jido.AgentServer.start(agent: MyAgent, id: "my-agent")
@spec start_link(keyword() | map()) :: GenServer.on_start()
Starts an AgentServer linked to the calling process.
Options
See module documentation for full list of options.
Examples
{:ok, pid} = Jido.AgentServer.start_link(agent: MyAgent)
{:ok, pid} = Jido.AgentServer.start_link(agent: MyAgent, id: "custom-123")
@spec state(server()) :: {:ok, Jido.AgentServer.State.t()} | {:error, term()}
Gets the full State struct for an agent.
Returns
{:ok, state}- Full State struct retrieved{:error, :not_found}- Server not found via registry{:error, :invalid_server}- Unsupported server reference
Examples
{:ok, state} = Jido.AgentServer.state(pid)
{:ok, state} = Jido.AgentServer.state("agent-id")
@spec status(server()) :: {:ok, Jido.AgentServer.Status.t()} | {:error, term()}
Gets runtime status for an agent process.
Returns a Status struct combining the strategy snapshot with process metadata.
This provides a stable API for querying agent status without depending on internal
__strategy__ state structure.
Returns
{:ok, status}- Status struct with snapshot and metadata{:error, :not_found}- Server not found via registry{:error, :invalid_server}- Unsupported server reference
Examples
{:ok, agent_status} = Jido.AgentServer.status(pid)
# Check completion
if agent_status.snapshot.done? do
IO.puts("Result: " <> inspect(agent_status.snapshot.result))
end
# Use delegate helpers
case Status.status(agent_status) do
:success -> {:done, Status.result(agent_status)}
:failure -> {:error, Status.details(agent_status)}
_ -> :continue
end
@spec stream_status( server(), keyword() ) :: Enumerable.t()
Streams status updates by polling at regular intervals.
Returns a Stream that yields status snapshots. Useful for monitoring agent execution without manual polling loops.
Options
:interval_ms- Polling interval in milliseconds (default: 100)
Examples
# Poll until completion
AgentServer.stream_status(pid, interval_ms: 50)
|> Enum.reduce_while(nil, fn status, _acc ->
case Status.status(status) do
:success -> {:halt, {:ok, Status.result(status)}}
:failure -> {:halt, {:error, Status.details(status)}}
_ -> {:cont, nil}
end
end)
# Take first 10 snapshots
AgentServer.stream_status(pid)
|> Enum.take(10)
Touches the agent to reset the idle timer.
Use this for request-based activity tracking (e.g., HTTP requests) where you don't want to maintain a persistent attachment.
Examples
# In a controller
{:ok, pid} = Jido.Agent.InstanceManager.get(:sessions, key)
:ok = Jido.AgentServer.touch(pid)
Returns a via tuple for Registry-based naming.
Examples
name = Jido.AgentServer.via_tuple("agent-id", MyApp.Jido.Registry)
GenServer.call(name, :get_state)
Looks up an agent by ID in a specific registry.
Returns the pid if found, nil otherwise.
Examples
pid = Jido.AgentServer.whereis(MyApp.Jido.Registry, "agent-123")
# => #PID<0.123.0>