You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every safety mechanism vanguarstew ships today is static and pre-merge — it inspects who authored a PR, what paths it touches, or how it scores before any effect lands, and then relies on the fact that the runtime never acts at all. Nothing observes what happens after a change is merged, and nothing trips on a bad outcome.
Pre-merge static gates (author/path/shape):scripts/benchmark_pr_policy.py auto-closes out-of-surface PRs; .github/workflows/pr-target-check.yml auto-closes any PR to main whose author is not matedev01; plus the pr-gaming-policy, pr-integrity, pr-limit, pr-reopen-policy, pr-source-check workflows. All key off identity/path/shape, not results.
Pre-merge value gate (still not an outcome):scripts/score_pr_delta.py bands a benchmark delta with a Pareto floor; it is explicitly a reporter, always exits 0, and only looks at two run_eval artifacts produced before merge.
"Safety = do nothing": the runtime is read-only and advisory by construction (vanguarstew_runtime/github.py:1-6; service.py:56-64); dry_run=true is the default; can_auto_execute() returns False "ever" (openvang/factory.py:434-438).
The one place "rollback" is named is unbuilt and pre-effect: ROADMAP.md §4 lists "containment/rollback rules," but its acceptance is only that "a malformed, stale, duplicate, or unapproved request cannot reach the external effect" (ROADMAP.md:61-69) — validating a request before it acts, not catching a regression after it acted.
There is no post-merge regression detector, no revert path, and no outcome-conditioned kill-switch anywhere in the repo (grep-confirmed: rollback occurs only as SQLite ROLLBACK; revert only as a commit-kind classifier). Tellingly, agent/decider.py:51 already asks the model to weigh "stability, blast radius, rollback cost" while the system has no rollback capability at all. The existing kill-switches (dry_run, allow_external_inference, the per-repo allow-list) are all input-conditioned — set once by the operator up front — never tripped by observed harm.
Why it blocks autonomous external maintenance
Once autonomy is earned on a low-risk surface (docs, tests, mechanical refactors) of an external product repo, the pre-merge gates stop being sufficient: a merge can be individually "safe" under every gate and still regress the live system. On an external repo there is no benchmark delta to gate on (that apparatus only exists for changes to our own agent/**), and the pre-merge judge signal that would substitute currently saturates and cannot discriminate (#2379). So the only trustworthy discrimination signal for external PRs is what actually happens after the merge — which the system does not measure. An autonomous maintainer with no post-merge feedback loop and no outcome-tripped stop can degrade a repo monotonically and never notice.
Proposed direction (concrete, staged; reuses existing components, no new external APIs)
Stage A — Post-merge outcome recorder (read-only, no writes). For each low-risk PR the bot recommended-and-that-merged, persist {repo, pr_number, base_sha, head_sha, merge_commit, decision, band} to the durable queue (state.py), owner-only 0600. Watch a declared outcome signal for that merge commit — main-branch check-runs / commit status / workflow-run conclusion — by extending the read-only client (github.py) with the missing read endpoints only, keeping it mutation-free by construction. Emit an owner-only outcome record per merge. This stage changes nothing the bot does; it only starts measuring.
Stage B — Outcome-tied circuit breaker (the kill-switch). Compute a rolling merge-error rate from Stage-A records (fraction of the last N autonomous merges whose declared outcome went red within a window). When it crosses an operator-set threshold, trip a hard stop: force dry_run on, stop claiming new jobs, require an explicit operator re-arm to resume. Wire the trip check as a precondition inside run_once() / serve_forever() (service.py:199-217) and persist the tripped state durably so a restart cannot silently clear it. This is categorically different from the existing switches: those are configured up front; this is tripped by observed harm, and it is safe in both directions because tripping only ever reduces capability.
Stage C — Revert as a non-executable, receipt-bound proposal. When a regression is confirmed for a specific merged PR, generate the revert as a non-executable ActionIntent — a GITHUB_WRITE owner action (factory.py:90-99, 137-167) for which can_auto_execute() still returns False — bound by digest to {merge_commit, regression evidence} so it is exactly-approvable. Route execution through the owner-action execution gateway (external signing, exact approval binding, idempotency, audit; ROADMAP §4). Emit a per-decision receipt for the revert via benchmark/attestation.pybuild_evidence so the corrective action is independently auditable.
Honesty on what is automated: detection (A), the outcome-tripped hard stop (B), and a bounded, receipt-bound revert proposal (C). The revert write itself stays human-gated.
Acceptance criteria
Post-merge outcome record persisted (owner-only, durable) for every autonomously-merged low-risk PR, capturing {repo, pr_number, base_sha, head_sha, merge_commit, decision, band, outcome_signal}.
The read-only client gains only read endpoints; a test asserts github.py still exposes zero mutation methods.
A rolling merge-error rate is computed over an operator-configurable window/threshold.
Crossing the threshold trips a hard stop that (i) forces dry_run, (ii) halts new job claims, and (iii) survives process restart until explicit operator re-arm.
A test proves a tripped breaker cannot be cleared by restart alone and blocks all downstream effects while tripped.
A confirmed regression produces a revert ActionIntent bound by digest to {merge_commit, evidence}; can_auto_execute() returns False; a test proves it cannot reach an external effect without the gateway.
Each revert proposal carries a report_data-bound evidence bundle reproducible offline by the owner.
Circuit breaker and revert proposal are inert on privileged paths: a test asserts a regression on an incentive/weights/payment path yields no automatic revert intent and no auto-action.
Docs distinguish this outcome-tied envelope from the existing static pre-merge guardrails.
The revert executes only through the owner-action execution gateway (external signer; ROADMAP §4) — a separate, permanently human-gated track; this issue produces the bound intent and evidence, not the gateway.
Out of scope / stays human-gated
Incentive / consensus / emission / weights / payment / wallet code paths stay PERMANENTLY human-gated. The circuit breaker and revert-proposal apply only to the low-risk surface (docs, tests, mechanical refactors). A regression touching a privileged path never receives an automatic revert intent and never any auto-action — it escalates to a human. Autonomy is never earned here.
Auto-execution of a revert (the actual GitHub write) stays gated behind the owner-action execution gateway with external signing and human approval. This issue automates detection, the hard stop, and a bounded proposal only.
No change to benchmark scoring, the Pareto floor, or the existing static pre-merge guardrails — this is additive.
Problem (current state, with file refs)
Every safety mechanism vanguarstew ships today is static and pre-merge — it inspects who authored a PR, what paths it touches, or how it scores before any effect lands, and then relies on the fact that the runtime never acts at all. Nothing observes what happens after a change is merged, and nothing trips on a bad outcome.
scripts/benchmark_pr_policy.pyauto-closes out-of-surface PRs;.github/workflows/pr-target-check.ymlauto-closes any PR tomainwhose author is notmatedev01; plus thepr-gaming-policy,pr-integrity,pr-limit,pr-reopen-policy,pr-source-checkworkflows. All key off identity/path/shape, not results.scripts/score_pr_delta.pybands a benchmark delta with a Pareto floor; it is explicitly a reporter, always exits 0, and only looks at tworun_evalartifacts produced before merge.vanguarstew_runtime/github.py:1-6;service.py:56-64);dry_run=trueis the default;can_auto_execute()returns False "ever" (openvang/factory.py:434-438).There is no post-merge regression detector, no revert path, and no outcome-conditioned kill-switch anywhere in the repo (grep-confirmed:
rollbackoccurs only as SQLiteROLLBACK;revertonly as a commit-kind classifier). Tellingly,agent/decider.py:51already asks the model to weigh "stability, blast radius, rollback cost" while the system has no rollback capability at all. The existing kill-switches (dry_run,allow_external_inference, the per-repo allow-list) are all input-conditioned — set once by the operator up front — never tripped by observed harm.Why it blocks autonomous external maintenance
Once autonomy is earned on a low-risk surface (docs, tests, mechanical refactors) of an external product repo, the pre-merge gates stop being sufficient: a merge can be individually "safe" under every gate and still regress the live system. On an external repo there is no benchmark delta to gate on (that apparatus only exists for changes to our own
agent/**), and the pre-merge judge signal that would substitute currently saturates and cannot discriminate (#2379). So the only trustworthy discrimination signal for external PRs is what actually happens after the merge — which the system does not measure. An autonomous maintainer with no post-merge feedback loop and no outcome-tripped stop can degrade a repo monotonically and never notice.Proposed direction (concrete, staged; reuses existing components, no new external APIs)
Stage A — Post-merge outcome recorder (read-only, no writes). For each low-risk PR the bot recommended-and-that-merged, persist
{repo, pr_number, base_sha, head_sha, merge_commit, decision, band}to the durable queue (state.py), owner-only 0600. Watch a declared outcome signal for that merge commit — main-branch check-runs / commit status / workflow-run conclusion — by extending the read-only client (github.py) with the missing read endpoints only, keeping it mutation-free by construction. Emit an owner-only outcome record per merge. This stage changes nothing the bot does; it only starts measuring.Stage B — Outcome-tied circuit breaker (the kill-switch). Compute a rolling merge-error rate from Stage-A records (fraction of the last N autonomous merges whose declared outcome went red within a window). When it crosses an operator-set threshold, trip a hard stop: force
dry_runon, stop claiming new jobs, require an explicit operator re-arm to resume. Wire the trip check as a precondition insiderun_once()/serve_forever()(service.py:199-217) and persist the tripped state durably so a restart cannot silently clear it. This is categorically different from the existing switches: those are configured up front; this is tripped by observed harm, and it is safe in both directions because tripping only ever reduces capability.Stage C — Revert as a non-executable, receipt-bound proposal. When a regression is confirmed for a specific merged PR, generate the revert as a non-executable
ActionIntent— aGITHUB_WRITEowner action (factory.py:90-99,137-167) for whichcan_auto_execute()still returns False — bound by digest to{merge_commit, regression evidence}so it is exactly-approvable. Route execution through the owner-action execution gateway (external signing, exact approval binding, idempotency, audit; ROADMAP §4). Emit a per-decision receipt for the revert viabenchmark/attestation.pybuild_evidenceso the corrective action is independently auditable.Honesty on what is automated: detection (A), the outcome-tripped hard stop (B), and a bounded, receipt-bound revert proposal (C). The revert write itself stays human-gated.
Acceptance criteria
{repo, pr_number, base_sha, head_sha, merge_commit, decision, band, outcome_signal}.github.pystill exposes zero mutation methods.dry_run, (ii) halts new job claims, and (iii) survives process restart until explicit operator re-arm.ActionIntentbound by digest to{merge_commit, evidence};can_auto_execute()returns False; a test proves it cannot reach an external effect without the gateway.report_data-bound evidence bundle reproducible offline by the owner.Dependencies
report_datareceipt format.Out of scope / stays human-gated