Skip to content

Design: blocked-action deferral for /go

A /go run halts when the harness classifier blocks an action, because a sub-agent has no channel to reach a human and correctly refuses to invent one. This design makes a run survive that: blocked work is deferred rather than halting, the run completes its remaining children, and one self-verifying script lands at the end. The deferred list then becomes the backlog signal for what to automate next, so the interruptions shrink by design rather than by anyone remembering to reduce them.

Design: the problem was measured before it was designed

Section titled “Design: the problem was measured before it was designed”

The blocking rate and its composition were measured across 958 session transcripts on 2026-08-15, before any option was considered — an explanation generated rather than derived is FC-19, and this design exists because of a defect that class produced elsewhere.

54 handoff scripts sit adjacent to real block language — a true block hits roughly 1 session in 18. Classifying them by what they did:

ClassCountExamples
Classifier — authorised, the classifier could not tell~45ADO pipeline authorisation, secret and credential provisioning, host permission fixes, Dependency-Track and Zabbix API writes, a Terraform apply
Policy — a human genuinely should decide~9Cloudflare, Home Assistant, PVE firewall, a decom

Roughly 4:1 toward the class that is friction rather than policy, which is the class this design is allowed to engineer away.

Seven of the 54 were an agent hand-running ADO pipeline authorisation that ado-pool-auth-reconcile.sh already performs nightly at 04:10. That block was never a permission problem — it was the agent reaching for the wrong path to do something already automated. It is the cheapest possible fix and the clearest illustration of what “routed” means.

Design: the classifier is a separate gate from the permission allow-list

Section titled “Design: the classifier is a separate gate from the permission allow-list”

The design’s first candidate — pre-authorise the narrow classes in settings.json — was rejected on evidence, and this finding is why.

The auto-mode classifier gates on intent, not command pattern, and is independent of the permission allow-list. Bash(curl *) is already allowed, yet a curl that reads a secret still blocks. The lesson corpus records that for the credential category in-chat approval never clears the block — only a code-path change does, and that in one case even writing a script containing the blocked action to /tmp was itself blocked while the same write to the scratchpad passed.

So widening permissions cannot reach the blocking class. Routing the action to a sanctioned deterministic path is the code-path change the corpus says is the only thing that works — which is why the routed set, and not a permission grant, is the primary mechanism.

YesNoYesNoNoYesgrowsChild hits a blocked actionIn the routed set?Trigger the sanctioned pathe.g. the 04:10 poolreconcilerChild continues anddeliversPolicy class?Cloudflare · UDM · HA ·exposure · destructiveReported separatelyexplicit decision NEVERbatchedIn the deferrable whitelist?FAILED counts towardthe breakerDEFER action recorded,Issue stays To DoDependents deferred too,never spawnedRun CONTINUESnothing blocked haltsOne end-of-run scriptself-verifies, closes Issueson PASSDead-man ages anythingunactionedDeferral becomes a markedsnagClose-out review: recurringones get a path
YesNoYesNoNoYesgrowsChild hits a blocked actionIn the routed set?Trigger the sanctioned pathe.g. the 04:10 poolreconcilerChild continues anddeliversPolicy class?Cloudflare · UDM · HA ·exposure · destructiveReported separatelyexplicit decision NEVERbatchedIn the deferrable whitelist?FAILED counts towardthe breakerDEFER action recorded,Issue stays To DoDependents deferred too,never spawnedRun CONTINUESnothing blocked haltsOne end-of-run scriptself-verifies, closes Issueson PASSDead-man ages anythingunactionedDeferral becomes a markedsnagClose-out review: recurringones get a path

Design: decisions and rejected alternatives

Section titled “Design: decisions and rejected alternatives”
DecisionChoiceRationaleRejected
Root approachRoute what can be routed, defer the rest, and let deferrals grow the routed setPre-authorising cannot reach the blocking class — the classifier gates on intent, not command patternPre-authorise narrow classes in settings.json (cannot work); escalate to a human mid-run (reintroduces the interruption /go exists to remove, and can block overnight against quiet hours)
Routed set formCommitted YAML, the child brief points at itMachine-readable so /go can classify a deferral; versioned and reviewable; starts at ~3 entriesA runbook section (not machine-readable, prose drifts); baked into the agent definition (grows unboundedly, every addition a config change)
Feedback loopFolded into the existing snag close-out, distinctly markedReuses a drain that already works rather than adding a check; the marking keeps the deferrals legible as a toil measurementA dedicated /wrapup check (another mechanism); a standalone scheduled review (another job to keep alive)
Circuit breakerOnly FAILED counts; nothing blocked haltsK was derived from a 3.2% CI-failure floor — it measured failures, never blocks. A blocked child is not evidence the run is brokenPolicy blocks still halt (the two classes are not reliably distinguishable at the moment of blocking); cap total deferrals (a second unjustified number — FC-16)
Deferred child’s IssueStays To Do with the deferred action recordedState is a claim, not proof — the work is not done until the script runs and its assertion passesA new Deferred state or tag (ADR-0399 closed the tag vocabulary); closed as Done with a caveat (launders unfinished work into a green board)
Dependents of a deferred childDeferred too, never spawnedThe work order already declares Depends, so the orchestrator knows without spawning; a FAILED here would pollute the breakerSpawn and let it discover (burns a worker to learn what was already known); spawn anyway (builds on a missing prerequisite)
End-of-run scriptRuns unread, self-verifies, closes each Issue only on a real PASSArron’s explicit call: he executes these scripts without reading them, so a manifest prompt is review theatre, and designing around a fiction is worse than designing without it. Matches the house handoff conventionA plain-English manifest with a pause (theatre, by his own account); per-action confirmation (review theatre by the third action)
Policy-class actionsHeld out of the deferrable whitelist entirely“Nothing halts” plus “no prompt” would otherwise let a Cloudflare or Home Assistant change auto-run unread, dissolving RULE 3, 4, 13 and 14 as a side effect of an unrelated design. Costs ~9 of 54 blocksEverything deferrable (four prime directives become advisory); policy actions pause inside the script (a mostly-unattended script that occasionally waits is easy to abandon half-finished)
Authority tierDelivery stays delegated; the end-of-run script is a standing grantNo human confirms scope at execution time, which is the standard’s own definition of standing. The controls chosen — narrow enumerated whitelist, blast-radius cap, fail-safe — are exactly the standing-tier set, so this names reality rather than adding burdenKeep it all delegated (rests on a gesture Arron has stated is not a review); defer the framing to the ADR author (that decision determines which controls are mandatory)
Unactioned deferralsDead-man, aged and alerted“Nobody pasted the script” must be caught by instrumentation, not by remembering; the open Issues are a second independent signalRely on the open Issues alone (only caught when a session looks); Pushover only (quiet hours mean a 22:00 notification may never be seen)
Decomposition2 issues → 2 promptsThe mechanism is one context; the PRR needs a real run with a real deferral and cannot self-certify in the session that builds it1 issue (the PRR would be self-certification); 3 issues (same files, same context — the piecemeal handoff Arron rejects)
  • Five Pillars. Pillar 1 and 3: a Zabbix dead-man aging unactioned deferrals, and its trigger. Pillar 2: go_deferrals_total and deferral-age metrics through the existing go_run.py textfile pattern, surfaced on the cc-pool dashboard beside the /go SLI tiles. Pillar 4 is inherited — no new image; the dependency remains the Claude Code CLI, already a registered material service provider. Pillar 5: the ADR, the standard amendment and the runbook update are deliverables of issue one, not follow-ups.
  • Config as code. The routed set and the deferrable whitelist are committed YAML deployed by Ansible; the agent definition and skills are committed to claude-config. The end-of-run script is deliberately ephemeral — it is a run artifact, not configuration.
  • Docs as code and ADRs. A new ADR amends ADR-0403 with the two-tier framing and the revised breaker semantics; the Autonomous Remediation Authority Standard gains a standing-sub-grant clause; operations/go-delivery-runs.md gains the deferral and script sections.
  • Security and blast radius. The whitelist is the envelope and it is enumerated, not open — anything not named is not batchable. Policy-class actions are excluded by construction. The script closes an Issue only on a real re-asserted PASS, so a failed action cannot launder itself into a green board. Residual risk: the whitelist derives from reading 54 script names rather than a classifier taxonomy, so it will be wrong at the edges before it is right.
  • Software update and patch lifecycle. Inherited unchanged from /go: the dependency is the Claude Code CLI, which auto-updates and is not pinned. cc_fork_resume_test.sh re-proves sub-agent conformance on every version change, and its verdict is a fail-safe — a non-green result makes /go refuse to fan out and degrade to inline delivery. The deferral path rides that same degradation: delivering inline means the orchestrator can hand Arron a script directly, so the mechanism fails safe into the status quo. Staleness detection is the version comparison itself; the trigger is the vendor’s own upgrade, so nothing depends on remembering.
  • Planned maintenance. Unchanged and inherited — per-child scoped silences before any child that perturbs a live service, recorded in the run state so a halt can lift them.
  • Notifications. Pushover at priority 0 only when the end-of-run script fails, and the dead-man trigger. The quiet-hours ceiling is respected: nothing here can wake anyone, and a deferred action failing overnight fails safe (the Issue stays open, the dead-man keeps ageing).
  • Tagging. Deferral metrics carry epic and outcome labels per the taxonomy; the Epic carries only grilled.
  • DORA and SRE delivery performance. Change failure rate becomes materially more accurate: BLOCKED currently pollutes it by counting friction as failure, and this design removes that. Deployment frequency and lead time are unchanged (they come from pipeline runs). MTTR gains a second dimension — the deferral-to-paste interval, which is the toil this design is trying to shrink and is exactly what the dead-man measures.
  • Minimal decomposition. 2 issues → 2 prompts.
Control / decisionFrameworkHow it is tested and evidencedResidual risk
Deferrable set is an enumerated whitelist, not an open grantISO 27001 A.5.15 (access control), A.8.2 (privileged access); NIST CSF ProtectThe whitelist is committed YAML; proven RED by confirming a policy-class action is refused entry to the scriptThe list is seeded from observed blocks, not a taxonomy — edges will be wrong first
Policy-class actions excluded from unattended executionNIST CSF Govern; ISO 27001 A.5.2 (roles and responsibilities)RULE 3/4/13/14 remain the gate; the exclusion is tested by the same RED proofAn action misclassified as classifier-class would bypass its directive — mitigated by the whitelist being additive and reviewed
Standing-tier controls on the end-of-run scriptAPRA CPS 234 (controls commensurate with threat, regular testing)The Autonomous Remediation Authority Standard’s standing-tier checklist applied at /code-review and re-checked at /wrapupThe script runs unread by design — an accepted risk, recorded here rather than hidden
Self-verification before an Issue closesCPS 234 (control testing); NIST CSF DetectThe script re-runs each Issue’s own acceptance assertion and closes only on PASS; proven by inducing a failing action and confirming the Issue stays openAn assertion that is weak lets a weak PASS through — the work-order contract’s substance check is the upstream control
Dead-man on unactioned deferralsISO 27001 A.8.16 (monitoring activities); CPS 230 (operational risk)Zabbix item returns a non-zero age while a deferral is unactioned; trigger proven to fireThreshold measured at the PRR — 24h retained against an observed maximum of 13.02 h across 78 handoffs
Audit record of what ran unattendedISO 27001 A.8.15 (logging); CPS 234 (traceability)Every deferral is recorded in the run state, written onto its ADO Issue, and raised as a marked snagThe script’s own execution log is ephemeral unless captured — the deterministic /tmp/<name>.log convention covers it
Claude Code CLI as a material service providerAPRA CPS 230 (material service provider risk)Version-change conformance test with a fail-safe degradation path, inherited from /goAn upgrade landing mid-run is not covered; the test fires on next invocation

Design: operational readiness and handover

Section titled “Design: operational readiness and handover”
  • Service record. Agent tooling within the cc-pool service, tier T2 under the Critical Operations Register. No new host, no new material service provider.
  • Reliability contract. The primary SLI remains /go’s handover-free rate. This design adds a secondary signal that is explicitly a toil measurement, not a reliability one: deferrals per run, which should trend toward zero as the routed set grows. A rising deferral rate is not a fault — it is the backlog telling you what to automate next.
  • Runbook. docs/operations/go-delivery-runs.md gains: how to read a run that deferred, where the script lands and how to run it, what to do when it fails, and how the dead-man presents. A cold session must be able to action a deferral from that page alone.
  • Test strategy. Per-issue: the deferral path proven by inducing a real block; the whitelist proven RED against a policy-class action; the dead-man proven to fire. The service-level plan is the PRR itself — a real run that actually deferred, batched, executed and closed. Future work touching /go runs that acceptance first as a regression baseline.
  • Day-2 BAU and toil. This design removes toil rather than adding it — the recurring obligation is the close-out snag review, which already exists. The one new standing obligation is keeping the routed set current, and that is the feedback loop’s job, not a separate chore.
  • Backup and restore. No new data. The run state is ephemeral by design; its loss costs the automatic script assembly, which is recoverable by hand from the ADO Issues.
  • Standing owner. The dead-man is the detective control; the snag close-out is the adjudication path; /health surfaces the cc-pool verdicts. Nothing new needs remembering.
  • Lifecycle exit. Withdrawing the deferral mechanism means removing the whitelist and reverting the breaker semantics — a superseding ADR and a standard amendment, the same path this design walked.

Design: service transition — the standing contract (completed at PRR, Issue #2438)

Section titled “Design: service transition — the standing contract (completed at PRR, Issue #2438)”

Completed against ADR-0341, which makes the project→operations handover a gated deliverable. The fields below were open at design time and are resolved here with measured values.

Primary SLI (unchanged)/go’s handover-free rate — runs finishing without a context handover
Secondary signal (new, toil not reliability)go_deferrals_open — deferrals recorded and not yet actioned, estate-wide
Targetgo_deferrals_open returns to 0 within 24h of a run finishing
Why 24hThe measured paste-latency maximum is 13.02 h across 78 handoffs; 24h clears it with 1.8× headroom. The same number the dead-man uses, from the same measurement — deliberately one threshold, not two.
Baseline at go-live0 open, 0 ever recorded from a real block. The mechanism is proven synthetically only.

go_deferrals_open must never enter the SLI. A run that deferred still finished in one session and handed over a script rather than a context, which is exactly what the SLI measures. Folding them together would make one number mean two things and neither clearly.

Error-budget policy — graded by what the deferral count is telling us

Section titled “Error-budget policy — graded by what the deferral count is telling us”

Modelled on the servicemap policy: decided in advance so it is not argued during an incident. This is a toil budget, not a reliability budget — the response is automate, never page.

Open deferrals ageing past 24hResponse
0Normal.
1The dead-man fires (Warning). Hand Arron the line; no design change implied.
≥2 in one week, same classThat class is the backlog naming itself. The next /go work item is routing it — moving it from deferrable to routed with a sanctioned deterministic path — not delivering more features.
≥3 concurrently openFreeze on widening deferrable. The whitelist is outrunning the routed set; the only permitted register change is an addition to routed.

Forbidden as a response to burn: raising the 24h threshold, muting the dead-man, or widening deferrable to absorb a class that keeps recurring. All three convert a toil problem into a measurement problem — the same rule as never widen a tolerance to make a red go away, one level up. Who decides: Arron. Nothing here is automated on budget state.

BAU ownership — who holds this in steady state

Section titled “BAU ownership — who holds this in steady state”
ObligationOwnerCadence
Actioning a deferral (pasting the script)ArronOn the dead-man, or at run end
Noticing an unactioned deferralZabbix go.deferral.age triggerContinuous
Adjudicating deferrals as findingsThe snag close-out (snag close refuses while any are unrouted)Per Epic
Keeping the routed set currentThe error-budget policy aboveOn the ≥2-in-a-week trigger
Surfacing run verdicts/healthDaily

No new scheduled job and no new slot in schedule_config.yml. Every mechanism above rides one that already exists — which is the point: a handover that needs a new chore has not been handed over.

Toil delta — measured, and honest about its sign

Section titled “Toil delta — measured, and honest about its sign”

The baseline is measured, not asserted: 78 genuine blocked-action handoffs over 30 days = 18.2/week, each one an interruption that stops a run and demands Arron’s attention at a moment not of his choosing.

What the mechanism changes is the shape of that toil, and only part of its volume.

  • Removed: the run no longer halts. Every handoff previously ended the run’s autonomy and demanded a context handover; now the run finishes and batches. That is the whole delivery.
  • Batched: N interruptions across a run collapse into one script at the end. On the observed rate a single multi-child run carrying 2–3 blocks becomes one paste instead of three stop-the-world moments.
  • NOT removed: the paste itself. A deferred action still needs Arron. The toil is deferred and batched, not eliminated — and honesty here matters, because the routed set is the only thing that actually eliminates it (7 of the observed blocks were already automated and simply reached by the wrong path).
  • Added: one new obligation — keeping the routed set current — with the error-budget policy above as its forcing function rather than anyone’s memory.

The quantitative claim stops here deliberately. How much of the 18.2/week the mechanism absorbs depends on the friction:policy split, and that split is not measured — see the residual below. Publishing a percentage would be inventing the number this design exists to avoid inventing.

Residuals — accepted, and what would close each

Section titled “Residuals — accepted, and what would close each”
ResidualStatusWhat would close it
No real deferral observed end to endOpen. Synthetic proof only; three /go runs on 2026-08-16 produced zero genuine blocks. The empty ledger is confirmed a real reading by positive control, not a broken query.The next run that hits a genuine deferrable block, carried defer → batch → paste → assert → close. Wait for one; build nothing.
Classifier parent/child symmetryOpen, and not answerable by an agent — any stimulus that triggers an intent-gated classifier is sensitive enough to be blocked, as the PRR’s own probe discovered.A human-executed probe holding action and framing constant across the boundary.
The 45:9 friction:policy splitWithdrawn as unvalidated. From 54 script names; keyword re-derivation over handoff text over-matched and was discarded rather than published.The mechanism’s own ledger — go_run.py deferrals records the class of every real block, making the split counted rather than estimated.
The script runs unreadAccepted, knowingly, on Arron’s stated behaviour. The control is the whitelist plus self-verification, not review.Nothing — this is recorded rather than mitigated.
policy_excluded completenessImproved, not closed. pve01-host-mutation added from six observed blocks; the fail-closed unknown default is what carries the rest.Each new observed block that names a class nothing covers.
Compliance mapping resolved by the agent, not grilledOpen, carried from design time.A grill pass with Arron.

2 sub-issues → 2 prompts.

  1. Deferral mechanism, routed set and the standing-grant envelope (#2437). The whole mechanism in one context: routed-set YAML, deferrable whitelist, breaker change, deferral recording, script generation with self-verification, dependent deferral, dead-man, metrics, the ADR, the standard amendment and the runbook.
  2. Handover / PRR — prove deferral on a real run (#2438). Requires a real run that genuinely deferred, so it cannot share a prompt. Derives the dead-man threshold from observed paste latency, answers the sub-agent classifier question with evidence, and corrects the whitelist against what actually blocked.
  • The dead-man threshold has no measurement behind it. CLOSED at PRR. Measured across 78 handoffs: maximum 13.02 h, so 24h is retained with 1.8× headroom. The quiet-hours rationale was withdrawn — quiet-hours handoffs proved faster than daytime ones.
  • Whether the classifier weighs an identical action identically for a child and its parent is still open. That it covers sub-agents is proven twice (ADR-0413’s probe; the PRR’s own accidental block). The symmetry question is structurally not answerable by an agent — the probe designed to settle it was itself classifier-blocked, and was not routed around.
  • The deferrable whitelist derives from reading 54 script names. PARTLY CLOSED at PRR. Reviewed against 78 re-derived handoffs; pve01-host-mutation added to policy_excluded from six observed blocks. The 45:9 split is withdrawn as unvalidated — a keyword re-derivation over handoff text over-matched and was discarded rather than published as a correction.
  • No real deferral has been observed end to end. The mechanism is proven synthetically; the live ledger is empty and confirmed a real reading by positive control.
  • The review integrity trade was made knowingly. Arron stated he executes handoff scripts without reading them, so the design does not pretend otherwise. The control moved to the whitelist. This is an accepted risk, recorded rather than mitigated.
  • The compliance mapping above was resolved by the agent, not grilled with Arron.

Filled by wrapup Check O at Epic close.