Repository navigation
1.9.0 — 2026-09-30: your agent, your database, 15 minutes
A minor release for the agent path, which is outside the API freeze. It adds two
flags, a runner for each of two agent stacks, and the first README that leads with the
agent path. Nothing frozen moves: exit codes, ScanResult.passed, the score, the
eleven metric names and every seeded draw are unchanged. Service-path output is
unchanged. Agent-path output changes in the ways stated below.
1.8.0 was committed but not published separately; its changes ship in 1.9.0.
Added
- The walkthrough, first in the README and in
docs/SCANNING.md. It takes a
stranger's own agent and database-backed MCP server to an agent-path verdict in six
steps, with a table of everyNO VERDICTreason and what to do about it. --verify-command CMD(scan,ci;--target agentonly). It is an effect
oracle for a store no MCP tool reads. It runs as/bin/sh -con the string as typed,
with the scan's whole environment, stdin/dev/null, and its own process group, which
a timeout kills. The read must exit 0 and print a non-negative integer (a count) or a
JSON list (the entries); with--verify-count, an object holding one at that path.
Anything else is a failed read, never a zero, and empty output is not[]. At
setup the command runs twice and refuses (exit 2) when a read fails, printing the exit
code, the last 500 bytes of stderr and the first stdout line. It also refuses when the
two readings differ, which is the stand-in for a read-only check a command cannot have.
A mid-scan failure is the task'sfailedwindow, which means NO VERDICT. It is
mutually exclusive with--verify-tool, and--verify-argsis refused beside it. A
list feedsexpected_entriesunchanged. The export records
verify_command_digest(first word and a sha256 prefix), never the text.--key-path PATH(scan,ci;--target agentonly) andretry_keyson the
behaviour probe. The proxy reads the retry key at a dotted path (default
idempotency_key; only a string counts) and records it in the row's
idempotency_keyfield. It also recordsoperation_fingerprint, the call's
fingerprint with the key removed.retry_keysclassifies every retry that reached the
proxy against its operation's first call:kept,changedorno_key. It is
null("not read") when no call in the scan carried a key, the clean pass included.
It is report only and printed asretry keys (reached the proxy); a zero prints
no retry reached the proxy. Under
--repeatsit is summed over the runs, never averaged. The path travels to the proxy
as the schedule file'skey_path, written only when the flag is given.
trajectory_idis unchanged.- The
tools_listrecord row andkey_path_declaredon the behaviour probe. The
proxy records thetools/listlisting it served, once per record, as
{"kind": "tools_list", "tools": [{"name", "inputSchema"}]}.key_path_declared
reads it for each tool a recorded call named:declaredwhen the schema has a
property at every segment of the key path,absentwhen it does not,nullwhen no
listing was seen (an agent that never listed, or a record from before this row). It
is read off the schema only; no property name is taken as key-like. When no call sent
a key, theretry keysline now says why from it:not read: create_order takes a key at idempotency_key; no call sent one(no--key-pathhint), ornot read: create_order takes no key at options.key, so its retries cannot be deduplicated by key; pass --key-path if it takes one elsewhere. With no listing, the text is as
before. Report only. suggested_seedon the fault probe. When no run had an uncertain task, it is the
first seed above--seed(up to 10,000) whose forced schedule drops the reply to
some task's first clean-path call. It uses the same draw that builds the schedule,
and the NO VERDICT reason names it:...; raise --fault-rate, or --seed 1431 drops the reply to t1's first create_order call.It is a message, never a behaviour.examples/runners/run_openai_agents.pyandrun_langgraph.py, derived from the
Gate S runners. You edit one function. Each has a PEP 723 block pinned to Gate S's
versions, and neither importsratemyagent.--scriptedruns either with a
scripted model and no key:agents.testing.ScriptedModel, or alangchain_core
fake chat model whosebind_toolsreturns itself. It never builds a real model.- CI job
example-runners, one leg per runner, at $0. A key in the environment fails
the job. Each leg runs Gate S's task, seed and close (with anidempotency_keyadded)
and asserts exit 0, a non-empty record, a parsed claim,lost_acknowledgements == 1,
no retry reaching the proxy, andPASS, UNRECONCILED. tests/fixtures/orders_mcp_server.py, the walkthrough's server:create_orderwrites
into a stdlibsqlite3file.
Changed
PASS, UNRECONCILED. When an agent scan passes andlost_acknowledgementsor
unsupported_claimsis nonzero in any run, the verdict line readsPASS, UNRECONCILED: score N meets pass threshold 75, but the agent's claims and the upstream's state disagree: lost acknowledgements 1 (t1).The second line explains the two readings.
ciprintsPASS, UNRECONCILED score ...; lost acknowledgements 1 (t1)and still
exits 0.passedand the score are unchanged, and a FAIL or NO VERDICT is never
relabelled. Re-rendered from the shipped Gate S exports, all ten SDK replicates and
Claude Code replicates 3 and 5 now readPASS, UNRECONCILED. The six PASS-absence
assertions tightened in 1.7.2 now name this form as well.- The agent's stderr is logged on every exit without a result line, at WARNING, as
its last 4,000 characters. That includes a task killed at its deadline, which used
to log nothing because a cancelledcommunicate()discarded what it had read. The
refusal that follows (no calls recorded, abandoned, did not complete, or no claim at
--claim-path) quotes the last lines. A run with a result still logs the first 2,000
characters at INFO, as before. - A
--work-dirthat already holds records is refused (exit 2), naming them. The
proxy continues a record so that a reconnecting agent keeps its place, which meant a
second scan into the same directory started its fault schedule where the first
stopped. Its clean-call counts also included the first scan's calls. Found building
the walkthrough: step 6's re-run at the suggested seed realized no fault. The default
work directory is new on every scan and is unaffected. --agentgoes through the stdio space rule: an unquoted path with a space that
exists when rejoined is refused, with the quoted form printed.- Messages that name the oracle name the one in use:
no --verify-tool or --verify-command,the verify command did not read the upstream around task t1, the
clean-pass persistence refusal, andci'sNOT MEASUREDline. repeat_by_groupentries gainvalues, every run's value per metric, which
PASS, UNRECONCILEDreads.- The README's Gate D, BD and S narrative moved, verbatim, into each gate's
examples/*/README.md. The README keeps the comparison table and links. - The walkthrough's step 6 changes the payload as well as the work dir
(rma-probe-1torma-probe-2).app.dbkeeps step 5's rows, and a server that
honours idempotency keys absorbs a key the agent reuses from the first scan, so an
agent that builds its key from the prompt had its re-run's clean pass apply nothing,
and the scan refused it as a persistence problem. The named seed still applies: it
places the fault by task, tool and call number, not by content. - The clean-pass persistence refusal names key reuse across scans as a possible
cause when a task applied nothing and its record shows a key on a call answered
with success. It is worded as a possibility from what the record holds, and printed
beside the persistence advice, never instead of it. The refusal still exits 2. - The NO VERDICT table's "applied nothing" row states a limit instead of advising a
loop. It said to change the payload again, and for an agent that derives its key
from the task's content that yields the same NO VERDICT: the new scan's chaos pass
re-sends the key its own clean pass stored. The row now reads: an agent that derives
its key from the task's content is absorbed by a key-honouring server on the chaos
pass, so this walkthrough cannot measure it; this is a known limit (state isolation),
not a fault in the agent.docs/LIMITATIONS.mdsays the same. testpaths = ["tests"], so a barepytestfrom the repository root collects the
suite and nothing else. An unpacked sdist underassets/brought its owntests/and
stopped collection.
Shipped gate evidence: archived, not regenerated
examples/phase-d-gate/, examples/gate-bd/ and examples/gate-s/ keep their evidence
exactly as shipped. Their READMEs gain the prose that left the top-level README. Read by
1.9.0, with nothing re-run:
- Gate S's verdicts. The ten SDK replicates and Claude Code replicates 3 and 5 render
PASS, UNRECONCILED(lost acknowledgements 1 (t1)). Claude Code 2 staysPASS, and 1
and 4 stayFAIL. All fifteen re-score to the exportedpassedand score. retry_keyson the Tier 0 RUN-LIVE records. Those rows predate
operation_fingerprint, so they group onfingerprint. The five replicates, summed as
the runs of one scan, givekept 1, changed 0, no key 4, the count RUN-LIVE §3 reports.
Unchanged, stated
- Exit codes,
ScanResult.passed, the score, the eleven metric names,ALL_FAULTS, and
every seeded draw and forced schedule. - The service path:
examples/mock-failing.*, regenerated from its documented command,
differs only in its timestamps. trajectory_id, and so recovery and amplification grouping. Without--key-path, a
record row'sidempotency_keyholds exactly what 1.8.0 wrote, and the schedule file is
1.8.0's byte for byte. Every row gainsoperation_fingerprint. A record gains one
tools_listrow when the agent lists tools, numbered with the other rows; it is never
replayed as a call.- The verdict rule.
PASS, UNRECONCILEDrelabels a pass and blocks nothing.
suggested_seedonly extends the existing "no task had a call whose outcome was
unknown" reason.