Skip to main content

TaskContract

Struct TaskContract 

Source
pub struct TaskContract {
Show 18 fields pub goal: String, pub file: PathBuf, pub root: Option<PathBuf>, pub constraints: Vec<String>, pub verify: Verification, pub max_steps: u32, pub max_duration: Option<Duration>, pub max_tokens: Option<u64>, pub max_retries: u32, pub mcp: Vec<McpServer>, pub images: Vec<Media>, pub commit_identity: Identity, pub tools: Toolbox, pub retry: RetryPolicy, pub stall: StallPolicy, pub context: ContextBudget, pub exec_timeout: Duration, pub skills: Option<PathBuf>,
}
Expand description

A single unit of work handed to the harness.

The agent edits one file to meet Verification, bounded by budgets. v0.2 adds the time and cost (token) budgets and the retry limit; a 0.1.0 caller that only set goal, file, verify, and max_steps still compiles — the new bounds default to unbounded / two retries.

Every entry point in the crate takes one of these, so it is where a run’s definition of done and its ceilings are decided — before the model is asked anything, and independently of which provider will serve it:

use io_harness::{TaskContract, Verification};
use std::time::Duration;

let contract = TaskContract::workspace(
    "make `parse` return an error on empty input instead of panicking",
    "/path/to/repo",
    // The criterion is checked by compiling and running, so a plausible-looking
    // stub cannot satisfy it. This is the half of the contract that decides
    // whether the run *succeeded*, as opposed to merely stopping.
    Verification::WorkspaceTestPasses {
        files: vec!["src/parse.rs".into()],
        test_src: "#[test] fn empty_is_err() { assert!(parse(\"\").is_err()); }".into(),
    },
)
// And this is the half that decides when it stops regardless. All three are
// independent stops with their own `RunOutcome`, so a run that ran out of
// money is distinguishable afterwards from one that ran out of ideas.
.with_max_steps(20)
.with_time_budget(Duration::from_secs(900))
.with_token_budget(200_000)
// Surfaced to the model verbatim. A constraint is guidance, not a boundary —
// what the agent may actually touch is the `Policy`'s job, because the model
// can ignore a sentence and cannot ignore a refusal.
.with_constraint("do not change the public signature of `parse`");

assert_eq!(contract.max_steps, 20);
assert!(contract.root.is_some()); // workspace mode: grep, find, read, write

TaskContract::new is the other constructor: one file, one tool, and no policy enforcement — a policy passed to a single-file run is refused with Error::Config rather than silently ignored. Reach for TaskContract::workspace for anything with a boundary.

The with_* builders that add capability rather than bounds — with_mcp, with_tools, with_skills — are workspace-mode only and are validated at run start, so a duplicate tool name or an unreadable skills directory fails the run before the first completion is billed.

Fields§

§goal: String

Plain-language goal, e.g. “add a hello function that returns 42”.

§file: PathBuf

The one file the agent may read and write in single-file mode. In workspace mode (root is Some) it is unused.

§root: Option<PathBuf>

Workspace root for multi-file mode. None (the 0.1/0.2 default) runs the single-file loop over file. Some(dir) runs the workspace loop, where the agent greps/finds/reads/writes several files under dir.

§constraints: Vec<String>

Extra rules the agent must respect, surfaced to the model verbatim.

§verify: Verification

The checkable success criterion. The run succeeds when this passes.

§max_steps: u32

Step budget: hard cap on loop iterations. The run stops when reached.

§max_duration: Option<Duration>

Time budget: the run stops if it runs longer than this. None = unbounded.

§max_tokens: Option<u64>

Cost budget, measured in total tokens summed across completions (no price telemetry exists, so cost is counted in tokens). None = unbounded.

§max_retries: u32

How many times a failing provider/tool step is retried before the run escalates the error. Defaults to 2.

§mcp: Vec<McpServer>

MCP servers to connect for this run. Their tools are offered to the model beside the built-ins, namespaced mcp__<server>__<tool>.

Empty by default, so a 0.7.0-era contract behaves exactly as before. Workspace mode only: single-file mode has one tool and no tool layer to extend.

§images: Vec<Media>
Available on crate feature media only.

Images handed to the agent alongside the goal, shown to the model on every step of the run.

This is the caller’s half of the image capability: the task is about these, so they persist for the whole run rather than being attached once. The agent’s own half — looking at an image already in the workspace — is the view_image built-in, which is gated on the path the model names.

Empty by default, so a 0.14.0-era contract behaves exactly as before. A provider that does not accept images refuses a run carrying any, before anything is sent; see crate::Provider::accepts_images.

§commit_identity: Identity

Who a commit the agent makes is attributed to.

Defaults to an agent identity at a domain reserved so it can never exist. git commit fails outright with no user.email configured, so this cannot be left to the machine, and inheriting the repository’s identity would attribute the agent’s commit to whichever human configured that checkout.

§tools: Toolbox

Tools the embedding program supplies itself, offered to the model beside the built-ins and governed by the same policy and trace.

Empty by default, so a 0.8.1-era contract behaves exactly as before. In process, unlike TaskContract::mcp: see crate::tools::Tool for what registration does and does not authorize.

§retry: RetryPolicy

How long to wait between provider attempts, and how long a wait may grow.

Applies only to a failure that is_retryable; an authentication failure escalates on its first occurrence however patient this is.

§stall: StallPolicy

When to decide the agent has stopped making progress, and how many times to tell it. StallPolicy { window: 0, .. } switches detection off.

§context: ContextBudget

How much of each request the observation log may occupy.

Defaults to ContextBudget::default. Separate from TaskContract::max_tokens because they bound different things: that is what the whole run may spend, this is what one request may carry — though the two are related, since the share is taken of what the spend budget has left.

§exec_timeout: Duration

How long a command the agent runs with the exec tool may take before it is killed and reported as a timeout.

Defaults to DEFAULT_EXEC_TIMEOUT. Set it with TaskContract::with_exec_timeout. Separate from TaskContract::max_duration because they bound different things: that is how long the whole run may take, this is how long any one command may hang before the run gets its turn back — without it, a wedged command consumes the run’s whole time budget and the run reports a budget stop, which is the wrong diagnosis for what happened.

§skills: Option<PathBuf>

Directory of skill files to offer the agent, or None (the default) for no skills.

The path is held rather than the discovered set, because reading a directory is fallible and a builder method is not: discovery happens at run start, so a directory that does not exist fails the run with Error::Config naming the path — the same point and the same way TaskContract::tools is arbitrated.

Implementations§

Source§

impl TaskContract

Source

pub fn new( goal: impl Into<String>, file: impl Into<PathBuf>, verify: Verification, ) -> Self

Minimal contract: goal, target file, and a success criterion. Defaults to 8 steps, no time/token budget, 2 retries, no constraints.

Source

pub fn workspace( goal: impl Into<String>, root: impl Into<PathBuf>, verify: Verification, ) -> Self

A workspace task: the agent may grep, find, read, and write several files under root. verify should be a multi-file variant (Verification::EachCompilesRust / Verification::WorkspaceTestPasses). Defaults match TaskContract::new (12 steps here, since repo tasks take more turns), no time/token budget, 2 retries.

Source

pub fn with_mcp<I>(self, servers: I) -> Self
where I: IntoIterator<Item = McpServer>,

Connect these MCP servers for the run and offer their tools to the model.

Each server is authorized before it is reached — spawning a stdio server is an exec check on its binary, dialling an HTTP one is a network check on its host — so configuring a server here does not grant access to it.

Source

pub fn with_images<I>(self, images: I) -> Self
where I: IntoIterator<Item = Media>,

Available on crate feature media only.

Hand the agent images to look at, alongside the goal.

A new named method rather than a parameter on any of the sixteen entry points: every one of them takes a TaskContract, so attaching here works with all of them and changes no existing signature.

Construct each crate::Media with crate::Media::image, which refuses a media type no provider documents and refuses an image over the per-image size bound. The total carried by one request is bounded too — see crate::provider::MAX_REQUEST_IMAGE_BYTES.

Source

pub fn with_commit_identity( self, name: impl Into<String>, email: impl Into<String>, ) -> Self

Attribute the agent’s commits to this name and address.

Replaces the default agent identity. Neither may be empty or contain a control character — both reach the commit object and the reflog.

Source

pub fn with_tools(self, tools: Toolbox) -> Self

Register in-process tools for the run and offer them to the model.

Registration makes a tool available; it does not authorize it. Each call is an Act::Exec check on the tool’s name, and a registered tool runs with the embedding program’s own privileges — see crate::tools::Tool for the full bound.

A name that shadows a built-in, uses the mcp__ prefix, or duplicates another registered tool fails the run with Error::Config before the first completion.

Workspace mode only, like TaskContract::with_mcp: single-file mode has one tool and no tool layer to extend.

Source

pub fn with_skills(self, dir: impl Into<PathBuf>) -> Self

Offer the agent the skills in dir — see crate::skills for the layout.

The directory is read at run start, not here, so a path that does not exist, is not a directory, or holds more than MAX_SKILLS skills fails the run with Error::Config naming it, before the first completion.

A skill is instructions the model may choose to read. Offering one grants nothing: the read goes through the policy when it happens, and anything the model then does is checked as it always is.

Source

pub fn with_max_steps(self, max_steps: u32) -> Self

Override the step budget.

Source

pub fn with_time_budget(self, max_duration: Duration) -> Self

Set the time budget.

Source

pub fn with_token_budget(self, max_tokens: u64) -> Self

Set the cost budget, in total tokens across all completions.

Source

pub fn with_exec_timeout(self, exec_timeout: Duration) -> Self

Set how long a command the agent runs with exec may take.

A new named builder rather than a parameter, like every other capability this crate has added: every entry point takes a TaskContract, so setting it here works with all of them and changes no existing signature.

use io_harness::{TaskContract, Verification, DEFAULT_EXEC_TIMEOUT};
use std::time::Duration;

// A repository whose cold build is slower than the default ceiling. Raise
// it rather than watching honest work be killed as if it had hung.
let patient = TaskContract::workspace("build and test it", "/monorepo", Verification::None)
    .with_exec_timeout(Duration::from_secs(2400));

// And the other direction: an unattended fleet job that would rather give
// up on a command than sit behind it.
let impatient = TaskContract::workspace("lint it", "/repo", Verification::None)
    .with_exec_timeout(Duration::from_secs(60));

assert!(patient.exec_timeout > DEFAULT_EXEC_TIMEOUT);
assert!(impatient.exec_timeout < DEFAULT_EXEC_TIMEOUT);
Source

pub fn with_context_budget(self, context: ContextBudget) -> Self

Set how much of each request the observation log may occupy.

Sits beside TaskContract::with_token_budget because they are the two halves of one thing: that bounds what the run may spend, this bounds what any one request carries of what it has already observed.

Source

pub fn with_retry_policy(self, retry: RetryPolicy) -> Self

Set how long to wait between provider attempts.

Source

pub fn with_stall_policy(self, stall: StallPolicy) -> Self

Set when a run decides its agent has stalled, and how often to tell it.

StallPolicy { window: 0, .. } disables detection, restoring 0.10.0 behaviour exactly.

Applies to workspace and sub-agent runs. A single-file run (TaskContract::new) ignores it: it has one tool and one file, so “repeated a call without changing anything” describes its only move.

Source

pub fn with_max_retries(self, max_retries: u32) -> Self

Override the retry limit for failing provider/tool steps.

Source

pub fn with_constraint(self, constraint: impl Into<String>) -> Self

Add a constraint the agent must respect.

Trait Implementations§

Source§

impl Clone for TaskContract

Source§

fn clone(&self) -> TaskContract

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 TaskContract

Source§

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

Formats the value using the given formatter. 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> 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<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

Source§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
Source§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
Source§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
Source§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
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