Files
member-console/docs/agent-runner.md
T
cgalo5758 5829091881 Track the agent runner and its method page
The contained runner that drove the 2026-09 security audit, the README
review and four rounds of design ideation lived only inside the ignored
notebook. It moves to scripts/agent-runner/: the prepare, tools, audit,
ideation and teardown scripts, the compose and container files, the
allowlist proxy and the prompt templates, with the paths that assumed
the notebook fixed and findings written to a caller-named directory. Run
outputs, transcripts and the round-specific sheet scripts stay behind.

docs/agent-runner.md states the method: the disposable git archive
copy and the fail-closed proxy, how a task is shaped, union rather than
intersection of findings across models, separate adjudication of every
finding against the source, and the evidence a finding must carry.
2026-09-19 19:47:15 -05:00

140 lines
6.9 KiB
Markdown

---
title: "Agent Runner"
audience: [developer]
summary: "How models from other families review this repository: what contains them, how a task is shaped, and what a finding must carry before it counts."
---
# Agent runner
A repeatable method for putting this repository in front of models that did not
write it. Two lanes use it. The **security lane** audits a named slice of the
code against a threat model. The **ideation lane** answers a design brief with
a proposal and a mockup. Both run the same containment and the same rules about
what comes back.
The runner is [`scripts/agent-runner/`](../scripts/agent-runner/README.md),
which documents the commands and the environment variables. This page states
the method.
## The problem this solves
The model that built a flow cannot audit it. It knows which inputs it already
validated and which branch it already considered, so the gap it left is the gap
it cannot see. A model from another family lacks that memory, and it also
carries different priors: published evaluations put the same frontier model far
apart on different bug classes, so the family that misses a class is not the
family that catches it.
The cost of that instrument is over-reporting. Models reading code unaided
invent vulnerabilities, cite lines that do not say what they claim, and rate
hardening advice as critical. Every rule below exists to keep that cost
bounded.
## Containment
The model never sees the working tree. The runner unpacks `git archive HEAD`
into a disposable copy, mounts that copy as the container's working directory,
and deletes it at teardown. The model may edit anything it likes; the edits go
nowhere, and uncommitted work is not exposed.
The model container sits on a Docker network marked `internal`, which has no
route off the machine. Its only exit is a proxy sidecar that denies every host
except an allowlist of the model APIs. The arrangement is fail-closed: a
misconfigured proxy costs the run its network instead of leaking the code.
One secret reaches a container: the opencode auth file holding the
model-provider keys, mounted read-only. No other credential, and no live
database, identity provider or payment surface, is in the run at all.
## Shaping a task
A task names a **small set of files**, states a **one-sentence threat model**,
and asks **falsifiable questions** that each end in a yes, a no, or an explicit
"undetermined" with the code that settles it. The questions are the floor: the
model then hunts freely inside the slice, in whatever category it finds
something.
A task is not a bug-class checklist. A checklist inherits the reviewing model's
blind spots, because a class the model scores badly on stays badly scored when
it is listed, and it anchors the model away from whatever the list omits. One
benchmark scored a frontier model at F1 0.000 on authentication failures while
the same model scored 0.689 on SSRF; a checklist would have hidden that gap
behind a line item marked done.
The system context is supplied verbatim rather than left to the model to
assemble. Models are poor at gathering their own context, so the auth model,
the request pipeline in its actual middleware order, and the trust boundaries
are written out as fact, with file and line references the model can verify,
and prepended to every task. Prescriptive about slice and stakes; open about
bug class.
The ideation lane shapes a task the same way: the brief names the surface, what
survives any redesign, and the constraints the repository enforces, and it does
not name candidate solutions. A brief that lists the shapes it expects gets
those shapes back.
## Method rules
- **Union, never intersection.** Findings from several models are combined, not
filtered by agreement. Coverage is the reason to run more than one model, and
consensus filtering throws that coverage away: a model has reported a real,
later-confirmed finding on one pass and reported nothing on the same slice on
the next, so requiring a second reporter would have discarded it.
- **No self-review inside a run.** A model never re-checks or argues with its
own findings in the same context. Self-refinement loops measurably raise the
count of reported critical vulnerabilities without raising the count of real
ones. Adjudication is a separate pass by a different reader, against the
source.
- **Every claim is spot-checked before it counts.** A finding is a claim about
this repository, and it is checked against this repository, at the cited
path and line, before it enters any report or ledger. Claims fail this check
in practice: one model reported a committed `node_modules` directory that
does not exist here.
- **Candidates are not findings.** What a run produces is a candidate list.
Only what survives adjudication and the spot check enters
[`status/issues.md`](../status/issues.md).
## The evidence toll
A finding that does not pay all four is deleted before the report is written.
1. An exact `path:line-range` **and a verbatim quote** of the code. A model
that cannot quote the line has not found the bug.
2. The **full path from attacker-controlled input to the dangerous operation**,
naming each function it passes through. "Probably reachable" is not a path.
3. The **preconditions**: what the attacker must already hold, such as no
session, a member session, an operator session, a webhook signing key, or
control of DNS.
4. A **failure scenario** with concrete inputs and state, and the wrong outcome
they produce.
Severity and difficulty are rated on two independent axes rather than combined
into a CVSS score, because the one number hides which of the two a reader
disagrees with. Hardening and defence-in-depth are reported in a separate
section from vulnerabilities, so advice cannot inflate the vulnerability count.
## Security audits: the scanners run first
The deterministic scanners run before any model reads the code, offline, over
the same snapshot; the model then triages their hits as one of its tasks. That
order is deliberate. A model adjudicating scanner output removes most of the
scanner's false positives at a small cost in recall, while a model reading code
unaided over-reports. [`testing.md`](testing.md) names the scanners and fixes
the order.
Scanner output is a lead, not a finding. A hit confirmed in the source becomes
a candidate and pays the evidence toll like any other; a hit the model judges
false is reported as false, with the reason.
## Definition of done for a review
1. Every task in the lane has run for every model in the set, and the run
manifest records the audited commit, the model ids and the per-lane budget.
2. Each candidate finding has been adjudicated against the source by a reader
who did not produce it.
3. Each surviving finding has been spot-checked at its cited path and line.
4. Survivors are recorded in [`status/issues.md`](../status/issues.md); the
rest are dropped, and a finding dropped for being unreproducible is
recorded as such rather than silently deleted.
5. `teardown.sh` has run, and its residue check is clean.