pub struct TurnCtx<'a, P, T>{Show 19 fields
pub provider: &'a P,
pub tools: &'a T,
pub model: &'a str,
pub options: &'a RunTurnOptions,
pub messages: Vec<Message>,
pub outputs: Vec<Message>,
pub total_usage: Usage,
pub last_stop: Option<StopReason>,
pub executed_tools: bool,
pub produced_text: bool,
pub pending_handoff: Option<HandoffRequest>,
pub denied_sigs: HashSet<(String, String)>,
pub approved_remaining: HashSet<(String, String, String)>,
pub denial_reprompts: usize,
pub saw_sig_match_denial: bool,
pub grant_replays: Vec<GrantReplayClear>,
pub unattended_denials: Vec<UnattendedDenial>,
pub escape_hatch_fired: bool,
pub delegate_records: Vec<DelegateRecord>,
}Expand description
The turn’s working state, threaded through each TurnStep.
Owns what were locals in the turn function — the working transcript, the
accumulated wire outputs, the folded usage, the last stop reason, and the
loop-control flags — and borrows the turn’s immutable inputs (the provider,
tool executor, model, and options) for the lifetime 'a so a step can dial
the provider without re-plumbing them.
Fields§
§provider: &'a PThe LLM provider the turn dials.
tools: &'a TThe executor that advertises and runs this turn’s tools.
model: &'a strThe model identifier for provider requests.
options: &'a RunTurnOptionsThe options the turn was invoked with (streaming channel, decisions).
messages: Vec<Message>The working transcript driven through the loop and any post-steps.
outputs: Vec<Message>The wire messages produced so far — assistant text and tool results.
total_usage: UsageUsage folded across every provider call this turn has made.
last_stop: Option<StopReason>Stop reason of the most recent provider step.
executed_tools: boolWhether any tool ran this turn (resume pre-pass or the loop).
produced_text: boolWhether the model ever emitted user-visible text this turn.
pending_handoff: Option<HandoffRequest>The pending handoff request, if the model asked to hand off.
denied_sigs: HashSet<(String, String)>STICKY/TERMINAL denials keyed to the tool signature (name + canonical args) rather than the provider call-id. Once a human denies an action, the model can re-emit the SAME logical call with a fresh call-id; a call-id-only check would re-pause and re-prompt for something already rejected. The resume pre-pass seeds this and the in-loop batch records into it, so a matching re-emit is auto-denied (synthetic result) without ever pausing again.
approved_remaining: HashSet<(String, String, String)>Approvals still awaiting execution, keyed by the canonicalized
(id, name, args) identity (#141). Seeded from
RunTurnOptions::approved_call_ids; the resume pre-pass removes each
entry it spends so neither the pre-pass nor the loop re-executes an
approval the model re-emits. args is canonicalized through canon_args
so a re-emit with reordered keys still matches by value.
denial_reprompts: usizeHow many loop iterations have resolved a signature-matched terminal
denial — the model retrying an action a human already denied. The first
signed denial (by call-id, before any signature is recorded) does not
count; only re-emits of an already-denied signature do. Persists across
iterations so the stateless CircuitBreaker step can increment it and
end the turn once it reaches MAX_DENIAL_REPROMPTS.
saw_sig_match_denial: boolWhether the step that just resolved handled a signature-matched terminal
denial. The loop republishes it onto the ctx each iteration before the
CircuitBreaker step reads it; the step never touches the working
state.
grant_replays: Vec<GrantReplayClear>Gate clears a remembered passkey grant was solely responsible for
(#594), accumulated across the turn’s loop iterations. Each entry is an
executed tool call that ran only because a grant kept a capability
untrusted content in context would have revoked; the final
TurnResult::grant_replays carries
them out for the control plane to audit.
unattended_denials: Vec<UnattendedDenial>Gated calls an unattended turn denied fail-closed (#623), accumulated
across the loop iterations. Each entry is a call the capability gate would
have escalated on a turn with RunTurnOptions::unattended
set, where no live grant covered the shape; the model saw a legible denial
result and the call neither ran nor paused. The final
TurnResult::unattended_denials
carries them out for the control plane to audit. Always empty on an
attended turn.
escape_hatch_fired: boolWhether the fuzzy-match escape hatch (#582, invariant 9) has fired
this turn. The hatch widens the advertised tool set at most ONCE per
turn; once set, a later call naming an unadvertised tool resolves to
the ordinary unknown-tool result again.
delegate_records: Vec<DelegateRecord>One entry per __delegate_to call dispatched this turn (#872),
accumulated across the loop iterations. The final
TurnResult::delegate_records
carries them out for the control plane to append as signed forensic
events. Empty for every turn that never called __delegate_to.
Implementations§
Source§impl<P, T> TurnCtx<'_, P, T>
impl<P, T> TurnCtx<'_, P, T>
Sourcepub fn finish(
self,
pending_approvals: Vec<PendingApproval>,
handoff: Option<HandoffRequest>,
) -> TurnResult
pub fn finish( self, pending_approvals: Vec<PendingApproval>, handoff: Option<HandoffRequest>, ) -> TurnResult
Consumes the turn’s working state into a crate::TurnResult, moving
every accumulated audit surface out in one place.
The turn loop’s return sites differ only in the approvals they surface
and whether a handoff rides along; the transcript, folded usage, last
stop reason, and the audit surfaces (#594 grant replays, #623
unattended denials) are always whatever the context accumulated. Owning
that move here makes forgetting an audit surface at a return site
impossible by construction.
Sourcepub fn finish_failed(self, failure: MidStreamFailure) -> TurnResult
pub fn finish_failed(self, failure: MidStreamFailure) -> TurnResult
Consumes the turn’s working state into a crate::TurnResult that
reports a mid-turn provider stream failure (#798), exactly like
Self::finish but with crate::TurnResult::mid_stream_failure set
and no pending approvals (a failed stream never paused for HITL).
Whatever the loop already accumulated — executed tool results, produced
text, folded usage — rides along on the returned crate::TurnResult
instead of being discarded, which is the whole point: the caller can
persist iterations 1..N-1’s work AND fail the turn with a typed error,
rather than losing both to a bare Err propagated via ?.
Auto Trait Implementations§
impl<'a, P, T> !RefUnwindSafe for TurnCtx<'a, P, T>
impl<'a, P, T> !UnwindSafe for TurnCtx<'a, P, T>
impl<'a, P, T> Freeze for TurnCtx<'a, P, T>
impl<'a, P, T> Send for TurnCtx<'a, P, T>
impl<'a, P, T> Sync for TurnCtx<'a, P, T>
impl<'a, P, T> Unpin for TurnCtx<'a, P, T>
impl<'a, P, T> UnsafeUnpin for TurnCtx<'a, P, T>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more