1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
//! An embeddable agent runtime for Rust. Any task, any provider, in your process
//! — with a permission boundary, a sandbox, and a durable trace you own.
//!
//! You hand it a [`TaskContract`]: the task, the workspace it may touch, and what
//! it may read, write, run and dial. The harness runs the loop — observe, reason,
//! act, check, stop — and returns a [`RunResult`] whose [`RunOutcome`] says why it
//! stopped, with every step, refusal, and budget draw in a SQLite trace
//! ([`Store`]) you can read afterwards. No daemon and no CLI.
//!
//! The agent can run the project's own toolchain, so the language a project is
//! written in is not this crate's business. [`Verification`] is optional and
//! language-agnostic: a criterion can be `cargo test`, `npm test`, `go test
//! ./...` or [`Verification::None`] when the task has no checkable criterion at
//! all.
//!
//! # Quickstart
//!
//! ```no_run
//! use io_harness::{run_with, ApproveAll, OpenRouter, Policy, Store, TaskContract, Verification};
//!
//! #[tokio::main]
//! async fn main() -> io_harness::Result<()> {
//! let provider = OpenRouter::from_env()?; // OPENROUTER_API_KEY + OPENROUTER_MODEL
//! let store = Store::memory()?;
//!
//! let contract = TaskContract::workspace(
//! "the test suite is failing; find out why and fix it",
//! "/path/to/repo",
//! // The project's own command decides whether the work is done. Nothing
//! // on this path is Rust-aware.
//! Verification::Command { argv: vec!["npm".into(), "test".into()], expect_exit: 0 },
//! );
//!
//! // src/ is writable, secrets/ is refused outright and never reaches a human,
//! // and the agent may run the test runner but nothing that publishes.
//! let policy = Policy::default()
//! .layer("app")
//! .allow_read("*")
//! .allow_write("src/*")
//! .deny_read("secrets/*")
//! .allow_exec("npm*")
//! .deny_exec("npm publish*");
//!
//! let result = run_with(&contract, &provider, &store, &policy, &ApproveAll).await?;
//! println!("{:?}", result.outcome);
//! Ok(())
//! }
//! ```
//!
//! [`run`] is the same loop with no policy — it uses [`Policy::permissive`],
//! which enforces nothing. Every entry point has an observed twin taking an
//! [`Observer`] as its last argument ([`run_with_observed`], and so on).
//!
//! # What it does
//!
//! **The loop.** [`TaskContract::workspace`] gives the agent `grep`, `find`,
//! `read_file`, `write_file` and `edit_file` across a repository root;
//! [`TaskContract::new`] names one file to edit.
//!
//! **Commands, under the same boundary as everything else.** The agent runs the
//! project's own build, tests, linter or package manager through an `exec` tool
//! ([`tools::EXEC_TOOL`]) taking a fixed argv and never a shell string, so there
//! is no `;`, `&&` or `$( )` to parse and therefore none to get wrong. Every call
//! is an [`Act::Exec`] check on the program *and* on the whole argv, so
//! `allow_exec("cargo test*")` beside `deny_exec("cargo publish*")` means what it
//! reads. A command runs in the workspace with the embedding program's privileges
//! and is **not** sandboxed — see [`tools::exec`] for the whole of that bound,
//! and [`DEFAULT_EXEC_TIMEOUT`] for the ceiling on one that wedges.
//!
//! **Verification in any language, or none.** [`Verification::Command`] runs a
//! caller-supplied command in the sandbox and asserts its exit status — one
//! variant covering every language the machine has a toolchain for.
//! [`Verification::None`] is a run with no gate, ended by an assistant turn that
//! calls no tool and reported as [`RunOutcome::Finished`]. What a passing gate
//! proves is narrower than it reads, and [`Verification`] states it exactly.
//! [`toolchain::detect`] tells the agent what this project's commands
//! conventionally are, so it does not spend turns finding out.
//!
//! **A permission boundary.** A [`Policy`] of layered, deny-first [`Rule`]s
//! decides what the agent may read, write, execute ([`Act::Exec`]) and connect
//! to ([`Act::Net`]), enforced in the tool and verification layers rather than
//! in the prompt. Anything marked [`Effect::Ask`] goes to an [`Approver`], which
//! may approve (optionally rewriting the action or remembering a rule), deny, or
//! [`Defer`](Decision::Defer) — persisting the pending action so a human can
//! decide after this process has exited, with [`resume_with_decision`] and
//! [`resume_tree_with_decision`] continuing on that answer. Every refusal is in
//! the trace, attributed to the rule and the layer that produced it.
//!
//! **Budgets and stop conditions.** Steps, wall-clock time, and token spend are
//! capped by the contract. A whole tree of agents draws from one shared
//! [`Ledger`] that no spawned contract can raise.
//!
//! **Agent composition.** [`run_tree`] runs a workspace contract as the root of
//! a tree: the agent gains [`SPAWN_TOOL`], which launches a contained sub-agent
//! over the same workspace, and the child's result composes back into the
//! parent's next turn. Children nest, and many run at once up to
//! [`Containment::max_concurrent`]. A child inherits its parent's policy and can
//! only narrow it ([`Policy::contain`]: allows intersect, denies union, at any
//! depth). [`run`] and [`run_with`] never expose the spawn tool.
//!
//! **An execution sandbox.** Commands the verification gate runs execute in an
//! ephemeral [`Sandbox`]: an isolated workdir, resource caps that *kill* rather
//! than throttle ([`SandboxLimits`]), outbound network denied by default, and
//! guaranteed teardown. One trait, a native [`Backend`] per platform over a
//! portable floor that runs everywhere, chosen by [`select`] and recorded in the
//! trace.
//!
//! **Durable, unattended runs.** After every completed step the trace, the
//! budget draw, and a checkpoint commit in one transaction, so a crash leaves
//! either a whole step or none of it. [`resume`] and [`resume_tree`] reconstruct
//! the run from the store and continue every agent from its own last committed
//! step: completed steps are not re-run, the [`Ledger`] is restored from durable
//! totals rather than reset or double-charged, and an irreversible edit already
//! applied is re-observed rather than repeated. [`resume_from_stored_policy`]
//! and [`resume_tree_from_stored_policy`] read the boundary the run was started
//! under back out of the store instead of trusting the caller to pass the same
//! one again — prefer them when the policy is what matters.
//! [`Store::run_status`] and [`RunStatus`] report where a run stands; a resume
//! against a missing or newer-format checkpoint is a typed [`Error::Resume`],
//! never a panic or a half-resume.
//!
//! **Providers, with fallback.** [`OpenRouter`], [`Anthropic`] and [`OpenAi`]
//! behind one [`Provider`] trait, over the crate's own HTTP+SSE client.
//! [`provider::Fallback`] moves to the next configured provider when one is down
//! or rate-limited, and failures are classified ([`ProviderErrorKind`]) so a
//! caller can tell a retryable transport error from a terminal one.
//! [`RetryPolicy`] governs the backoff and [`StallPolicy`] detects a run that is
//! repeating itself rather than progressing.
//!
//! **Extensibility, in-process and out.** Implement the object-safe [`Tool`]
//! trait for something the embedding program already does, collect them in a
//! [`Toolbox`], and register it with [`TaskContract::with_tools`] — no second
//! process, transport, or serialization hop. Or point the harness at MCP servers
//! with [`TaskContract::with_mcp`]: [`McpServer`]s spawned as child processes
//! ([`McpTransport::Stdio`]) or dialled over streamable HTTP
//! ([`McpTransport::Http`]), offered to the model under `mcp__<server>__<tool>`
//! so a server can never shadow a built-in. Either way registration makes a tool
//! *available*, not authorized: every call is an [`Act::Exec`] check on its
//! name. [`TaskContract::with_skills`] adds [`Skills`] — markdown instruction
//! files that shape how the agent approaches a class of task, loaded through
//! [`read_skill`](tools::READ_SKILL_TOOL) as an ordinary policy-checked read,
//! with no Rust at all.
//!
//! **Context that stays relevant.** Each turn is assembled to fit a stated share
//! of the token budget ([`ContextBudget`]): superseded observations are
//! compacted, and an observation a later write invalidated is re-read rather
//! than trusted. Durable memory keyed to the workspace ([`MemoryEntry`],
//! [`Store::memory_list`]) survives between runs.
//!
//! **Observation and replay.** Register an [`Observer`] and be called as the run
//! happens — [`RunEvent`]s covering steps, tool calls, approvals, refusals,
//! spend draws, retries, fallbacks and outcomes — instead of polling the store;
//! [`Flow`] lets an observer ask the run to stop. [`provider::Record`] captures
//! a case and [`provider::Replay`] runs it back identically.
//!
//!
//! **Configuration in a file.** [`Config::discover`] reads one `io.toml` across
//! four scopes — the crate's defaults, a user file, a committed project file, and
//! a gitignored local one — and projects it onto this API: a [`Policy`], a
//! [`SandboxConfig`], the run budgets applied through [`Config::apply_to`], the
//! [`toolchain`] commands, a [`pricing::PriceTable`], and [`McpServer`]s.
//! `${env:...}` and `${file:...}` keep a credential out of the file, an unknown
//! key is an error rather than a shrug, and nothing is loaded implicitly — the
//! caller reads the file, before the run, once, which is what stops an agent
//! widening the boundary it is running under.
//! **Documents and images**, behind opt-in features: spreadsheets, Word,
//! PowerPoint text, PDF, and barcode decoding, each gated on [`Act::Read`] or
//! [`Act::Write`] against the real path the model named, and verified with
//! [`Verification::DocumentContains`] rather than a container read as the empty
//! string; plus image passthrough to any provider whose model accepts one.
//!
//! **Git**, as fixed-argv built-ins: status, diff, log, add, and commit (under a
//! caller-supplied [`Identity`]), so a run ends as a reviewable commit rather
//! than a working tree someone has to reconstruct. The model supplies paths and
//! a message, never a subcommand or a flag, so push, fetch, reset and rebase are
//! unreachable by construction.
//!
//! What none of it governs: a stdio MCP server and a registered [`Tool`] both
//! run outside the sandbox with the privileges of whoever started them. The
//! harness decides what may *start* and what may be *called* — not what a
//! started thing then does.
//!
//! # Feature flags
//!
//! `default = []`. The default build compiles no optional dependency at all.
//!
//! | Feature | What it adds |
//! | --- | --- |
//! | `media` | Image passthrough to providers that accept images |
//! | `documents` | Umbrella over the five below |
//! | `xlsx` | Spreadsheet read, generate, and preserving single-cell edit |
//! | `docx` | Word read and generate (no in-place edit, deliberately) |
//! | `pptx` | PowerPoint text extraction (read-only, no writer) |
//! | `pdf` | PDF generate, extract text, watermark, fill AcroForm fields |
//! | `barcode` | Barcode and QR decoding from an image |
//!
//! # Minimum supported Rust
//!
//! **MSRV: Rust 1.88.** The floor comes from `rmcp`, which publishes no
//! `rust-version` of its own, so cargo cannot catch it at resolve time — on
//! 1.87 the build fails inside that dependency rather than here.
//!
//! # Platform support
//!
//! | Platform | Sandbox containment |
//! | --- | --- |
//! | macOS | Native, `sandbox-exec` |
//! | Linux | Native, namespaces and rlimits |
//! | Windows | Portable floor only |
//!
//! The portable floor is the honest floor: on Windows the
//! [`Backend::WindowsJobObject`] path was designed and never implemented, so a
//! Windows run reports [`Backend::PortableFloor`] and only the wall-clock cap
//! fires. [`Cap::Cpu`] and [`Cap::Memory`] rest on unix `rlimit` mechanisms with
//! no Windows equivalent, and there is no kernel network boundary there either.
//! The full suite runs on all three in CI.
//!
//! # Guides
//!
//! Longer prose than a doc comment should carry, one page per capability:
//!
//! - [Permissions and approval](https://github.com/initorigin/io-harness/blob/main/docs/guide/permissions.md)
//! - [Command execution](https://github.com/initorigin/io-harness/blob/main/docs/guide/command-execution.md)
//! - [Language support](https://github.com/initorigin/io-harness/blob/main/docs/guide/language-support.md)
//! - [Verification](https://github.com/initorigin/io-harness/blob/main/docs/guide/verification.md)
//! - [Agent composition](https://github.com/initorigin/io-harness/blob/main/docs/guide/composition.md)
//! - [Execution sandbox](https://github.com/initorigin/io-harness/blob/main/docs/guide/sandbox.md)
//! - [Durable runs](https://github.com/initorigin/io-harness/blob/main/docs/guide/durable-runs.md)
//! - [MCP and network egress](https://github.com/initorigin/io-harness/blob/main/docs/guide/mcp-and-network.md)
//! - [Tools and skills](https://github.com/initorigin/io-harness/blob/main/docs/guide/tools-and-skills.md)
//! - [Context and memory](https://github.com/initorigin/io-harness/blob/main/docs/guide/context-and-memory.md)
//! - [Resilience](https://github.com/initorigin/io-harness/blob/main/docs/guide/resilience.md)
//! - [Observability and replay](https://github.com/initorigin/io-harness/blob/main/docs/guide/observability.md)
//! - [Configuration](https://github.com/initorigin/io-harness/blob/main/docs/guide/configuration.md)
//! - [Accounting](https://github.com/initorigin/io-harness/blob/main/docs/guide/accounting.md)
//! - [Documents](https://github.com/initorigin/io-harness/blob/main/docs/guide/documents.md)
//! - [Images and git](https://github.com/initorigin/io-harness/blob/main/docs/guide/images-and-git.md)
//!
//! [The public contract](https://github.com/initorigin/io-harness/blob/main/docs/CONTRACT.md)
//! states what is stable, what may change, and the limits that hold today. The
//! crate is pre-1.0: a minor release may break the contract, and when it does it
//! is marked in
//! [CHANGELOG.md](https://github.com/initorigin/io-harness/blob/main/CHANGELOG.md)
//! with a migration note. That file is where the release history lives.
//!
// docs.rs builds with every feature on and sets the `docsrs` cfg (see
// Cargo.toml). This labels each gated item with the feature it needs, so a
// reader browsing the rendered docs is never shown an item that would not exist
// in their build without being told why. Nightly-only, and reached only under
// that cfg — a stable `cargo doc` is unaffected.
//
// `doc_cfg`, not `doc_auto_cfg`: the latter was removed in 1.92.0 (rust-lang
// PR 138907) and merged into `doc_cfg`, which now does the automatic labelling
// itself. 0.16.1's docs.rs build failed on the removed feature name.
pub use ;
pub use Config;
pub use ;
pub use ContextBudget;
pub use TaskContract;
pub use ;
pub use ;
// The `net` module itself stays private, so the default request deadline is
// surfaced here as well as from each provider module. A caller overriding it with
// `with_timeout` should be able to name the value they are overriding without
// reaching into a provider's namespace to find it.
pub use REQUEST_TIMEOUT;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
// Each entry point has an observed twin: a separate function rather than an extra
// parameter on the existing seven, so 0.11.0 code compiles unchanged against
// 0.12.0. The observer is this release's headline, not a reason to break every
// caller that does not want one.
pub use ;
pub use ;
pub use ;
// `AgentEvent` and `SpawnRow` were `pub` inside this private module but were not
// re-exported, so `Store::agent_events` and `Store::find_spawn` returned values an
// external caller could hold and could not name — which made `agent_events`, the
// only audit of per-step budget draws against the shared tree ledger, unreadable
// through the public API. Exported in 0.12.0: an observability release cannot ship
// leaving its own audit table reachable only by opening the SQLite file.
pub use ;
pub use Identity;
pub use ;
pub use ;