pub enum RunOutcome {
Success {
steps: u32,
},
StepCapReached {
steps: u32,
},
TimeBudgetExceeded {
steps: u32,
},
CostBudgetExceeded {
steps: u32,
},
Denied {
steps: u32,
},
AwaitingApproval {
request_id: i64,
steps: u32,
},
Stalled {
steps: u32,
},
Escalated {
steps: u32,
retryable: bool,
},
BudgetCeilingReached {
steps: u32,
},
Refused {
steps: u32,
},
Cancelled {
steps: u32,
},
Finished {
steps: u32,
},
}Expand description
Why a run stopped.
A run stopping is not a run failing — only one of these variants is success, and lumping the rest together as “it didn’t work” loses the difference between a run that needs more budget, one that needs a human, and one that needs a different task. The match is what a caller writes around every entry point:
use io_harness::RunOutcome;
match outcome {
RunOutcome::Success { .. } => "verification passed; ship it",
// Paused, not finished. The pending action is persisted under
// `request_id` and this process may exit; whoever decides later calls
// `resume_with_decision` with that id. Never retry the run from scratch.
RunOutcome::AwaitingApproval { request_id: _, .. } => "ask a human, then resume",
// Ceilings, and they mean different things. More steps or more time is a
// knob; a token ceiling that keeps being hit is usually a task too big
// for one contract.
RunOutcome::StepCapReached { .. } | RunOutcome::TimeBudgetExceeded { .. } => "raise the bound and resume",
RunOutcome::CostBudgetExceeded { .. } | RunOutcome::BudgetCeilingReached { .. } => "split the task",
// The agent is going in circles and was already told once. Resuming
// spends the rest of the budget proving it again — change the goal.
RunOutcome::Stalled { .. } => "rewrite the contract",
// Both are reported *after the fact*: the run that escalated or was
// refused returned the `Err` itself, and a later `resume` reports this
// instead of re-driving the loop.
RunOutcome::Escalated { retryable: true, .. } => "transient provider failure; resume",
RunOutcome::Escalated { .. } => "wrong key or bad request; fix it first",
RunOutcome::Refused { .. } => "the provider's host was denied; widen the net policy",
RunOutcome::Denied { .. } => "a human said no; the action never happened",
// An observer returned `Flow::Cancel`. Finished cleanly, and still resumable.
RunOutcome::Cancelled { .. } => "resume when you want it to continue",
// Only a `Verification::None` run reaches this: it stopped because the
// agent stopped, not because a ceiling did. Nothing checked the work —
// read it, rather than shipping it the way a `Success` may be shipped.
RunOutcome::Finished { .. } => "the agent is done; nothing verified it",
}Every variant carries steps, which is how many steps completed — so a
StepCapReached { steps: 12 } and a Success { steps: 12 } cost the same
and only one of them produced anything. For what the run actually spent, use
RunResult::summary.
Variants§
Success
Verification passed. steps is the step it passed on.
StepCapReached
The step budget was reached before verification passed.
TimeBudgetExceeded
The time budget was exceeded. steps is how many steps completed.
CostBudgetExceeded
The cost (token) budget was exceeded. steps is how many steps completed.
Denied
A human denied a deferred action on resume, so the run stopped without
performing it. steps is how many steps completed.
AwaitingApproval
An approver deferred a decision. The run is paused, not finished: the
pending action is persisted under request_id and survives this
process, so resume_with_decision can continue it once a human
decides. steps is how many steps completed.
Stalled
The agent stopped making progress: for StallPolicy::window consecutive
steps it changed nothing in the workspace while repeating a tool call it had
already made, and it had already been told once. The run stops here rather
than spending the rest of its step budget proving it is stuck. steps is
how many steps completed.
Escalated
A provider failure exhausted its retries and the run was escalated to the
caller. retryable is whether the failure was one another attempt could have
survived — a rate limit or a 503 — as opposed to a wrong key or an
unacceptable request. Reached through resume after the fact: the run that
escalated returned the Err itself.
BudgetCeilingReached
(sub-agent trees) The tree’s aggregate spend ceiling was crossed, so the
whole tree halts — not this one agent hitting its own budget. steps is
how many steps this agent completed before the tree-wide halt.
Refused
The run never started, because reaching the provider needed network access
the policy asked about and a human denied. The authorization happens before
the run’s first step, so steps is normally 0.
Reached through resume after the fact: the run that was refused
returned the Err itself, exactly as an escalation does. Added in 0.12.0 —
"refused" was written to the store from 0.8.0 onward with no variant and
no mapping, so resuming a refused run fell back into the loop and asked
the human again.
Cancelled
An Observer asked the run to stop, and it stopped — at the next step
boundary rather than where the request landed, so no step was abandoned
half-done. steps is how many steps completed before it stopped.
Added in 0.12.0 with Flow::Cancel, which is the
first supported way to stop a run in flight: dropping the run’s future
abandons it mid-step and leaves runs.status as running forever, which
nothing can tell apart from a process that crashed. A cancelled run is
finished rather than abandoned, and stays resumable — a resume reports this
outcome instead of re-driving the loop.
Finished
The agent finished. Only a Verification::None run reaches this: with
no criterion to pass, an assistant turn that calls no tool is the run
saying it is done, and the loop stops there.
Distinct from every ceiling on purpose. An unattended run that completed
its work and one that ran out of steps both stop, and treating them alike
is how a fleet operator ends up re-driving finished work — or worse,
shipping the output of a run that never got there. steps is the step it
finished on.
It is not a claim the work is correct. Nothing checked it; that is
what choosing Verification::None means. A run with a criterion reports
RunOutcome::Success, and that one is a claim — bounded by what the
criterion checked and no wider.
Added in 0.17.0 with Verification::None.
Trait Implementations§
Source§impl Clone for RunOutcome
impl Clone for RunOutcome
Source§fn clone(&self) -> RunOutcome
fn clone(&self) -> RunOutcome
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for RunOutcome
impl Debug for RunOutcome
impl Eq for RunOutcome
Source§impl PartialEq for RunOutcome
impl PartialEq for RunOutcome
impl StructuralPartialEq for RunOutcome
Auto Trait Implementations§
impl Freeze for RunOutcome
impl RefUnwindSafe for RunOutcome
impl Send for RunOutcome
impl Sync for RunOutcome
impl Unpin for RunOutcome
impl UnsafeUnpin for RunOutcome
impl UnwindSafe for RunOutcome
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> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
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 moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
impl<T> Scalar for T
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.