Pixir.Turn (pixir v0.1.0)

Copy Markdown View Source

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

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.

Summary

Functions

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

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.

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

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

Types

ctx()

@type ctx() :: %{
  :session_id => String.t(),
  :workspace => String.t(),
  :role => atom(),
  optional(:fork_root_session_id) => String.t()
}

Functions

default_max_iterations()

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

developer_context(ctx, mode, permission_mode \\ :auto, presenter_context \\ nil)

@spec developer_context(ctx(), :build | :plan, atom(), 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.

run(ctx, user_text, opts \\ [])

@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(ctx, mode \\ :build, skills_opts \\ [])

@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.