ADR 0005: Engine-side EPP builds, brokered to the agent over MCP
Status: Accepted; implemented (builds run buildah-unified on the loop pod — kaniko was dropped; open questions 1–2 were resolved by ADR-0008, see the annotations below)
Date: 2026-06-26
Related: ADR-0001 (freeze the judge), ADR-0002
(the agent asks, the host holds the keys), the World::apply trait, the epp-mcp broker,
Containerfile.epp-sandbox, the #1109 TTFT domain (crucible.ttft.toml).
Context
#1109 is the first domain that needs in-loop EPP code rebuilds. The perf domain dodged this — it
is config-tuning (apply scorer weights onto the live EPP, no rebuild). When the #1109 loop ran, the
sandboxed agent could not make progress and doom-looped, because:
- It can't build.
build-imageshells out to an out-of-sandbox remote-build script, which isn't in the sandbox; and a kaniko build needs build-namespacekubectl, a push credential for the candidate registry, and egress that the deny-by-default sandbox does not have. The agent correctly concluded it had no container builder available and gave up. - It can't see the measurement.
epp-sandbox:spike-1predatesbench --ttft, so the agent'sbenchis the old prefix-cache bench — it can't reproduce the judge's TTFT metric, so it reverse-engineers the binary (strace/strings) and spirals.
Putting build creds in the sandbox (the "provision the sandbox" option) enlarges the attack surface
and fights the World trait, which already says build+deploy is apply's job (engine-side).
Decision
Move build+deploy off the agent to the loop pod (engine-side), and expose it to the agent as broker (MCP) tools — the ADR-0002 pattern, the agent asks, the loop pod holds the keys.
Why MCP rather than only [world].apply_cmd: a code loop needs compile feedback within the turn.
A purely post-turn apply build means a typo fails the whole iteration with no feedback. The agent
must trigger a build, get errors, and fix them before its turn ends. So:
- Primary path — broker tools. Add
build_epp(anddeploy_candidate, or a combinedbuild_and_deploy) toepp-mcp, alongsiderequest_trace. The agent calls them over the existing bridge (host.containers.internal:8849); the loop pod runs the container build (buildah today; the original design said kaniko) with its own push credential for the candidate registry, and returns a Resolution:{ok, image_ref}|{compile_error, log}. The agent iterates on errors, then deploys. - Backstop —
[world].apply_cmd. Keep an engine-sideapply_cmdthat ensures the latest built candidate is the deployed one before the judge measures (idempotent; a no-op ifdeploy_candidatealready ran). The manifest already supports[world].apply_cmdandCommandWorld::applyruns it. - The agent's sandbox loses build creds entirely.
build-epp/deploy-candidatedirect tools are dropped; the agent only edits Go + calls the broker.
Components to build
- Loop-pod build capability (in the crucible loop image): a self-contained buildah build + push (no dependency on an out-of-sandbox remote-build script) and a scoped push robot credential for the candidate registry (mounted secret).
epp-mcpbuild toolsbuild_epp/deploy_candidate, mirroringrequest_trace's admit/return shape. They run the loop-pod build and surface the compile log on failure.- Workspace sync (the crux). The loop pod must build the agent's current edits, which live in
the sandbox mid-turn (the driver copies the workdir in/out per turn, it is not shared). Proposed:
the agent commits its branch and pushes it to the loop pod's workspace as a git remote over the
relay; the broker builds from that ref. (Reuses git-memory; no new driver plumbing. Alternative: a
driver "mid-turn checkpoint" that syncs
/sandbox/epp→ loop workspace on the build call.) - Rebuild
epp-sandboxwith current epp-tools (so the agent's read-onlybench --ttftmatches the judge) + bakebench.rssource (or a TTFT measurement skill) so the agent understands the metric instead of reverse-engineering it. Drop the direct build/deploy tools. - Manifest wiring: broker build tools enabled in
[agent.broker];[world].apply_cmdas the ensure-deployed backstop; the candidate registry + egress allowlisted.
Open questions
- Build latency. Resolved by ADR-0008: the immutable compose bakes the toolchain and a warm
GOMODCACHE/GOCACHEinto the base, so the per-iteration build is an incremental build against a hot cache, not a cold from-scratch image build. - Workspace sync mechanism. Resolved by ADR-0008: the agent's branch push is the CoW overlay;
the broker builds
base@SHA + overlay. - Cred scoping — a push robot token limited to the candidate registry, not a broad registry cred.
Consequences
- Positive: build creds never enter the sandbox (ADR-0001/0002 aligned); the agent gets real
compile feedback; the loop pod is the one trusted place for push creds;
apply_cmdguarantees the measured candidate is the built one; code-change domains become first-class, not just config tuning. - Negative / cost: real infra (push cred, RBAC, a builder in the loop image — kaniko as designed, buildah as shipped — the workspace-sync mechanism, a sandbox rebuild) and per-iteration build latency.