# `Pixir.ACP.Server`
[🔗](https://github.com/Ranvier-Technologies/pixir/blob/main/lib/pixir/acp/server.ex#L1)

The ACP agent (server side) over stdio (ADR 0009): a single `GenServer` that owns the
stdout writer and the `acp_session_id ↔ pixir_session_id` map, decodes ndjson JSON-RPC
from stdin, dispatches by method onto `Pixir.Conversation`, and runs each
`session/prompt` in a supervised Task.

## Channel discipline (ADR 0005)

**stdout carries only JSON-RPC.** Every write goes through this one process, so the
ndjson stream never interleaves. Prompt Tasks never touch stdout directly — they call
`emit/2`. Diagnostics go to stderr. The caller (`run/0`) redirects `Logger` to stderr
before starting so no log line corrupts the stream.

## stdin

A dedicated reader process blocks on `IO.read(io, :line)` and forwards `{:line, l}` /
`:eof` / `{:io_error, r}` to this server, so the server mailbox is never blocked on raw
stdin. `run/0` explicitly configures stdio as Unicode because GUI launchers can start
Pixir without a UTF-8 locale; ACP wire text must remain UTF-8 regardless of the parent
process environment. On EOF the server stops normally and `run/0` unblocks (exit 0).

## Scope

Implements `initialize`, `session/new`, `session/prompt`, `session/cancel`,
`authenticate` + `logout` (ACP handshake no-ops; Pixir advertises terminal
auth through `pixir login`, and owns Credential storage outside the stdio channel),
`session/set_mode` + `session/set_config_option` (modes, models, and reasoning effort,
D.2), `session/set_model` (legacy Pixir/T3 compatibility), and `session/load` + `session/resume`
(lifecycle, A.6); emits `session/update` (incl. `current_mode_update` and
`plan`) and ORIGINATES `session/request_permission` (interactive permissions,
A.2 — correlating the client's response against `pending_requests`). Per-turn
knobs (model, reasoning effort, `permission_mode`) ride on `session/prompt`
`_meta`; sticky model and reasoning-effort selection are exposed through
`configOptions`; the legacy model catalog + auth status ride on `initialize._meta.pixir`.
Other methods get `-32601`. JSON-RPC errors are reserved for protocol faults; a
failed Turn is reported as content with `stopReason:"end_turn"` (ADR 0009 §5).
Permission posture follows the session mode (`plan` → read-only) and
`_meta.permission_mode "ask"` (→ interactive approval via the ACP asker).

## TODO(presenter-session-id)

ACP clients already receive the Pixir Session id from `session/new`, but tool/model
projections can still make the parent id invisible to the assistant text layer. The
next Presenter slice should expose the parent `pixir_session_id` consistently in
Pixir-specific `_meta`, tool result raw output, or session/status updates so T3/Zed
prompts can report it without guessing from child ids. Keep this presentation-only:
the Log remains authoritative and stdout must remain JSON-RPC only.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `emit`

```elixir
@spec emit(GenServer.server(), map()) :: :ok
```

Emit a `session/update` notification (called by prompt Tasks; serializes writes).

# `emit_event`

```elixir
@spec emit_event(GenServer.server(), binary(), Pixir.Event.t()) :: :ok
```

Translate and emit a Pixir Event with server-owned presentation state.

# `feed`

```elixir
@spec feed(GenServer.server(), binary()) :: :ok
```

Feed one already-decoded JSON-RPC line into the Server. Test seam that drives the same
`handle_info({:line, _})` path the reader uses, without real stdio.

# `request_permission`

```elixir
@spec request_permission(GenServer.server(), map()) :: {:ok, map()} | {:error, term()}
```

Originate a `session/request_permission` request to the client and BLOCK until
the client responds (A.2). Returns the raw `RequestPermissionResponse` result
(a map) for `Translate.permission_outcome/1` to interpret, or `{:error, reason}`.

Called from inside the Executor's Task (the Turn's tool loop), so blocking here
blocks only that one Task — never the Server GenServer (which keeps writing and
reading lines, including the eventual response). The Server owns the timeout and
removes the pending request if a silent client never replies.

# `run`

```elixir
@spec run() :: :ok
```

Blocking entrypoint for `pixir acp`. Redirects `Logger` to stderr, starts a linked
Server reading `:stdio`, and blocks until the Server stops on EOF. Returns `:ok` so the
CLI router exits 0.

# `start_link`

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

Start the Server. Opts:

  * `:io` — the stdio device (default `:stdio`; inject a `StringIO`/pipe in tests).
  * `:provider`, `:provider_opts` — passed through to each Turn (test seam).
  * `:prompt_resolve_hook` — test callback at the terminal-status/reply boundary.
  * `:name` — optional registered name.

---

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