Alloy.Agent.Server (alloy v0.10.1)

Copy Markdown View Source

OTP-backed persistent agent process.

Wraps the stateless Turn.run_loop/1 in a GenServer so the agent can hold conversation history across multiple calls, be supervised, and run concurrently with other agents.

Usage

{:ok, pid} = Alloy.Agent.Server.start_link(
  provider: {Alloy.Provider.Anthropic, api_key: "sk-ant-...", model: "claude-opus-4-6"},
  tools: [Alloy.Tool.Core.Read, Alloy.Tool.Core.Bash],
  system_prompt: "You are a helpful assistant."
)

{:ok, r1} = Alloy.Agent.Server.chat(pid, "List the files in this project")
{:ok, r2} = Alloy.Agent.Server.chat(pid, "Now read mix.exs")
IO.puts(r2.text)

Alloy.Agent.Server.stop(pid)

Options

All options from Alloy.run/2 are accepted at start time, plus:

  • :name - Register the process under a name (optional)

Supervision

children = [
  {Alloy.Agent.Server, [
    name: :my_agent,
    provider: {Alloy.Provider.Anthropic, api_key: System.get_env("ANTHROPIC_API_KEY"), model: "claude-opus-4-6"}
  ]}
]
Supervisor.start_link(children, strategy: :one_for_one)

Summary

Functions

Cancel an async request by request_id.

Send a message and wait for the agent to finish its full loop.

Returns a specification to start this module under a supervisor.

Export the current conversation as a serializable Session struct.

Returns a health summary map for the agent process.

Return the full conversation message history.

Clear conversation history. Config and tools are preserved.

Send a message to the agent without blocking the caller.

Switch the provider (and its config) mid-session.

Start a supervised, persistent agent process.

Stop the agent process.

Send a message with streaming. Calls on_chunk for each text delta. Returns the same result shape as chat/3.

Return accumulated token usage across all turns.

Types

result()

@type result() :: Alloy.Result.t()

Functions

cancel_request(server, request_id)

@spec cancel_request(GenServer.server(), binary()) :: :ok | {:error, :not_found}

Cancel an async request by request_id.

If the request is currently running, the active task is terminated. If the request is queued, it is removed from the queue.

When cancelled, the server broadcasts an {:agent_response, result} payload with status: :error, error: :cancelled, and the matching :request_id.

chat(server, message, opts \\ [])

@spec chat(GenServer.server(), String.t(), keyword()) ::
  {:ok, result()} | {:error, result()}

Send a message and wait for the agent to finish its full loop.

Blocks until the model reaches end_turn (including all tool calls). Conversation history is preserved for subsequent calls.

Options

  • :timeout - GenServer call timeout in milliseconds (default: 30_000).

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

export_session(server)

@spec export_session(GenServer.server()) :: Alloy.Session.t()

Export the current conversation as a serializable Session struct.

Note: if an async Turn is in progress via send_message/3, the exported session reflects the state before that Turn started (a pre-Turn snapshot).

health(server)

@spec health(GenServer.server()) :: map()

Returns a health summary map for the agent process.

messages(server)

@spec messages(GenServer.server()) :: [Alloy.Message.t()]

Return the full conversation message history.

Note: if an async Turn is in progress via send_message/3, this returns a snapshot of the conversation before that Turn started. The in-flight assistant response will appear only after the Turn completes.

reset(server)

@spec reset(GenServer.server()) :: :ok | {:error, :busy}

Clear conversation history. Config and tools are preserved.

Returns {:error, :busy} if an async Turn is currently running via send_message/3.

send_message(server, message, opts \\ [])

@spec send_message(GenServer.server(), String.t(), keyword()) ::
  {:ok, binary()} | {:error, :busy | :queue_full | :no_pubsub}

Send a message to the agent without blocking the caller.

Returns {:ok, request_id} immediately. The agent runs its full Turn loop in a supervised Task, then broadcasts the result via PubSub to "agent:<id>:responses" as {:agent_response, result} where result includes a :request_id field matching the returned ID.

Backpressure behavior:

  • If no Turn is running, the request starts immediately.
  • If a Turn is running and :max_pending > 0, the request is queued.
  • If the queue is full, returns {:error, :queue_full}.
  • If :max_pending == 0, returns {:error, :busy} while running.

Returns {:error, :no_pubsub} if the agent was started without a :pubsub option — without PubSub there is no way to receive results.

Requirements

PubSub must be configured on the agent. Add pubsub: MyApp.PubSub to the agent start options.

Options

  • :request_id - supply your own correlation ID (binary). Defaults to a random URL-safe ID.

Example

{:ok, agent} = Alloy.Agent.Server.start_link(
  provider: {...},
  pubsub: MyApp.PubSub
)

Phoenix.PubSub.subscribe(MyApp.PubSub, "agent:#{session_id}:responses")

{:ok, req_id} = Alloy.Agent.Server.send_message(agent, "Summarise the logs")

receive do
  {:agent_response, %{request_id: ^req_id, text: text}} -> IO.puts(text)
end

set_model(server, provider_opts)

@spec set_model(
  GenServer.server(),
  keyword()
) :: :ok | {:error, :busy}

Switch the provider (and its config) mid-session.

Accepts provider_opts in the same format as the :provider option in start_link/1. Conversation history, tools, system prompt, and all other config fields are preserved.

Examples

Server.set_model(pid, provider: {Alloy.Provider.Anthropic, api_key: key, model: "claude-haiku-4-5"})
Server.set_model(pid, provider: Alloy.Provider.OpenAI)

Returns {:error, :busy} if an async Turn is currently running via send_message/3.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Start a supervised, persistent agent process.

stop(server)

@spec stop(GenServer.server()) :: :ok

Stop the agent process.

stream_chat(server, message, on_chunk, opts \\ [])

@spec stream_chat(GenServer.server(), String.t(), (String.t() -> :ok), keyword()) ::
  {:ok, result()} | {:error, result()}

Send a message with streaming. Calls on_chunk for each text delta. Returns the same result shape as chat/3.

Options

  • :timeout - GenServer call timeout in milliseconds (default: 30_000).

usage(server)

@spec usage(GenServer.server()) :: Alloy.Usage.t()

Return accumulated token usage across all turns.