Skip to content

Devin CLI + opencode CLI as worker backends - #3

Merged
copeus merged 4 commits into
mainfrom
devin-opencode-backends
Jul 13, 2026
Merged

Devin CLI + opencode CLI as worker backends#3
copeus merged 4 commits into
mainfrom
devin-opencode-backends

Conversation

@copeus

@copeus copeus commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Adds Devin and opencode as opt-in, lower-trust, WORKER-ONLY headless backends (excluded from the arbiter panel — multi-provider family ≠ fixed backend family).

  • resolve-model.py + validate-manifest.py: both registered (tier→model maps; opencode multi-provider provider/model; xhigh stays codex-only; no-Haiku intact).
  • Worker scripts mirror the codex/cursor template (worktree-at-HEAD, timeout supervisor </dev/null, git-derived scope gate, job_result in $ART). opencode env-scrub is live-proven (leak-canary) — it can't borrow the dispatcher's provider creds (it authed with zero creds via inherited ANTHROPIC_BASE_URL).
  • Devin -p run VERIFIED LIVE with an authenticated account (exit 0, bare-text stdout; a retryable transient-capacity error class; cwd-reset noted). Devin session-id + rich failure-classifier are auth-pending (safe defaults).
  • Both CLIs installed locally and kept.

Codex-reviewed (cross-model). Selftests + bash-n + shellcheck green.

🤖 Generated with Claude Code

copeus and others added 4 commits July 13, 2026 10:40
Adds devin + opencode to resolve-model.py (backend enum + tier->model maps;
opencode uses provider/model strings, multi-provider) and validate-manifest.py
VALID_BACKENDS + worktree-required set. New adapter-devin.md (Devin -p headless,
--permission-mode dangerous; Devin -p run VERIFIED LIVE with an authenticated
account: exit 0, bare-text stdout, a retryable transient-capacity error class,
cwd-reset noted) + adapter-opencode.md (opencode run --format json, multi-
provider, MANDATORY env-scrub — opencode authed with zero creds via inherited
ANTHROPIC_BASE_URL). Both LOWER-TRUST, opt-in, WORKER-ONLY (excluded from the
arbiter panel — multi-provider family != fixed backend family). xhigh stays
codex-only; no-Haiku respected; no fabricated metrics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the codex/cursor worker template: worktree-at-HEAD (caller owns git
base), timeout supervisor with </dev/null + bounded output, job_result in $ART
(not $WT — avoids a false scope-gate BLOCKED), git-derived scope gate via
compound-v-scope-check.py (never self-reported). opencode worker does a
VERIFIED-live env-scrub (env -u ANTHROPIC_*/OPENAI_* — leak-canary proven) so it
can't borrow the dispatcher's provider creds, + a restrictive opencode.json.
Devin session-id/rich-failure-classification marked auth-pending (safe default:
empty session, 'other' class; empty-stdout-on-exit-0 -> error). bash -n +
shellcheck clean; E2E verified with inert stand-ins (success/BLOCKED/timeout).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HIGH: validator now REJECTS a reviewer job on backend devin/opencode
(worker-only enforced, not just documented); opencode.json backup/restore is
symlink-safe + exit-trap-guarded (no write outside the worktree); opencode
false-success closed (exit 0 with zero valid JSONL events / no final text ->
error). MED: env-scrub is now an env -i ALLOWLIST (leak-canary proven vs
ANTHROPIC_BASE_URL/API_KEY + OPENCODE_API_KEY); opencode provider/model shape
enforced in resolver + validator; '--' terminator before the opencode prompt
(live-verified leading-dash prompts); Devin --export dropped (was outside the
byte cap); glob-safe WRITE_ALLOWED split (set -f). Selftests + bash-n +
shellcheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n-string model)

opencode.json BACKUP artifact is now symlink-safe: private artifact dir
validated non-symlink, backup path must not pre-exist (rm -f before cp), and
restore refuses a symlink/tampered backup (fail-closed -> scope gate BLOCKS
rather than reading attacker-redirected content). Validator now rejects a
NON-STRING opencode model (42/list/dict) that previously bypassed the
provider/model shape check. Live-proven (mid-run symlink swap -> blocked) +
3 negative selftests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@copeus
copeus merged commit 280ca13 into main Jul 13, 2026
1 check passed
copeus added a commit that referenced this pull request Jul 13, 2026
…; usage-object conformance; advisor_calls derived

Codex findings #1(BLOCKER),#2,#3,#4: empty/string/negative usage no longer becomes measured:true 0/0;
aggregate emits null (not real 0) when nothing measured; advisor_calls aggregates independent of
token-measured; collect-results deep-validates the usage object + derives advisor_calls from the
per-job advisor log (<run-dir>/logs/<job-id>.advisor.jsonl).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
…ucer

Round 3 found four judgment-layer defects the validator cannot see, and one of
them would have deadlocked every run:

- The gate receipt had a home (task-6's schema) and a consumer (task-17's
  postcondition) and NO PRODUCER. With no writer, every receipt is missing, the
  authority re-derives for every job, and integration never completes. task-9's
  Gate stage now emits the six fields and Record persists them.
- task-17's own acceptance contradicted itself on the missing case -- 're-derive
  and that verdict wins' beside 'refuse until every job has a valid receipt'.
  MISSING now means RE-DERIVE (derived verdict authoritative); only a PRESENT
  receipt whose digests disagree is refused outright, because that is a forged
  claim rather than an absent one.
- The declared waves lied: round 2 added task-16 depends_on task-4 without
  moving task-16 out of wave 2, ahead of its own prerequisite. Recomputed to
  seven waves.
- Manifest AC-02 still demanded the mode-scoped default the spec had deleted,
  and two jobs explicitly disclaim it -- the reviewer would have been asked to
  rule on a deliberately unimplemented requirement.
- AC-06's repo-wide sweep was contradicted by task-14, which correctly leaves
  the unamended invariant in CHANGELOG history. Scoped to live files.

Also: task-17 drops write access to scope-check.py (invoking needs none, and
task-15 consumes that matcher one wave earlier); AC-24 and AC-25 give the
integration authority and the cross-vendor path feature-level criteria they
lacked; task-0 names the third target its CI glob activates; the plan's
partition map is refreshed for tasks 15-17.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
…stead of shapes

Three things, and each of them exists because the alternative fails silently.

SCOPED becomes its own cohort. `_cohorts` collapsed everything that was not fast-path
into `fullpipeline`, which was correct while there were two tiers and wrong the moment
the engine grew a third: a whole new population would have been folded into the
escalation-evidence figure `/v:status` renders beside fast-path precision, and nobody
reading that number would have known it had changed meaning. `tier2_lookup` keeps its
`escalation_evidence_n` aggregate exactly as it was (Iron-Invariant #3 is unchanged --
a SCOPED outcome is still evidence, never health) and reports `scoped_n` /
`fullpipeline_n` alongside it, so the split is visible without shrinking a number other
readers already consume.

The decision strings and tier tokens are now READ FROM the engine. This file carried its
own `FASTPATH_DECISION = "FASTPATH_ELIGIBLE"` literal, which was a second copy of a wire
vocabulary the engine owns -- fine while nothing changed, and exactly how two halves
drift apart when something does. `_preeval()` is deliberately not fail-soft: a load
failure raises rather than falling back to hardcoded strings, because a silent fallback
IS the duplicated vocabulary. A selftest reads this file's own source and fails if any
decision string is ever assigned here again.

The circuit breaker counts NEGATIVE ACTUAL OUTCOMES, not demotions. An earlier revision
counted post-diff demotions alone and a cross-model review showed that cannot see the
failure it exists to catch: a mis-sized change that stays one allowed, non-sensitive,
sub-20-line file is never demoted, so twenty bad commits in a row leave the rate at zero
and the breaker armed. The numerator is a demotion, a CI failure on the resulting
commit, a revert of it, or a later escalation of the same work -- and the selftest builds
exactly that blind spot (four DIRECT decisions, zero demotions, four CI failures) so a
regression to demotion-only turns the suite red rather than quietly re-opening the hole.

Three details are load-bearing:

* It never delays the first decision. No warm-up gate, no min-sample floor of the kind
  `precision_stats` uses: an empty history is rate 0.0 and armed. The maintainer rejected
  a warm-up explicitly, so the safety property here is reactive, and the selftest proves
  both halves -- armed from decision one, disarmed once the rate crosses.
* It walks the RAW event sequence, not the last-writer-wins reduction. A later
  `predicted` at a higher tier REPLACES the DIRECT one under LWW, so a reduction-based
  breaker would watch an escalated decision vanish out of its own denominator.
* It reduces the WORKING-TREE stream while precision reduces the committed blob. Those
  answer opposite questions: precision must never be fabricated upward and so refuses
  what git cannot back; a safety latch must fail toward over-disarming, so a negative
  outcome that is recorded but not yet committed still disarms auto-route.

"Disarms itself ... until a human re-arms it" is a latch, so it is appended to the same
append-only stream as a `breaker` event rather than recomputed -- otherwise it un-disarms
by itself the moment the bad outcomes roll out of the rolling window. A `rearm` requires
a named human and sets the watermark the rolling rate counts from; without that
watermark the re-arm is a no-op, because the decisions that tripped the breaker are still
inside the window and the next evaluation re-disarms immediately. Nothing is erased: the
stream stays append-only and the whole history remains readable.

Selftest: 58 cases before, 110 after. Four mutations were run to prove the suite can
fail -- demotion-only numerator (13 FAIL), SCOPED re-collapsed into fullpipeline
(5 FAIL), a warm-up gate (8 FAIL), and a no-op re-arm (2 FAIL).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant