Trusting Code No Human Wrote
Presented live around a running demo. The embedded panels showed a local harness that isn’t running here — each one is captioned with what it displayed on the night. If a diagram fails to draw, refresh the page.
Roger & Renate Rössing · Deutsche Fotothek · CC BY-SA 3.0 DE
Zechariah Judy · via Wikimedia Commons · CC BY 2.0
NASA Glenn Research Center · public domain
Evergreen Systems, 1987 · public domain
For the first time, we’re automating the building of the
software itself.
How do we do it securely?
One role decides what to build; another turns it into working software.
Owns what to build, and why: requirements & roadmap.
market · business · feedback · intuition
Software Engineer
The unit of work is a card. It flows left to right through fixed stages, owned by a human at every step.
https://www.testaify.com/blog/a-comprehensive-history-of-cicd
def run_agent(user_goal):
context = [{"role": "user", "content": user_goal}]
while True:
# 1. REASON: Ask the model what to do next
response = llm.call(messages=context)
context.append(response)
# 2. ACT: Check if the model wants to use a tool
if response.tool_calls:
for tool in response.tool_calls:
result = execute_tool(tool.name, tool.args)
# Feed the result back into memory
context.append({"role": "tool", "content": result})
else:
# 3. STOP: Goal achieved, or model is done
return response.content
A tool is just a function the model is allowed to call, described to it as a JSON schema.
{
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
The model only ever asks. You act.
The first, highest-priority message. Sets role, constraints, voice and output contract before the task is even seen.
soul: security-reviewer-go
role: qa
model: claude-opus-4-8
sandbox: go-toolchain@sha256:1c2…
tools: [read_file, search, submit]
system_prompt: |
You are a senior Go security
reviewer. Find vulnerabilities in
the diff: injection, auth bypass,
secrets committed in code.
Cite file:line and the exploit;
do not fix. Be terse. No praise.
The stable identity that prompt encodes: its expertise, its tone, what it refuses to do.
soul: security-reviewer-go
role: qa
model: claude-opus-4-8
sandbox: go-toolchain@sha256:1c2…
tools: [read_file, search, submit]
system_prompt: |
You are a senior Go security
reviewer. Find vulnerabilities in
the diff: injection, auth bypass,
secrets committed in code.
Cite file:line and the exploit;
do not fix. Be terse. No praise.
Persona + model + tools + sandbox, captured as declarative config. (We’ll come back to this.)
soul: security-reviewer-go
role: qa
model: claude-opus-4-8
sandbox: go-toolchain@sha256:1c2…
tools: [read_file, search, submit]
system_prompt: |
You are a senior Go security
reviewer. Find vulnerabilities in
the diff: injection, auth bypass,
secrets committed in code.
Cite file:line and the exploit;
do not fix. Be terse. No praise.
The scaffolding that makes an agent safe to run.
https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04
main is autonomous.
… but the build steps are hostile-by-assumption LLM agents.
How do you trust what they merge?
The app the factory built, opened live on stage. Every line of it was written by agents from a human-authored spec.
Where a human authors the spec. The only place a person writes anything — everything downstream of it is autonomous.


Six choices that buy back the trust
A human writes the spec. From there every stage is an agent, and the graph only moves forward when the orchestrator says so.
Emergent breadth, declarative depth: an agent proposes how many children a stage spawns; config fixes what stage comes next.
What moves a card forward is not the agent saying so; it’s the orchestrator checking guards the stage declares. The agent does the work; the orchestrator decides whether it counts.
implement:
precondition: blockers-closed
postcondition: [tests-red-then-green]
on_failure: implement # no human, must route
produces: [qa]
qa:
postcondition: [tests-pass, "mutation>=0.8", gosec]
on_failure: implement
produces: [integrate]
blockers-closed).
The guards are abstract predicates:
tests-red-then-green says what must be true, never
how to check it.
The actual pipeline I’m running. Each stage names abstract guards; cards only advance when the orchestrator says they hold.
dag:
plan → decompose the spec
author-tests → tests fail (red)
implement → tests red → green
qa → tests · lint
· static analysis
integrate → autonomous → main
qa fails → on_failure routes back to
implement. No human in the loop.
One registry maps each abstract predicate to the command that decides it.
checks: # abstract → real tooling
tests go test
compiles go build
lint golangci-lint
static analysis gosec
· govulncheck
· licenses
Every agent runs in a zero-network sandbox; the runner brokers all I/O. The agent never holds a key or touches the wire.
Blocks code that exfiltrates secrets, phones home, or pulls malware: there’s nothing to phone with.
On the board: every card you see is executing inside its own isolated sandbox.
Tests are authored by a different soul than the implementor, and gates run in a fresh sandbox the orchestrator controls, never the one that wrote the code.
Blocks an agent marking its own homework. Independence is what makes a green check mean something.
On the board: author-tests and qa are separate
cards, owned by separate souls.
Only the orchestrator writes the work graph. Agents propose mutations via their Result; each is validated as DAG-legal before it’s applied.
Blocks an agent rewriting the graph to delete its own gate or jump straight to merge. It can ask; it can’t write.
On the board: cards only move when the orchestrator says so. No agent moves itself.
Agents decide how many work items a stage produces; config decides what stage comes next. An emergent issue-DAG inside a fixed role-flow.
Buys bounded creativity: work fans out as far as the problem needs, never off the rails the config lays down.
On the board: watch one issue fan into many during plan.
Budgets and retry caps aren’t cost control; they’re the termination guarantee. Breach a budget and the work dead-letters for a human to triage.
Blocks an agent that loops forever, burning money and never halting. The budget is what promises it stops.
On the board: the budget meter, and stuck work landing in the DLQ.
Every change traces issue → soul → model → prompt → evidence, produced as a by-product of how the work is done, not logged after the fact.
Why you can’t trust a merge you can’t trace. Provenance is what lets a human re-enter and audit any decision the factory made.
On the board: every merged card links its full decision trail.
The mechanisms behind the six choices
One work item, one sandbox, destroyed after. The backend is config, not code:
| Backend | Isolation | Startup | Use |
|---|---|---|---|
| Docker | shares the host kernel (weak) | fast | local dev only |
| gVisor | user-space kernel | medium | medium-trust |
| Firecracker | own kernel (KVM microVM) | ~125 ms | production target |
Images are digest-pinned: a soul names a logical profile, infra resolves the digest, and the digest rides in provenance.
One chokepoint: the sandbox can only reach its runner; the runner brokers everything else.
Zero network, zero credentials, one allowlisted window — and that window doubles as the observability collector: every call, span, and transcript flows through it.
Live traces, logs and metrics from the running pipeline: every agent action, tool call and gate decision, end to end.
One orchestrator hands work to a queue; any number of runners, across any number of hosts, compete to pull it.
Declarative config: name, role, model, persona, tools, sandbox profile. Stateless: no cross-task memory, reconstituted fresh every invocation. All durable state lives in beads, git, and specs.
Everyone runs the same loop. A role’s behaviour is its persona plus which tools are enabled: planner, test-author, implementor, and security agents all share one loop.
Souls fulfil roles, chosen per-issue by a selector; you scale
by adding runners, not souls. This is what makes
producer≠verifier concrete: implementor-go is a
different soul from test-author-go.
No agent framework, no Claude Code: the loop, the tool dispatch, and every schema the model calls are custom.
read_file · list_dir · search
find_symbol · references · diagnostics …
write_file · edit_file · rename · code_action
run · run_tests · run_gate
trace_test → test↔spec map
submit_plan · submit · request_subtask · escalate
explore: broad question → distilled {summary · file:line anchors · leads} — a nested loop on a cheap model; it burns its own context, not mine
No human reads the log — a red gate has to be actionable.
build first · tri-state · never a false green
file:line · rule · severity — raw output kept as evidence
the next attempt sees the findings — the loop learns
Every merge to main is a harness-authored,
SSH-signed provenance commit:
Soul: implementor-go | Model: claude-opus-4-8 | Tests-Soul: test-author-go
Issue: bd-1234 | Prompt-SHA: 9af… | Verified: build@sha256:1c2…,test@sha256:8be…,gosec@sha256:0a4… | Traceability: sha256:7c1… | Transcript: sha256:3d2…
Transcript = a replayable
decision trail.
The same app after its spec was merged to main and shipped — the far end of the provenance trail.
Spec in, merged to main, deployed, with a full provenance trail
Untrusted by default · producer ≠ verifier · provenance by construction
lochieashcroft.com