Raxol. AgentClientProtocol. Session. SteerAdapter behaviour
(Raxol Agent Client Protocol v0.1.0)
Copy Markdown
View Source
The Session's steer seam — the one injection point through which a live turn's compare-and-swap steer decision is driven.
Raxol.AgentClientProtocol.Session is the single-writer turn owner Steer's
contract demands (its turn field is :idle | {:prompting, Turn} | {:cancelling, Turn}, mutated only from its own mailbox). A steer decision is a
read-modify-write over per-turn CAS state, and the concurrent-steer guarantee
holds ONLY if that read-modify-write is serialized by exactly one owner
process. This behaviour lets the Session BE that owner — it holds the opaque
steer state and calls these callbacks from inside its own handle_call, so the
serialization is satisfied by construction — WITHOUT the ACP package owning
the CAS decision itself. A host injects an adapter that makes that decision;
this package ships only the seam and a default that refuses.
The three callbacks and how the Session drives them
The Session holds one OPAQUE steer_state term (the adapter's — this package
never inspects it) and threads it through:
turn_started(steer_state, turn_token)— a prompt turn began;turn_tokenis the Session's own per-session turn ordinal (turn_seq), the same value the durableturn_startedjournal record and the client's turn events carry. The adapter binds it as the running turn's CAS token so a subsequent steer'sexpected_turn_idcan match it. Returns the next steer_state.turn_ended(steer_state)— the turn drained (any outcome). The adapter clears the CAS token so a steer arriving in the idle window between turns rejects:no_live_turnrather than landing on a dead turn. Returns the next steer_state. The DEDUP index (session-lifetime window) is NOT cleared here — only the turn liveness.resolve(steer_state, request)— the CAS decision itself, run from the Session's mailbox.requestis a plain map%{text, expected_turn_id, client_msg_id}(seerequest/0). Returns{result, next_steer_state};resultis the honest outcome vocabulary (result/0) the Session returns synchronously on the wire and — on{:ok, {:accepted, _}}only — the trigger to forward the steered text to the running turn and append one durable steer record. Rejections change nothing and journal nothing; they are still the wire response.
Default: refuse
Raxol.AgentClientProtocol.Session.SteerAdapter.Unsupported is the default. It
holds no state, no-ops both turn hooks, and answers every resolve/2 with
{:error, :no_steer_channel} — so a base Session with no injected adapter
never mutates steer state (the turn hooks are identity) and honestly reports
that live steering is not wired. Live steering works only once a real adapter
is injected.
Summary
Types
A reference to an accepted (or de-duplicated) steer.
The normalized steer request the Session hands to resolve/2. Mirrors the
_raxol/session.steer wire payload
The honest CAS outcome vocabulary — the same the Surface banner renders and
Raxol.AgentClientProtocol.Ext.Schema.SteerResponse encodes on the wire.
Callbacks
Resolve one steer request against the live turn's CAS state (single-writer).
Clear the CAS token when the turn drained; preserve the dedup index.
Bind the CAS token for a turn that just began (its per-session ordinal).
Types
@type accepted_ref() :: %{ turn_id: term(), offset: non_neg_integer(), client_msg_id: term() | nil }
A reference to an accepted (or de-duplicated) steer.
@type request() :: %{ text: String.t() | nil, expected_turn_id: term(), client_msg_id: term() | nil }
The normalized steer request the Session hands to resolve/2. Mirrors the
_raxol/session.steer wire payload:
:text— the steering text (forwarded to the turn on accept).:expected_turn_id— the turn ordinal the client believes is running (the CAS target).:client_msg_id— the client-supplied idempotency key (may benil).
@type result() :: {:ok, {:accepted, accepted_ref()}} | {:ok, {:duplicate, accepted_ref()}} | {:error, {:stale_turn, term(), term()}} | {:error, :no_live_turn} | {:error, :client_msg_id_reuse} | {:error, :no_steer_channel}
The honest CAS outcome vocabulary — the same the Surface banner renders and
Raxol.AgentClientProtocol.Ext.Schema.SteerResponse encodes on the wire.
Callbacks
Resolve one steer request against the live turn's CAS state (single-writer).
Clear the CAS token when the turn drained; preserve the dedup index.
Bind the CAS token for a turn that just began (its per-session ordinal).