# `Alloy.Agent.Server`
[🔗](https://github.com/alloy-ex/alloy/blob/v0.10.1/lib/alloy/agent/server.ex#L1)

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)

# `result`

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

# `cancel_request`

```elixir
@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`

```elixir
@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`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `export_session`

```elixir
@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`

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

Returns a health summary map for the agent process.

# `messages`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Start a supervised, persistent agent process.

# `stop`

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

Stop the agent process.

# `stream_chat`

```elixir
@spec stream_chat(GenServer.server(), String.t(), (String.t() -&gt; :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`

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

Return accumulated token usage across all turns.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
