AI Operations · B10–B27wk 8 / 54
B11 · operations · Live operation

Surface Zero Goes Live

Status
in progress
Weeks
4–6
Permission gate
rung A
Depends on
B10
Page depth
full

Why this block exists

Scheduled here because every posting in this category screens on operating experience over time, and there's no later date at which that clock can be caught up. A thin system with real history beats a sophisticated one with three weeks, and Block 11 is the only place in this plan that clock could even start.

What I built

Surface Zero: a comment-only advisory agent that reads a new issue, suggests a label from the target repo's own set, flags a likely duplicate, and does nothing else. No write access beyond that single comment. The build follows Block 10's discipline into a live setting rather than replacing it: a ledger writes a started row before the agent runs and ends in exactly one immutable terminal state, so any run's actual outcome is checkable after the fact instead of trusted on faith. A kill switch gates the outbound comment specifically, never the model call, so disabling posting still leaves a record of what the agent would have said. And abstain is a first-class action with its own tool schema entry, not a fallback, carrying Block 10's should-abstain finding forward: a system with no schema slot for "I don't know" will eventually say something confidently wrong instead.

What I did

Outreach came before any of that got deployed. PCNtoolkit was the first real ask, chosen for a genuine neglected-backlog signal, and it declined on 2026-09-14. A second round (Amethyst-Mod-Manager, MetaHookSv) and a third (orchestkit, a stronger thematic fit) followed the same real-ask discipline; orchestkit went ten days without an answer. Rather than let a stranger's yes that might never come slip the block's own deadline, the target became ForgeAS/everForge, a repo I own myself, named plainly as a substitution rather than treated as equivalent to consent.

The pipeline came together target-agnostic first. Fly deployment went clean on the first attempt, no config correction needed. The kill switch got tested live in both directions against the running instance, not just against unit tests, with one real methodological catch along the way: a test payload that described itself as a kill-switch test got correctly abstained on as too ambiguous, so the first attempt never reached the code path it was supposed to exercise. Getting a real webhook delivery working end to end surfaced the block's actual finding: the posted comment read as me replying to my own issue, not as a distinct bot. A fine-grained PAT, correctly scoped to Issues-only exactly as designed, still authenticates as whoever generated it, and a permissions-only check has no way to catch that. Fixed by migrating to a GitHub App with its own installation-token identity, re-verified live as surface-zero-advisor[bot].

With the identity fixed, the blind test kit ran: 16 issue scenarios generated by Gemini from a domain brief that never mentions labels, categories, or triage, so the generator itself was blind to the fact that classification was being tested at all, not just working from an answer key written in advance. All 15 non-abstained results posted for real, through the same pipeline the live path uses.

What came out of it

15 of 16 exact matches against the answer key, one defensible abstain rather than a wrong guess, both engineered duplicate pairs caught with the correct originals pointed to. The single case worth naming: issue #8 was deliberately written to bait a bug label onto a request for functionality the target app never claimed to have. The advisor reasoned from what the app actually does rather than the reporter's framing, and called it enhancement instead of taking the bait, which is exactly the failure mode that kind of test exists to catch.

The bigger finding wasn't a number at all. The scope check on the posting credential passed every test it had and still missed the identity problem, because scope and identity are different properties of a credential and nothing in the methodology up to that point had asked a test to check the second one. That's a different category of gap than a failing test: some things don't fail a test, they fail to be tested in the first place, and this one only surfaced because a live comment got read, not because anything audited it.

What I'd change

The outreach thread should have had a check-in date attached to each round from the start, rather than each round's silence getting discovered mainly in retrospect when the next deadline forced the question. Orchestkit's ten days of silence weren't flagged until well after they'd passed. And the prediction sheet's own discipline slipped a third time in this block specifically: slot 5 was left as a placeholder and never filled before the live test happened, the same shape of miss Block 10's retrospective already called out once. A pattern that recurs after being named explicitly isn't a one-off anymore.

What carries forward

Live, continuously-updated version of these numbers -- population by evidence class, cost per successfully evaluated recommendation, the consequence-gate statement, and the identity-attribution incident -- lives on the Surface Zero operating page, not just in this writeup.

The scope-versus-identity distinction is now its own section in the methodology doc, not a fixed one-off bug: the next credential this program hands to anything posting publicly gets both questions asked, not just the scope one. The blind-test-kit pattern, a generator blind to the test rather than just an answer key written in advance, is reusable past this block for any agent whose output needs checking against unseen, realistic input instead of fixtures the builder already knows the shape of.

What this can't say yet: whether batch 2's harder, by-design-ambiguous cases hold up the way batch 1 did, what a Wilson-style interval on live accuracy would actually show against an unbounded issue stream instead of a fixed offline set, and what Gate A's real operating history looks like past a batch-posted test run. Also worth being plain about: the ladder's rung A still correctly shows no reached date. Giving good suggestions against my own repo isn't the same claim as a stranger consenting to run this against theirs, and that's the actual gate this block hasn't cleared yet.

01What it has to teach me

topicconsequence-gate design
topicrun-ledger governance
topiccredential scope vs. identity
topicblind test design for a live agent
topicmaintainer consent as a precondition

02Predictions, and what actually happened

Written beforehandActualVerdict
The Fly deploy needs at least one config correction after the first attempt to get auto_stop_machines and the volume mount both right. Medium confidence.Deployed clean on 2026-09-22 on the first attempt. No correction needed.FAIL. Called medium confidence, not a coin flip, and still missed.
The kill switch works correctly on its first live test against the deployed instance, not just in unit tests. High confidence.Verified both directions against the deployed instance: engaged, a real suggestion reached the ledger with comment_suppressed and no post attempted; disengaged, the next run went back to attempting a post.PASS. One methodological catch along the way: a test payload that announced itself as a kill-switch test got correctly abstained on as too ambiguous, so it never reached the outbound handler being tested. Had to redo it with a payload that reads like a real bug report.
GitHub's actual webhook signature header matches the implementation without changes. High confidence.Scored against real GitHub-originated deliveries: a ping, two hand-verified test issues, and sixteen from blind-test batch 1. verify_signature needed no changes across any of them.PASS.
The first real webhook payload surfaces at least one field or shape the test fixtures didn't anticipate. Medium confidence.No payload shape or field surprised the existing parsing across the same real deliveries. No code changes needed.FAIL. A genuine miss on a medium-confidence call, kept on the record rather than left unscored because it went the operationally convenient direction.

03Measured results

MetricValueHow it was measured
Blind test kit, batch 1 (n=16, generated blind to the classification task)15 of 16 exact label matches, 1 defensible abstain, both engineered duplicate pairs caught with the correct originalsGemini generated 16 issue scenarios from a domain brief that never mentions labels, categories, or triage, so the generator was blind to the test, not just the answer key. Scored against a pre-written key; results posted for real through the same pipeline the live path uses.
Kill switch, live verification (both directions)Engaged: ledger recorded comment_suppressed, no post attempted, suggestion still generated. Disengaged: normal posting path resumed.Signed test webhooks against the deployed Fly instance, checked against the actual ledger row and GitHub thread, not just unit test assertions.
Webhook signature verification against real GitHub deliveries (n=19: 1 ping, 2 hand-verified test issues, 16 blind-test batch 1 issues)0 changes needed to verify_signatureReal GitHub-originated deliveries against the deployed Fly instance, not self-signed synthetic payloads.
Credential identity gap, before the fix100% of posted comments attributed to Adam's own GitHub account rather than a distinct bot identity, despite the token's scope being correctFound by reading a live posted comment's byline, not by any automated check. Fixed by migrating to a GitHub App installation token; re-verified live afterward as surface-zero-advisor[bot].

04What I rejected

Rejected

A Render free-tier-plus-cron-ping workaround to avoid paying for always-on hosting. — Checked Fly's actual pricing before committing instead of assuming the free option was fine. Fly machines aren't always-on by default either, so the workaround would have hit the same spin-down problem on a different vendor, and Fly's real always-on cost (about $2/mo) undercut Render's own paid always-on tier ($7/mo) anyway.

Rejected

Treating PCNtoolkit's decline, or orchestkit's ten days of silence, as reasons to keep waiting on a stranger's yes past the block's deadline. — Rolled onto a repo I own (ForgeAS/everForge) instead, named explicitly as a real substitution rather than treated as equivalent to external consent. At Gate A the maintainer relationship is supposed to be the actual evidence, so nothing measured against everForge stands in for a stranger's consent.

05Judgment calls

Decided not to automate. Nothing in the automated test suite caught the credential identity gap, because the scope check tested exactly what it claimed to test, correctly. Scope and identity are different properties of a credential, and the methodology hadn't asked the tests to check the second one. Found by reading a live comment's byline, not by any check that ran on its own.

What surprised me. How completely a correct, fully-tested scope check still missed the identity problem. I expected a security gap, if there was one, to show up as an authorization failure the tests could catch. Instead the token did exactly what it was scoped to do and still posted under the wrong identity, which is a different category of miss than a failing test: some gaps don't fail a test, they fail to be tested at all.

06Evidence still missing

emptyScreenshot of a live comment posted by surface-zero-advisor[bot]
emptyLedger row export showing a comment_suppressed terminal state during the kill-switch test

07Operating record

Running since2026-09-24
Permission rungA
Surfacegithub:ForgeAS/everForge (owned repo -- rung A's stranger-consent precondition not yet reached)
Revocable byKill switch (gates the outbound comment) + Fly app stop

STACK · Python · FastAPI · uv · Fly.io · GitHub Apps (JWT / installation tokens) · Claude Sonnet (advisor) · Gemini (blind test generation)

2026-09-01Outline created from the AI Operations Roadmap (B10-B27).
2026-09-24Interim write-up after Week 2 build: deployment, kill-switch verification, GitHub App identity migration, blind test batch 1. Batch 2 and prediction 5 still open.
← B10 Evaluation Harness. The FoundationB12 The Installable →