agent-reliability-ci 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,506 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-reliability-ci
3
+ Version: 0.8.0
4
+ Summary: Regression testing for stochastic AI agents: frozen experiments, fault injection, replayable reduced failures.
5
+ Project-URL: Homepage, https://github.com/ajaysurya1221/agent-reliability-ci
6
+ Project-URL: Source, https://github.com/ajaysurya1221/agent-reliability-ci
7
+ Project-URL: Changelog, https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/ajaysurya1221/agent-reliability-ci/issues
9
+ Project-URL: Design records, https://github.com/ajaysurya1221/agent-reliability-ci/tree/main/docs/design
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ License-File: NOTICE
13
+ Keywords: ai-agents,evaluation,fault-injection,reliability,reproducibility,statistics
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: pydantic<3,>=2.7
26
+ Requires-Dist: pyyaml<7,>=6
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Did this change make your agent less reliable?
30
+
31
+ ARCI (agent-reliability-ci) runs your baseline and candidate repeatedly under the same predeclared
32
+ tasks and injected tool faults, grades each run with an independent success check, returns `PASS`,
33
+ `BLOCK`, `INCONCLUSIVE` or `ERROR` with an exit code, and can replay a recorded failure offline.
34
+
35
+ Try it without cloning (seeded fixture, no model call):
36
+
37
+ ```bash
38
+ uvx --from agent-reliability-ci arci demo
39
+ ```
40
+
41
+ Replace these example references with your project's importable Python functions:
42
+
43
+ ```bash
44
+ uv tool install agent-reliability-ci && arci init checkout-retry --baseline app.v1:run --candidate app.v2:run --toolset tests.world:make_world --oracle tests.world:passed && arci run arci.yaml
45
+ ```
46
+
47
+ `baseline + candidate → same predeclared tasks/faults → repeated trials → verdict → minimise/replay`
48
+
49
+ **PASS means only this:** for this predeclared experiment, the evidence supports that any reliability
50
+ drop from baseline to candidate is smaller than the allowed drop. It is **not** a claim of absolute
51
+ reliability.
52
+
53
+ A small first run will often be **INCONCLUSIVE**. That is a valid result; use `arci plan` before
54
+ choosing a larger confirmatory run.
55
+
56
+ ARCI observes calls that cross its tool boundary. It is not a sandbox, it does not replay model
57
+ inference, and trace divergence shows where runs differ, not why.
58
+
59
+ ## Try the offline demo
60
+
61
+ Python 3.11+, POSIX and [uv](https://docs.astral.sh/uv/). Installation needs network; the fixture makes
62
+ no model calls.
63
+
64
+ ```bash
65
+ uvx --from agent-reliability-ci arci demo
66
+ ```
67
+
68
+ `arci demo` runs a packaged copy of the seeded inventory fixture (`arci.demo`): the same world, agents,
69
+ seeds and sample size as the recorded run below, so it computes **192/200** baseline successes and
70
+ **132/200** retry-removed successes fresh on your machine and says whether they matched the recorded
71
+ run. It is a reproduction, not the recorded evidence. The six steps go through the real CLI and the
72
+ Python API, each trial in a fresh process with a separate grader process. Runtime depends on the
73
+ machine and current load (measured: 38 s on a 15-core Apple Silicon Mac with 8 workers; 72 s through
74
+ `uvx` from a local wheel including environment creation; CI runners are slower). `just demo` runs the checkout script
75
+ `examples/retry_agent/hero_demo.py`, the same six steps. Lines from the recorded run:
76
+
77
+ ```text
78
+ === 2. The frozen experiment, N=200 per arm, fault: tool_timeout on reserve
79
+ VERDICT: BLOCK (exit 1) [A vs agent_b]
80
+ VERDICT: PASS (exit 0) [A vs agent_a]
81
+
82
+ === 3. Where do a passing and a failing run part ways?
83
+ left: tool_start tool="reserve" arguments={"order_id":"order-7","quantity":2,"sku":"widget"}
84
+ right: tool_start tool="confirm" arguments={"order_id":"order-7","sku":"widget"}
85
+ after_injection: tool_timeout
86
+
87
+ === 4. Shrink the failing condition (3 injected faults)
88
+ kept: tool_timeout
89
+ removed: empty_result, tool_error_once
90
+ minimality: 1-minimal
91
+
92
+ === 5. Replay the reduced bundle offline
93
+ REPRODUCED: failure reproduced
94
+ ```
95
+
96
+ Copied from the unedited, committed log
97
+ [hero-demo-2026-10-05.txt](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/logs/hero-demo-2026-10-05.txt), without
98
+ the `ok` lines. Step 1 passes both agents on one clean run; step 6 passes the repaired agent C on
99
+ the exact reproducer and on its own frozen experiment; the run ends `All six steps held`, exit 0.
100
+ The experiment is also a frozen acceptance test ([test_hero.py](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/tests/acceptance/test_hero.py)).
101
+
102
+ Agent B is not rigged with dice. It is agent A with the retry around `reserve` removed, a plausible
103
+ refactoring slip: when `reserve` times out it carries on to `confirm` and still reports success. Its
104
+ failure rate comes from the environment. Reservations already exist with configured probability
105
+ 65% (135 of the default 200 seeds, plus 8 scenarios with a naturally flaky `confirm`; shares set in
106
+ [world.py](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/src/arci/demo/world.py), counts asserted in
107
+ [test_retry_example.py](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/tests/unit/examples/test_retry_example.py)), and in those the missing retry
108
+ never matters. That is exactly why one run hides it.
109
+
110
+ ### Recorded result and evidence
111
+
112
+ **Recorded fixture: 200 trials per arm; timeout on `reserve`.**
113
+ Baseline: **192/200**. Retry removed: **132/200**. Verdict: **BLOCK (exit 1)**.
114
+ [Recorded results](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/retry-demo-n200.md) · [Trace, reduction and replay](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/logs/hero-demo-2026-10-05.txt)
115
+
116
+ **Evidence:** [18 archived decisions re-derived](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/REPORT.md) · [Frozen contracts](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/FROZEN.sha256) · [Design records](https://github.com/ajaysurya1221/agent-reliability-ci/tree/main/docs/design) · [CI and macOS statistics checks](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/.github/workflows/ci.yml)
117
+
118
+ <picture>
119
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/assets/hero-dark.svg">
120
+ <img src="https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/assets/hero-light.svg" alt="arci, agent-reliability-ci: Did this change make the agent less reliable, and what broke? A frozen experiment, an exact verdict with its uncertainty, and a failure case you can replay offline. Seeded retry demo, 200 trials per arm, timeout on the first reserve call: A 192/200, B 132/200; bounds on the difference minus 0.405 to minus 0.183 against a margin of minus 0.10; VERDICT BLOCK, exit 1; 3 injected faults reduced to 1, 1-minimal; offline replay REPRODUCED. B is A with one retry removed. Source: docs/results/retry-demo-n200.md.">
121
+ </picture>
122
+
123
+ Re-derived means each archived `decision.json` regenerates byte for byte from its committed
124
+ inputs, whatever its verdict: reproducing a BLOCK, INCONCLUSIVE or ERROR counts, and no archived
125
+ run is turned green ([reproduction recipe](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/reproduction/README.md),
126
+ [CITATION.cff](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/CITATION.cff)).
127
+
128
+ > **Review a retry-policy change before merging**
129
+ >
130
+ > In the recorded inventory-reservation fixture, the candidate loses its retry after a timeout. ARCI reports BLOCK, locates the trace divergence, reduces three injected faults to one, and reproduces the failure offline. The repaired candidate passes the reproducer and its own separately frozen experiment.
131
+ >
132
+ > Scaffold your own experiment with `arci init` (below), or start from the example’s manifest builder. Define the oracle, fault conditions, margin and sample size before running. A small experiment may remain INCONCLUSIVE.
133
+
134
+ Manifest builder of the recorded run: [examples/retry_agent/experiment.py](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/examples/retry_agent/experiment.py).
135
+
136
+ ## Verdicts and exit codes
137
+
138
+ Every gating command ends in a verdict and an exit code. Nothing else decides.
139
+
140
+ | Verdict | Exit | Meaning |
141
+ |---|---:|---|
142
+ | `PASS` | 0 | The candidate is non-inferior to the baseline within `delta`. Nothing about absolute reliability. |
143
+ | `BLOCK` | 1 | The candidate is worse by more than `delta`, or it broke a hard invariant. |
144
+ | `INCONCLUSIVE` | 2 | Not enough evidence either way. CI stays non-green. No regression is claimed. |
145
+ | `ERROR` | 3 | The experiment is invalid: a harness or grader fault, a missing, duplicated or foreign trial, a broken seal. It can never pass. |
146
+
147
+ An agent crash, timeout, blown budget or uncaught tool fault is a `FAIL`. A broken environment,
148
+ injector, grader or event sink is an `ERROR`, and it invalidates the experiment instead of quietly
149
+ leaving the denominator. `arci replay` uses its own codes: 0 reproduced, 1 not reproduced, 3 invalid.
150
+
151
+ ## How it works
152
+
153
+ <picture>
154
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/assets/where-dark.svg">
155
+ <img src="https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/assets/where-light.svg" alt="Where arci sits. Before the run, a frozen manifest declares the baseline and candidate, oracle, faults, seeds, N and the decision rule. In CI, the recorded trials (seeded trials per arm, injected tool faults, sealed boundary records) feed the arci gate, which computes exact Clopper-Pearson bounds on the difference against a frozen margin and returns PASS, BLOCK, INCONCLUSIVE or ERROR with exit 0, 1, 2 or 3; merge on PASS, while BLOCK and ERROR fail the job and INCONCLUSIVE fails by default. A separate branch reads the same records for any verdict: trace comparison (the first divergent step between a passing and a failing trial), fault reduction (ddmin over injected faults, same failure fingerprint) and offline replay of the recorded boundary calls, not fresh model inference.">
156
+ </picture>
157
+
158
+ In words: the manifest (task, toolset, oracle, contract, both arms, fault conditions, `alpha`,
159
+ `delta`, `n_per_arm`, seeds) is sealed before the run. Each trial runs as a Python worker or a
160
+ command agent behind an MCP boundary, with a separate grader process; the tool boundary applies
161
+ budgets and seeded faults and records every call into sealed `trials.jsonl`. The gate is a pure
162
+ function of manifest and trials. Diagnosis reads the same records, whatever the verdict: `diff`
163
+ (first divergence), `minimize` (ddmin over injected faults, same failure fingerprint), `bundle`
164
+ and `replay` (re-serves recorded boundary calls: REPRODUCED, NOT_REPRODUCED or INVALID).
165
+
166
+ A Python agent is a plain function, `agent(task, tools, rng) -> dict`. It calls `tools.call("name", ...)`
167
+ and may annotate its reasoning with `tools.note_model_step(...)`. Success is decided by an oracle
168
+ over the environment's final state, never by what the agent says about itself.
169
+
170
+ **A gate that can say "I do not know".** The rule is fixed-sample and deliberately boring
171
+ ([docs/STATISTICS.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/STATISTICS.md)): per arm, an exact Clopper-Pearson interval at tail
172
+ `alpha / (4K)`; bounds on the difference by Bonferroni; `PASS` iff the lower bound is above
173
+ `-delta`, `BLOCK` iff the upper bound is below it. No peeking, no extending a run, no rerunning
174
+ until green. The decision rule is bound into every trial's identity, so a manifest resealed after
175
+ the run with a different rule no longer matches its trials and the gate returns `ERROR`: choosing
176
+ the rule after the data is detected, not merely forbidden. Earlier runs on the same candidate are
177
+ listed in `prior_runs` and printed in the decision and the Markdown report; `arci` does not
178
+ discover them or enforce a cross-run error budget for you. Wilson intervals are shown for
179
+ readability and never decide.
180
+
181
+ **Command agents.** An agent does not have to be a Python function: it can be any program
182
+ that speaks MCP over stdio. `arci` owns the one MCP server behind it, so every tool call is
183
+ recorded, budgeted, fault-injected and replayable from outside the agent's process.
184
+
185
+ ```text
186
+ your agent (any argv) --> arci.mcp_shim --> arci.mcp_boundary --> MCP server (the environment)
187
+ byte relay recorder, faults, yours, or any python toolset via
188
+ budgets, latches python -m arci.mcp_toolset_server
189
+ ```
190
+
191
+ [docs/REAL_AGENTS.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/REAL_AGENTS.md) has the recipe and the local-model worked example.
192
+
193
+ **Decision calls.** A command agent may call a System One decision endpoint (TypeSafe's Jev
194
+ API). The same boundary serves `POST /v1/systemone` on loopback, records each admitted request as
195
+ a tool call named `decision:systemone`, and applies budgets, perturbations, diff, minimisation,
196
+ bundles and exact replay to it. The agent is redirected with `TYPESAFE_BASE_URL` and a per-trial token
197
+ while any real key stays in the harness; the official Python and JavaScript SDKs are exercised
198
+ unchanged by the acceptance tests. `arci preflight` checks key, endpoint, pinned model and one
199
+ smoke decision before a live run.
200
+
201
+ ![The agent process, any SDK unchanged, reads two environment variables that point at a loopback port and a per-trial token. The harness-owned boundary serves the MCP tools and the decision endpoint, records every request and answer, budgets them and injects decision_low_confidence or decision_unavailable on schedule. The upstream is a seeded fixture in CI or the real API with the key held by the harness. Sealed records keep the served answer and the raw upstream answer, never the key, and feed the same gate, replay, preflight and report. Provenance: built from the vendor's documentation and both official SDKs, dated 2026-09-22; run against Jev on 2026-10-03, see docs/results/jev.](https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/images/decision-boundary.png)
202
+
203
+ **Run against Jev on 2026-10-03.** Built from documentation dated 2026-09-22, the boundary ran the
204
+ pre-registered [day one](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/design/0004-day-one.md) live. Four things did not survive contact
205
+ and are fixed: alias-only model lists (`arci preflight --allow-unlisted-model`), smoke questions
206
+ needing `instructions`, CONNECT-only egress (`ARCI_HTTPS_PROXY`), and the console script needing
207
+ the repository root on `PYTHONPATH`. Prices and provider limits are still as documented, not
208
+ measured, and the gateway route is still untested. See [docs/DECISIONS.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/DECISIONS.md).
209
+
210
+ ## Where it has been run
211
+
212
+ All runs are the maintainer's own; no external users yet. Failed and invalid attempts stay in
213
+ the linked records.
214
+
215
+ - **Seeded retry demo** (2026-09-23): the result above; A against itself and the repaired C both
216
+ PASS ([retry-demo-n200.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/retry-demo-n200.md)).
217
+ - **Support-triage agent, live Jev endpoint** (2026-10-03, `jev-1.13.0` pinned, Python and Node
218
+ SDKs unchanged; planted regression, the harness caps confidence and B stops escalating): A vs B
219
+ at N=200, 200/200 vs 0/200, bounds [-1.000, -0.957], BLOCK; one minimised failure replays
220
+ REPRODUCED; 902 recorded decisions ([docs/results/jev](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/jev/README.md)). Offline
221
+ against a seeded fixture: `.venv/bin/python examples/jev_triage_agent/hero_demo.py --n 50`.
222
+ - **The tool-call guard of the maintainer's other project, frontier-scout** (2026-10-05,
223
+ `hook_runtime.py` verbatim at `58b0b17`, run through the harness boundary, not inside a
224
+ coding-agent session; outages injected): fail-closed against fail-open under
225
+ `decision_unavailable` at N=200, 200/200 vs 130/200, bounds [-0.4304, -0.2537], BLOCK; diffed,
226
+ minimised (`reduced`) and replayed REPRODUCED ([docs/results/guardrail](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/guardrail/README.md)).
227
+ - **A local model, one sentence of prompt** (`qwen3.5:4b-mlx` through Ollama, one machine,
228
+ exploratory): N=400, 376/400 vs 308/400, bounds [-0.2445, -0.0921], INCONCLUSIVE; BLOCK needed
229
+ the upper bound below -0.10 and missed by 0.008. Not re-run until it blocks
230
+ ([docs/results](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/README.md)).
231
+
232
+ **What went wrong and was kept.**
233
+
234
+ - Ollama run 1: a harness bug (tools advertised with no parameters), found from the failure
235
+ clusters ([ollama-run1-n30-bridge-bug.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/ollama-run1-n30-bridge-bug.md)). Run 3:
236
+ the model server died and one ERROR trial invalidated the run
237
+ ([ollama-run3-n400-invalid.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/ollama-run3-n400-invalid.md)); hence
238
+ `infra_exit_codes` (75 by default), which turns an agent's own infrastructure failure into ERROR.
239
+ - Jev attempt 1: every trial crashed on an import error, so equally broken arms "passed"; the
240
+ invalid stores are kept in [attempt-1-invalid](https://github.com/ajaysurya1221/agent-reliability-ci/tree/main/docs/results/jev/2026-10-03/attempt-1-invalid/).
241
+ - Guardrail at N=50: 50/50 vs 38/50, bounds [-0.4016, -0.0348], INCONCLUSIVE against the
242
+ pre-registered BLOCK, reported as is ([docs/results/guardrail](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/guardrail/README.md)).
243
+
244
+ **Is the model's confidence something you can gate on?** A separate, pre-registered audit of
245
+ `jev-1.13.0` on CLINC150 and Banking77 answers that for intent routing, not permission
246
+ calibration: zero-shot accuracy 0.921 with ECE 0.024 on CLINC150, and 0.801 with ECE 0.084 on
247
+ Banking77, where confidence is over-stated in the middle of the range. Write-up, full table and
248
+ limits: [docs/results/jev-calibration](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/results/jev-calibration/README.md); protocol and code:
249
+ [bench/jev_calibration](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/bench/jev_calibration/PROTOCOL.md).
250
+
251
+ ## The gate, measured against itself
252
+
253
+ `just selfcheck` (or `.venv/bin/python bench/selfcheck.py`) computes the gate's own operating
254
+ characteristics by exact enumeration (`alpha` 0.05, `delta` 0.10, one condition). Probabilities of
255
+ PASS / BLOCK / INCONCLUSIVE:
256
+
257
+ | baseline -> candidate | N=20 | N=100 | N=200 | N=400 |
258
+ |---|---|---|---|---|
259
+ | 0.95 -> 0.95 | .000 / .000 / 1.000 | .443 / .000 / .557 | .889 / .000 / .111 | .999 / .000 / .001 |
260
+ | 0.95 -> 0.85 (the margin) | .000 / .000 / 1.000 | .001 / .000 / .999 | .001 / .000 / .999 | .001 / .001 / .999 |
261
+ | 0.95 -> 0.75 | .000 / .000 / 1.000 | .000 / .093 / .907 | .000 / .365 / .635 | .000 / .828 / .172 |
262
+ | 0.95 -> 0.65 | .000 / .008 / .992 | .000 / .685 / .315 | .000 / .985 / .015 | .000 / 1.000 / .000 |
263
+ | 0.80 -> 0.80 | .003 / .000 / .997 | .059 / .000 / .941 | .218 / .000 / .782 | .615 / .000 / .385 |
264
+
265
+ ![Verdict probabilities by exact enumeration for the default gate: a 95 to 65 percent drop is INCONCLUSIVE 99.2 percent of the time at N=20 and BLOCK 98.5 percent at N=200; a 95 to 75 percent drop is BLOCK 36.5 percent at N=200; two equal 80 percent agents PASS 21.8 percent at N=200.](https://raw.githubusercontent.com/ajaysurya1221/agent-reliability-ci/main/docs/images/calibration.png)
266
+
267
+ - False PASS at the margin and false BLOCK under no change are both far below `alpha` (worst
268
+ 0.004 and 0.001 over the boundary sweep). The price is conservatism: two equal agents at 80%
269
+ reach PASS only 21.8% of the time at N=200.
270
+ - **Twenty runs decide almost nothing.** At N=20 a 95% -> 65% collapse is still INCONCLUSIVE 99.2%
271
+ of the time, and showing a failure rate below 0.5% needs at least 598 clean trials. A
272
+ twenty-point drop from 0.95 to 0.75 BLOCKs only 36.5% of the time at N=200.
273
+ - `interval_method: newcombe` (about twice the power, calibrated by exact enumeration rather than
274
+ proved) and pre-registered `looks` must be frozen before the run; `arci plan` shows a design's
275
+ verdict probabilities before you spend trials. Details: [docs/STATISTICS.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/STATISTICS.md),
276
+ [report section 3](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/REPORT.md#3-operating-characteristics).
277
+
278
+ ## Gate your own agent
279
+
280
+ Scaffold one experiment from four references, then run it:
281
+
282
+ ```bash
283
+ uv tool install agent-reliability-ci
284
+ arci init checkout-retry --baseline app.v1:run --candidate app.v2:run --toolset tests.world:make_world --oracle tests.world:passed
285
+ arci run arci.yaml
286
+ ```
287
+
288
+ `arci init` imports the four references once (in a child process, as a trial would), checks that every
289
+ fault names a tool the toolset provides, and writes `arci.yaml`: a thin, schema-checked descriptor that
290
+ `arci run` seals into the same JSON manifest the Python builder produces. The scaffold is a **smoke
291
+ design** (8 trials per arm): under the default rule two perfect arms cannot PASS below N=42 and a
292
+ total collapse cannot BLOCK below N=8, so expect INCONCLUSIVE, then plan a confirmatory N with
293
+ `arci plan` before spending trials. Without `--fault` the scaffold writes one clean condition (no
294
+ injected fault); `--fault tool_timeout:reserve` adds a timeout on the first `reserve` call, and `--task '{...}'`
295
+ sets the task. A hand-written descriptor with one fault looks like this:
296
+
297
+ ```yaml
298
+ experiment_id: checkout-retry
299
+ task: {order_id: "order-7"}
300
+ toolset: tests.world:make_world
301
+ oracle: tests.world:passed
302
+ baseline: app.v1:run
303
+ candidate: app.v2:run
304
+ conditions:
305
+ - id: reserve-timeout
306
+ fault: {name: tool_timeout, tool: reserve, occurrence: 0}
307
+ alpha: 0.05
308
+ delta: 0.10
309
+ n_per_arm: 8
310
+ ```
311
+
312
+ The descriptor expands documented shorthands (`id`, `fault`, `occurrence`, `oracle`) into a sealed
313
+ manifest; the key reference lists its defaults. `arci schema` prints the JSON Schema, and [docs/DESCRIPTOR.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/DESCRIPTOR.md)
314
+ has the key reference, command-agent experiments and the YAML quoting rule. The toolset is `(task, seed) -> object with .tools and .snapshot()`, the
315
+ oracle `(task, final_state) -> bool`, and each agent a plain function `agent(task, tools, rng) -> dict`.
316
+
317
+ ### Advanced: the Python manifest builder
318
+
319
+ A manifest is a sealed JSON document built in Python. The retry example's builder,
320
+ [examples/retry_agent/experiment.py](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/examples/retry_agent/experiment.py), is the template; its shape:
321
+
322
+ ```python
323
+ Manifest.create(
324
+ experiment_id="retry-agent-agent_b-reserve-timeout-200",
325
+ task_id="confirm-inventory-order",
326
+ task={"order_id": "order-7", "sku": "widget", "quantity": 2, "stock": 10},
327
+ toolset="examples.retry_agent.world:make_world", # (task, seed) -> tools + snapshot
328
+ contract=ContractSpec(
329
+ oracle="examples.retry_agent.world:oracle"
330
+ ), # (task, final_state) -> bool
331
+ baseline=ArmSpec(label="agent_a", agent="examples.retry_agent.agents:agent_a"),
332
+ candidate=ArmSpec(label="agent_b", agent="examples.retry_agent.agents:agent_b"),
333
+ conditions=(
334
+ Condition(
335
+ condition_id="reserve-timeout",
336
+ faults=(
337
+ FaultSpec(
338
+ name="tool_timeout", bucket=Bucket.FALSIFY, tool="reserve", at_occurrence=0
339
+ ),
340
+ ),
341
+ ),
342
+ ),
343
+ n_per_arm=200,
344
+ base_seed=12_000,
345
+ budgets=Budgets(max_tool_calls=10, max_model_steps=10, max_seconds=15.0),
346
+ )
347
+ ```
348
+
349
+ A behaviour contract names the oracle plus the invariants the tool boundary can observe
350
+ (`max_tool_calls`, `forbidden_tools`, `required_tools`, `max_calls_per_tool`). A contract that asks
351
+ for something the boundary cannot see is rejected, not silently skipped. Fault conditions are tagged
352
+ `falsify` (a robust agent should survive), `benign` (must not change the outcome) or `ceiling`
353
+ (known-unrecoverable: reported, never gated on rates). Built in: `tool_timeout`, `tool_error_once`
354
+ and `empty_result` at the tool boundary, plus `decision_low_confidence` and `decision_unavailable`
355
+ at the decision boundary; a `"module:function"` reference plugs in your own. For a command agent,
356
+ replace `toolset` with `mcp_server` and the arms' `agent` with `command`, as
357
+ [docs/REAL_AGENTS.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/REAL_AGENTS.md) shows.
358
+
359
+ ```bash
360
+ arci init NAME --baseline M:F --candidate M:F --toolset M:F --oracle M:F # write arci.yaml (smoke N=8)
361
+ arci schema # the descriptor's JSON Schema, generated from code
362
+ arci demo # the packaged six-step retry story, no clone
363
+ arci plan --help # verdict probabilities of a design, before any trial
364
+ arci preflight manifest.json # live decision upstreams: key, model, smoke call
365
+ arci run arci.yaml --workers 8 # run + gate; exit code is the verdict (--out defaults to runs)
366
+ arci gate runs/<experiment> --junit junit.xml --markdown report.md
367
+ arci report runs/<experiment> # gate, failure clusters, tokens, estimated cost
368
+ arci diff runs/<experiment> <passing_trial> <failing_trial> # --all-steps to include annotations
369
+ arci minimize runs/<experiment> <failing_trial> --out min.json
370
+ arci bundle runs/<experiment> <failing_trial> --out bundle.json --root . --include my_agent.py
371
+ arci replay bundle.json # 0 reproduced, 1 not reproduced, 3 invalid
372
+ ```
373
+
374
+ A run store is one directory per experiment with the sealed manifest, `events.jsonl` and
375
+ `trials.jsonl`. Keep it with the evidence it supports; it can hold the agent's full task state and,
376
+ for decision calls, the full request state, so treat stores and bundles as sensitive data. Records
377
+ sealed by an earlier schema version are not readable after an upgrade (v0.2 and v0.5 changed the
378
+ schema); re-run the experiment rather than trust an unreadable store.
379
+
380
+ ### GitHub Actions
381
+
382
+ This example pins `v0.8.0`, the released feature set (the Action installs that tag's source; the
383
+ same code is on PyPI as `agent-reliability-ci==0.8.0`). See
384
+ [CHANGELOG.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/CHANGELOG.md).
385
+
386
+ ```yaml
387
+ - uses: actions/setup-python@v7
388
+ with: { python-version: "3.12" }
389
+ - run: pip install -e . # your agent, importable
390
+ - uses: ajaysurya1221/agent-reliability-ci@v0.8.0
391
+ id: gate
392
+ with:
393
+ manifest: reliability/manifest.json
394
+ workers: "4" # out: arci-runs by default
395
+ - run: echo "verdict=${{ steps.gate.outputs.verdict }}"
396
+ ```
397
+
398
+ This assumes your agent's repository is checked out and installed, and that you pin action versions
399
+ you have verified. The report lands in the job summary (a start-up failure shows only in the step's
400
+ stderr). `BLOCK` and `ERROR` always fail the job; `INCONCLUSIVE` fails it unless you set
401
+ `allow-inconclusive: "true"`. The `verdict` output (`PASS`, `BLOCK`, `INCONCLUSIVE` or `ERROR`) is
402
+ there for steps that run after the gate. This repository's own CI runs the test suite,
403
+ re-derives the archived decisions, smoke-tests the built wheel on Linux and macOS, and runs the Action
404
+ on a committed fixture that must BLOCK.
405
+
406
+ ## What it does not do
407
+
408
+ - **It is not a sandbox and not a security boundary.** Python agents share a process with their tool
409
+ boundary. Command agents use a separate harness-owned MCP boundary, which is isolation from
410
+ accident, not containment. Both assume buggy, non-hostile agent code; a hostile agent could still
411
+ forge or bypass its record. See [docs/TRUST_MODEL.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/TRUST_MODEL.md).
412
+ - **It only sees the tool boundary.** Files, network and subprocesses the agent touches directly are
413
+ invisible, as is any decision call that does not go through the injected base URL.
414
+ - **It replays the boundary, not the model.** Deterministic replay is guaranteed for seeded,
415
+ fixture-backed agents. A live model will not repeat itself token for token, so replaying its
416
+ bundle is usually INVALID; what survives is the recorded trace and the reduced fault condition.
417
+ - **INCONCLUSIVE is common at small N.** That is the honest answer, not a failure of the run: at
418
+ N=20 even a 95% -> 65% collapse is INCONCLUSIVE 99.2% of the time, and one real 400-trial run
419
+ missed BLOCK by 0.008 and stayed INCONCLUSIVE.
420
+ - **A bundle embeds code, and replaying it runs that code.** Hashes are integrity checks, not
421
+ signatures. Replay bundles only from sources you trust. A bundle embeds only the files you
422
+ `--include` (the minimiser's output embeds none), so portable replay needs the referenced code and
423
+ a compatible environment.
424
+ - **A divergence is evidence, not proof of cause.** It shows where two runs part ways.
425
+ - **The decision boundary has been run against one vendor model, on one day, on three seeded
426
+ tickets.** The live model answered every clean decision with confidence 1.0, so the runs in
427
+ `docs/results/jev` show the harness, the pacing, both SDKs and the gate working end to end and the
428
+ planted regression caught; they say nothing about the model's calibration or about ambiguous
429
+ tickets. Live answers are sampled, so live minimisation is reported as `reduced`, never
430
+ `1-minimal`, and a decision model's confidence never enters a verdict except through the agent's
431
+ actions.
432
+ - **Reference validation executes user code.** `arci init` validates Python references by importing
433
+ them, and calls the toolset factory once, in a child process; import-time code runs.
434
+ - **Declaring a fault does not guarantee injection.** A fault names a tool; if the agent never calls
435
+ that tool at that occurrence, the condition runs clean and nothing says so. Inspect the recorded
436
+ calls to confirm injection.
437
+ - Command agents get one stdio MCP server (revision 2026-07-28), serial tool calls, no HTTP
438
+ transport, no concurrent decisions.
439
+ - No importers (OTLP, Claude Code, Codex), no HTML report yet. POSIX only: on Windows the package
440
+ installs and `arci --help` runs, but experiments are unsupported.
441
+
442
+ **Related work:** [Documentation comparison, checked 2026-10-05; not a matched benchmark.](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/REPORT.md#6-related-work-and-honest-positioning)
443
+
444
+ **Independence.** ARCI's job is preregistered stochastic regression testing. The manifest is a
445
+ prospective contract supplied by its author: it fixes the task, fault conditions, sample size and
446
+ decision rule before the trials run, and a rule changed after the fact is detected, not merely
447
+ forbidden. It does not independently authenticate when the manifest was written or that every earlier
448
+ run was disclosed; that discipline stays with the author. A `PASS` reports relative non-inferiority
449
+ against the baseline within the registered margin under those conditions, never absolute reliability
450
+ or a general quality score; `BLOCK`, `INCONCLUSIVE` and `ERROR` are defined above. ARCI is one of the
451
+ maintainer's independent projects alongside the earlier
452
+ [evalopt-graph](https://github.com/ajaysurya1221/evalopt-graph) experiment, whose standalone feature
453
+ development has ended. ARCI declares no package or runtime dependency on evalopt and imports nothing
454
+ from it, and it shares no runtime layer with the maintainer's sibling projects Actseal, Dorian and
455
+ Frontier Scout. [NOTICE](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/NOTICE) records two adaptations from evalopt under MIT with attribution: the
456
+ canonical JSON hashing helpers in `src/arci/hashing.py` and the self-sealing decision pattern in
457
+ `src/arci/schema.py`, both attributed source adaptation inside the frozen manifest, not a dependency.
458
+ ARCI is not a successor to, conformance target for or drop-in replacement for evalopt, and nothing
459
+ here claims adoption, performance or superiority.
460
+
461
+ ## Roadmap
462
+
463
+ Most valuable first: per-request pacing through parent IPC; counterfactual replay of recorded
464
+ decisions, if it can be done without overstating what a re-perturbed recording proves; extending
465
+ the intent-routing calibration audit to labelled coding-agent permission decisions, with a
466
+ separate protocol and explicit limits. Full list in [docs/ROADMAP.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/ROADMAP.md).
467
+
468
+ ## Development
469
+
470
+ **How it was built.** One maintainer, September to October 2026, with Claude Code and Codex as
471
+ pair-programmers (recorded in the commit messages). The experiment design, the decision rule and
472
+ its calibration, the trust model and the acceptance tests are the maintainer's own; the retry
473
+ demo, the calibration table and the test suite reproduce from a clean clone. Issues and PRs welcome.
474
+
475
+ ```bash
476
+ uv sync
477
+ just check # ruff format --check, ruff, basedpyright strict, pytest
478
+ just frozen # the frozen contract files are unchanged
479
+ just selfcheck # the calibration table above
480
+ just demo
481
+ npm ci --prefix examples/jev_triage_agent # only for the Node agent and its test (Node 20+)
482
+ python docs/assets/src/make_figures.py --check # the README figures match their generator
483
+ ```
484
+
485
+ The acceptance suite is hash-pinned in `FROZEN.sha256`. CI checks formatting, lint, strict typing
486
+ and tests on Python 3.11 and 3.14, with separate macOS statistics checks. See
487
+ [the workflow](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/.github/workflows/ci.yml) for coverage and
488
+ [the evidence report](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/docs/reports/ci-gate-2026-10-06/REPORT.md) for measured results. Design
489
+ records live in [docs/design](https://github.com/ajaysurya1221/agent-reliability-ci/tree/main/docs/design/), the changelog in [CHANGELOG.md](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/CHANGELOG.md).
490
+
491
+ ### Availability and project status
492
+
493
+ **Availability:** v0.8.0 is on PyPI as `agent-reliability-ci` and as the Action tag `v0.8.0`; it
494
+ adds the packaged demo (`arci demo`), the descriptor (`arci.yaml`, `arci schema`), `arci init`, the
495
+ planner (`arci plan`), opt-in pass^k reporting (`arci report --pass-k`) and portability fixes over
496
+ v0.7.0. **Boundary:** PASS means relative non-inferiority, not absolute reliability. This is not a
497
+ sandbox.
498
+
499
+ [![CI](https://img.shields.io/github/actions/workflow/status/ajaysurya1221/agent-reliability-ci/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/ajaysurya1221/agent-reliability-ci/actions/workflows/ci.yml)
500
+ [![Release](https://img.shields.io/github/v/release/ajaysurya1221/agent-reliability-ci?sort=semver&style=flat-square)](https://github.com/ajaysurya1221/agent-reliability-ci/releases/tag/v0.8.0)
501
+ [![Evidence package](https://img.shields.io/badge/evidence_package-2026--10--06-555?style=flat-square)](https://github.com/ajaysurya1221/agent-reliability-ci/releases/tag/evidence-2026-10-06)
502
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/LICENSE)
503
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue?style=flat-square)](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/pyproject.toml)
504
+ [Security policy](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/SECURITY.md)
505
+
506
+ [Apache-2.0](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/LICENSE). See [NOTICE](https://github.com/ajaysurya1221/agent-reliability-ci/blob/main/NOTICE) for adapted code.
@@ -0,0 +1,40 @@
1
+ arci/__init__.py,sha256=147fAPiBqNlvDat8cKcagr8X7PFNoh8kq8se8m4jEJg,96
2
+ arci/__main__.py,sha256=hj411yFeJie6ShpC-MfAlKStBnZpBp3iunbmcRjFnks,52
3
+ arci/cli.py,sha256=rEPzytPgv-7kvvXow4Kuh4lOlyz5jco4q15-2utP7Mc,31837
4
+ arci/contracts.py,sha256=DZZJHrfFMFtFOAE8fQtN8j4nCvMEavM2GmKW9hvUj2I,3798
5
+ arci/descriptor.py,sha256=6MbJiYXnwHHkbPl1rtNZhza8GajJ1lQd066SHkR2UQg,41886
6
+ arci/diff.py,sha256=cR6j2T9--kiq36cmeDh29EnLrGrPoQ92uxiieV_NSMQ,6425
7
+ arci/fingerprint.py,sha256=CUJV40UKFxuPAGzN1uJjxZufc_9ORg-BN9pFBlGJd4I,1222
8
+ arci/gate.py,sha256=okZU-LGK4scss6tfr4TozLXUDsUePf_NjkrJzeazTfg,14632
9
+ arci/grade.py,sha256=YDy8NQNN61RtQQ6gVYkbt6PtTZxplY2cZOP-fXwDGd0,2907
10
+ arci/hashing.py,sha256=Iw2EfoDBvgXJDgaSHiwCIgFjkKUjVl5pwdqDLhpiz7M,906
11
+ arci/init.py,sha256=EA6POR6wYD5uCRzmLCDOouSgj1fkNts4ldDSgJm2k18,24638
12
+ arci/interfaces.py,sha256=QWYFeJOaHgC8Z02CnA4buJ5-93gFSFlYC5K8JA3dBSU,6338
13
+ arci/mcp_boundary.py,sha256=eyj2LnIrSF8NwxuaTGHCwA-ITFNQuv0WS8H7HsLUP-I,85308
14
+ arci/mcp_shim.py,sha256=4L-HjnfNx5RN9zGSfjeAQ_hATzWU5g0UItNX69lI884,1800
15
+ arci/mcp_toolset_server.py,sha256=lHb5CgkJkYGBYwc507Fxp6N5r0OmfcEIund-OAQ6ddY,9869
16
+ arci/minimize.py,sha256=dqadc0fr8rAgds2IRtrrgVRmex-995bhPOaF1BdoljE,5057
17
+ arci/perturb.py,sha256=JjTGJJCW8SMI-n9oB4OsTOUU_ueCEejYwkrO0MsPBf8,7963
18
+ arci/planning.py,sha256=evHVNNK64bwln-I7Db_zWme91TX4mm8iEDD8L-YNTgM,21283
19
+ arci/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ arci/reliability.py,sha256=g9fRG4iTTNEUQQu4QO9Xv3Crk_tgB-TDVR0cXBcI-9Y,4498
21
+ arci/replay.py,sha256=zbhWx6IicuZ9Ynf6IQDOfO6wYsPOHF6REBzvitRiYcY,4722
22
+ arci/report.py,sha256=Yqa-xNsn77NETrRqYx_UBYol7o_WP83_-YzyNf9z_kM,13886
23
+ arci/runner.py,sha256=abUVfidq1905xMfu0pKxtSRtDZaVOrqB8NmOwv_-dIg,64220
24
+ arci/schedule.py,sha256=S9sTrxH_s7M_XjVLQ8we6HyErOdpSO0VS3c3-AyJsvw,2387
25
+ arci/schema.py,sha256=1wCMzFGKLoGvjSR3aIb9agPqu2FeDCdVOh__e8Y9V1Y,26192
26
+ arci/stats.py,sha256=x5twT7Kg2t5rX6wfAfnQMdjwAJk_FyNQcJIIDa-n7zQ,6064
27
+ arci/storage.py,sha256=PyngbQbeArw8By7CQKj6FKNARN142qEtPxO8vIQoVUc,1647
28
+ arci/toolbox.py,sha256=y-Gw2cCuS7LNh10vQbjINfFc-_iiCk1XNpaO0gI5mJ0,12021
29
+ arci/worker.py,sha256=WB74W04lt8lFC6fhYwIyR3BaXbjaiGjhlhnoCEw4I5I,7586
30
+ arci/demo/__init__.py,sha256=H5slPkgA4VfenosAPV-k9owfgPTohnwg8zwQtRJNTKM,192
31
+ arci/demo/agents.py,sha256=o5VpHISDoJlA_epzeNmNTp-DJFW1PeLCIUfoQEg7i2A,4180
32
+ arci/demo/experiment.py,sha256=u0D4pQ2AWJ7Jwe3WPPq8WEvQlLEq8AdbAiMDgP117Hk,2489
33
+ arci/demo/run.py,sha256=XR0JiRfyYIfdJxnUiX9DkX6ZWgmxiiL-7kBUrwvK6Dg,13043
34
+ arci/demo/world.py,sha256=GxpGvJY5r9p0FtePpL7CNBKmz7wuE9YeY0BozcsghNU,4453
35
+ agent_reliability_ci-0.8.0.dist-info/METADATA,sha256=2zR9W4aYRnMFslsZF_cIczYLvG4s8UEmpaNdMjnajj4,36499
36
+ agent_reliability_ci-0.8.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
37
+ agent_reliability_ci-0.8.0.dist-info/entry_points.txt,sha256=L1AYQhsIQJzWoG3Q_SIHy5lzc3bnfMQpeou36c5OUnY,39
38
+ agent_reliability_ci-0.8.0.dist-info/licenses/LICENSE,sha256=EGsLk1OHMpu4Y4mYkaUBUVXaeDeSm4QX3O8MYFHgV4Q,11353
39
+ agent_reliability_ci-0.8.0.dist-info/licenses/NOTICE,sha256=a9LQB8vhSxcTc7KtQFKZcH1vgx6TpERz3S1BXrhuXR0,420
40
+ agent_reliability_ci-0.8.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ arci = arci.cli:main