Skip to main content

RunTurnOptions

Struct RunTurnOptions 

Source
pub struct RunTurnOptions {
Show 18 fields pub approval_decisions: Vec<ApprovalDecision>, pub max_steps: Option<usize>, pub stream_tx: Option<Sender<TurnStreamEvent>>, pub native_search_allowed: bool, pub session_approved_tools: HashMap<String, CapabilitySet>, pub unattended: bool, pub is_delegated_worker: bool, pub escape_hatch: bool, pub escalate_sandbox_denials: bool, pub untrusted_context_seed: bool, pub dispatch_recorder: Option<Arc<dyn DispatchRecorder>>, pub clock: Option<Arc<dyn Clock + Send + Sync>>, pub cache_hint: CacheHint, pub delegate_descriptors: Vec<DelegateDescriptor>, pub delegate_max_fanout: Option<u32>, pub delegate_turn_budget: Option<u32>, pub question_answers: Vec<VerifiedAnswer>, pub turn_start_unix_ms: Option<u64>,
}
Expand description

Options for a single run_turn invocation.

A small builder-style struct rather than a long parameter list — keeps the hot-path call sites readable (RunTurnOptions::default()) and gives the HITL-resume path a typed slot for occurrence-ordered decisions without adding another positional argument every existing caller would have to thread through.

Fields§

§approval_decisions: Vec<ApprovalDecision>

Verified, occurrence-ordered decisions for exact tool calls.

Used by the control plane → harness resume cycle: the control plane replays the conversation’s event log, collects every verified response not yet consumed by a matching tool result, and passes the ordered entries here so each dangling call consumes one decision.

A different tool or argument tuple cannot inherit a decision. Identical tuples remain separate entries and are paired oldest-first.

§max_steps: Option<usize>

Per-agent override of the provider↔tool round-trip cap (#801). None falls through to POLYCHROME_AGENT_MAX_STEPS (per-deployment), then the crate’s fixed default of 8 — which is tight for the shipped coding-tool family; a caller that knows this turn’s agent needs a larger (or smaller) budget sets it here rather than every deployment being stuck on one global default.

§stream_tx: Option<Sender<TurnStreamEvent>>

When set, the turn loop forwards each TurnStreamEvent (text delta, tool start) as it arrives, so a caller can stream partial output mid-turn (the harness forwards these over its bidi stream → control plane → Slack chat.appendStream). None keeps the buffered path: the full TurnResult is always returned regardless.

Bounded (#251): forwarding is an awaited Sender::send, so a slow consumer on the other end (an idle Slack client, a stalled control plane) applies real backpressure all the way back through polyc_llm::turn::collect_turn_observed to the provider stream poll loop, instead of letting turn-stream events accumulate in memory without limit.

§native_search_allowed: bool

Whether this turn’s resolved agent is SCOPED to the provider’s native web-search-grounding primitive (issue #1226) — i.e. its builtinTools names polyc_capability::NATIVE_SEARCH_GROUNDING (re-exported as polyc_tools::web::NATIVE_SEARCH_GROUNDING for that crate’s callers).

true does not mean grounding is on for every step: the per-step gate (see the answering loop, which is the only caller that ever sets CompletionRequest::web_search) additionally requires polyc_capability::Capability::ArbitraryEgress to survive this step’s taint state before actually turning the request flag on — the same required ⊆ granted comparison every other tool call goes through, applied once per step since there is no per-call tool_use for this provider-native primitive to intercept. The summarizer and classifier build their own requests and never consult this at all.

§session_approved_tools: HashMap<String, CapabilitySet>

Session-scoped approvals (“approve & don’t ask again”), already filtered to THIS turn’s caller by the control plane (the per-user scope): tool name → the capability set the signed grant covered at approval time (#595). A gated call to one of these tools auto-executes WITHOUT pausing — REGARDLESS of its arguments — but only when the grant’s covered set includes every capability the call is currently missing AND ToolExecutor::cacheable_approval returns true for the tool (the authoritative idempotency gate: a non-idempotent tool can never be session-approved even if a stale entry is present).

Scoped per-tool (not per-exact-args) because “don’t ask again” means “stop prompting me for this tool”; a model rarely repeats an identical call, so binding to exact args would make the approval near-useless. The covered-capability key keeps one convenience approval from silently widening: if the tool’s required set later grows, the old grant does not cover the new capability and the gate asks again.

Unlike Self::approval_decisions these are NOT drained on execution.

§unattended: bool

Whether this turn runs unattended — a scheduled routine firing with no human present (#623). The control plane sets it only for that path (an explicit wire flag, never inferred from the conversation-id shape here).

When true, a gated call the capability decision would escalate does not pause with a PendingApproval — there is no one to answer it and ADR 0003 forbids park-and-resume on this path. It resolves fail-closed to a denial-with-reason: the model receives a legible tool-result error (so it can finish the turn without the tool), the call surfaces on TurnResult::unattended_denials for the control plane to record as a durable audit event, and the turn runs to a normal end.

Default false ⇒ every attended turn is byte-for-byte unchanged: an escalation still pauses with a PendingApproval exactly as today.

§is_delegated_worker: bool

Whether this turn IS a delegated worker’s own nested turn (run_delegate_call), as opposed to a top-level or orchestrator turn. __delegate_to already caps delegation depth at one by never resolving delegate_descriptors for a nested call, but __handoff_to has no equivalent depth cap of its own: it’s advertised unconditionally by run_turn/run_turn_with and matched by tool NAME regardless of advertisement. Without this flag a worker that calls (or hallucinates calling) __handoff_to would suspend its own nested turn with a pending_handoff the delegate machinery has no way to surface — the orphaned request silently degrades into run_delegate_call’s "worker produced no answer" (ForcedCompletion also skips a turn with a pending handoff). When true, the reserved spec is never advertised AND a matching tool call is never treated as a handoff — it resolves through the ordinary unknown-tool path instead, exactly like any other unadvertised name.

Default false ⇒ every non-delegated turn is byte-for-byte unchanged.

§escape_hatch: bool

Enables the fuzzy-match escape hatch (#582, invariant 9): when the model calls a tool name that was NOT advertised this turn, the loop builds a retrieval query from the call itself (the name split into words plus the argument text — the model’s own expression of the capability it needs), asks ToolExecutor::recover_unadvertised for the closest not-yet-advertised tools, and — at most ONCE per turn — appends the matches to the advertised set so the model can re-issue the call against a real tool. The failed call resolves to a synthetic result naming the newly available tools; every firing is logged as a false-negative retrieval miss. A second unadvertised call in the same turn (same or different name) gets the ordinary unknown-tool result.

Default false ⇒ byte-for-byte today’s behavior: an unadvertised call resolves however the executor answers it (typically an unknown-tool error result). The harness sets this from the wire retrieval config’s escape_hatch knob, resolved control-plane-side.

§escalate_sandbox_denials: bool

Enable the graduated-approval sandbox-denial ESCALATION (#301): when true, a call ToolExecutor::sandbox_would_deny flags is routed through the approval gate (pauses with a PendingApproval) instead of being executed and returning the sandbox’s flat denial to the model. The control plane sets this from the resolved per-persona approval policy.

Default false, so existing callers are unaffected: a sandbox-denied call runs and surfaces its own error exactly as before.

§untrusted_context_seed: bool

Durable seed for the untrusted-content-in-context taint state, computed by the control plane over the conversation’s FULL durable event log (any quarantined_content-tagged event) and OR-ed into the agent’s structural in-memory check (untrusted_content_in_context). Taint is the provenance input to grant derivation: while it holds, the granted set loses arbitrary egress and external mutation.

The structural check only sees untrusted content that is still a live LlmContent::ToolResult in the projected transcript. History compaction folds older tool results into a single System summary message — erasing the ToolResult the check keys on — and a non-principal participant’s chat text is never a ToolResult at all. In both cases the durable log still carries the quarantined provenance, so the control plane reads it there and passes the verdict in here. true keeps the taint state live even when the transcript looks clean; the containment escalation then still fires.

Default false: a conversation with no durable untrusted provenance (and no multi-party input) is unaffected, so a first egress on a genuinely clean context still runs unattended.

§dispatch_recorder: Option<Arc<dyn DispatchRecorder>>

Signs + records dispatch mutations (#67, #539/#540) before they apply. When None (the default), pre_dispatch Modify/InjectContext and post_dispatch redactions are NOT applied — the proposed call runs and the raw result stands — so a policy mutation is inert unless a signer is wired. When present, each mutation is recorded first and applied only on success (fail-closed).

§clock: Option<Arc<dyn Clock + Send + Sync>>

The turn’s clock and jitter source (#656). When None (the default) the turn wires retry::RealClock — real wall time for jitter entropy and a real timer for the retry backoff — so production behaves exactly as before. A test supplies a virtual clock with a fixed jitter seed so the retry backoff (the turn loop’s only non-determinism) replays identically and can be stepped without a wall-clock wait.

§cache_hint: CacheHint

Provider prompt-caching hint for this turn (#629).

When CacheHint::StablePrefix, each step’s CompletionRequest marks the stable prefix — the system text plus the tool-spec set built once per turn (#628) — as cacheable, so a provider that supports prompt caching skips re-processing it on every step (the biggest latency lever on a multi-step turn). A provider without caching ignores it. Default CacheHint::None ⇒ no caching, so auxiliary calls that build their own options are unaffected. The control plane sets it from its turn-boundary config snapshot, so the knob lands at a turn boundary, never as a compiled constant.

§delegate_descriptors: Vec<DelegateDescriptor>

This turn’s resolved __delegate_to targets (#870), one entry per live can_delegate_to entry the bound Agent declares — each a complete, self-contained worker configuration the control plane resolved at dispatch. run_turn_with advertises the reserved delegate::DELEGATE_TOOL_NAME tool ONLY when this is non-empty; a call to it is resolved by find_delegate_descriptor and dispatched as a nested, context-isolated run_turn_with call that joins the SAME batch’s ordinary tool futures (contrast HandoffRequest, which short-circuits the batch). Default empty ⇒ byte-for-byte identical to a turn with no delegation targets: no tool is advertised, so a model that never sees the name can’t emit it.

§delegate_max_fanout: Option<u32>

Fan-out width cap for this turn (#874): the maximum number of __delegate_to calls allowed in a SINGLE batch/step — resolved control-plane-side from the bound agent’s Agent.delegateMaxFanout (see polyc_control_plane::delegate::resolve_delegate_max_fanout). None ⇒ this crate’s own DEFAULT_DELEGATE_MAX_FANOUT, clamped to DELEGATE_MAX_FANOUT_CEILING regardless of source — a caller that resolves a wire value ALREADY clamps it, but this crate clamps again defensively so a directly-constructed RunTurnOptions (a test, or a future caller) can’t accidentally exceed the ceiling either. A __delegate_to call beyond the cap, counted within the SAME batch in source order, resolves to a structured error result — it is never queued, never silently dropped, and never counts as an executed delegation for forensic/usage purposes (no DelegateRecord is produced for it).

§delegate_turn_budget: Option<u32>

Turn-scoped total delegate-call budget (#874): the maximum number of __delegate_to calls this turn may dispatch ACROSS ALL its batches/steps — not just one batch. Bounds a pathological re-decompose-every-step loop from spawning unbounded workers over a long-running turn, complementing Self::delegate_max_fanout’s per-batch bound. NoneDEFAULT_DELEGATE_TURN_BUDGET, clamped to DELEGATE_TURN_BUDGET_CEILING. A call beyond the turn budget resolves to a structured error exactly like an over-fan-out call.

§question_answers: Vec<VerifiedAnswer>

Verified, signed answers to ask_question questions this conversation gathered since the turn paused (#1660) — the question-pause SIBLING of Self::approval_decisions, not a reuse of it. Populated by the harness from control-plane-verified question_response events on the turn input.

The RUNTIME identity is the occurrence (turn_id, call_id, index) (#2523): step::QuestionResumePrePass matches an entry against a dangling call only when all three are equal, so an answer for one occurrence can never resolve a later call that merely reused the provider id. The call’s question_args_json is bound one layer down, in the SIGNATURE: polyc_turn_runner::verify_question_answers reconstructs the signed canonical from the forwarded args, so an altered args value fails verification and the entry never reaches this field. Carrying the args into question::VerifiedAnswer as well would add a field the runtime match does not read; the occurrence names exactly one call, and that call’s own arguments are re-parsed from the transcript.

Consumed by step::QuestionResumePrePass: a dangling ask_question tool_use in the resumed transcript resolves once every question in its call has a matching entry here; any question still missing re-pauses the turn exactly as a fresh call would. Default empty ⇒ byte-for-byte identical to a turn with no pending questions.

§turn_start_unix_ms: Option<u64>

This turn’s frozen dispatch clock (#1323), in Unix milliseconds: the SAME value the control plane freezes once per dispatch, renders as the top-level turn_start_block system message, and records as ModelCallRecord.captured_clock_unix_ms. run_delegate_call renders it into a worker’s own turn-start system message so a delegated worker learns the turn’s start instant exactly like the top-level turn does, instead of improvising one against its training-data era.

Never read from a fresh clock on this path: replay determinism (INV-11) requires the worker’s rendered prompt to reproduce byte-identically, which a second, independently-timed read could not guarantee. None means no turn-start stamp is rendered for any worker this turn delegates to (the caller didn’t resolve one, or the instant was underivable) — a worker told nothing is safer than one told a wrong time, mirroring turn_start_block’s own rule.

Trait Implementations§

Source§

impl Clone for RunTurnOptions

Source§

fn clone(&self) -> RunTurnOptions

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RunTurnOptions

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for RunTurnOptions

Source§

fn default() -> RunTurnOptions

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more