# `Pixir.Turn`
[🔗](https://github.com/Ranvier-Technologies/pixir/blob/main/lib/pixir/turn.ex#L1)

The tool loop (CONTEXT.md "Turn"): one input-to-final-answer cycle, run inside the
Session's supervised Task (ADR 0001).

    record user_message
    loop:
      fold History → call the Provider (streaming deltas as ephemeral Events)
      if the model returned function_calls → run each via the Executor
        (which records tool_call / tool_result), then repeat
      else record the final assistant_message and stop
      if the provider fails → record turn_failed; preserve useful partial text
        as audit-only partial assistant evidence

History is always re-derived from the Log each iteration (ADR 0003): the stateless
Provider sees tool results because they were persisted, not because we threaded them
in memory.

Wire it into a Session like:

    Pixir.Session.start_turn(sid, fn ctx -> Pixir.Turn.run(ctx, prompt) end)

Options: `:provider` (module, default `Pixir.Provider`), `:provider_opts` (passed to
the provider — e.g. `:auth`, `:transport`), `:dry_run` (Turn-level dry-run, ADR
0005), `:max_iterations`, `:bash_timeout_ms` for per-Turn bash execution, and
`:delegation_context` for Subagent child Turns, and `:virtual_overlay` for the
operator-owned `%{read_set: [...], limits: map | nil}` context used by virtual
command Tools. String-keyed virtual overlay maps are normalized to that atom-keyed
representation at Turn intake.

# `ctx`

```elixir
@type ctx() :: %{
  :session_id =&gt; String.t(),
  :workspace =&gt; String.t(),
  :role =&gt; atom(),
  optional(:fork_root_session_id) =&gt; String.t()
}
```

# `default_max_iterations`

Default tool-loop iteration cap. `:infinity` means no cap.

# `developer_context`

```elixir
@spec developer_context(ctx(), :build | :plan, atom(), term(), term()) :: String.t()
```

The late developer-context input item text (px2 Layer 2): the volatile,
session-scoped facts deliberately kept OUT of the cacheable instructions prefix.
Pairs with `system_prompt/3` — see its doc for the pairing contract.

The base text is deliberately byte-stable across plan/build flips (mode is already
fully expressed by the instructions, and a changed `input[0]` would break WebSocket
continuation's prefix-extension check). The base variation is a posture line when the
EFFECTIVE permission deviates from the mode's default: a build-mode Turn forced
read-only — the one case the instructions cannot know about.

Presenter-supplied UX context is appended here as late, non-authoritative developer
context. Presenters such as T3 Code may supply open-file, selection, branch, or
diagnostic facts, but Pixir still renders them into Provider input itself.

Subagent Delegation Context is appended here too: child-specific ids, limits,
deadlines, and Workflow step facts are authoritative for this Turn but deliberately
excluded from the stable instructions prefix.

# `run`

```elixir
@spec run(ctx(), String.t(), keyword()) :: {:ok, String.t()} | {:error, map()}
```

Run one Turn for `user_text`. Returns `{:ok, final_text}` or a structured error.

# `system_prompt`

```elixir
@spec system_prompt(ctx(), :build | :plan, keyword()) :: String.t()
```

The default system prompt for a Turn (open knob). `mode` defaults to `:build`.

px2 pairing contract (ADR 0020): this prompt is byte-stable per mode and names no
workspace. It tells the model a developer message identifies the workspace root, so
any direct `Provider.stream` caller using this prompt MUST also pass
`developer_context: Turn.developer_context(ctx, mode, permission_mode)` — otherwise
the model is promised a message that never arrives and has no workspace root at all.

---

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