Skip to content

Phase 13: multi-backend strength-routing (the Trust Ledger goes live)#57

Merged
RNT56 merged 4 commits into
mainfrom
feat/p13-multibackend
Jun 21, 2026
Merged

Phase 13: multi-backend strength-routing (the Trust Ledger goes live)#57
RNT56 merged 4 commits into
mainfrom
feat/p13-multibackend

Conversation

@RNT56

@RNT56 RNT56 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Closes the Trust Ledger feedback loop — the deferred follow-on the trust PR flagged now lands. Configure multiple coding backends and NilCore tries the historically-strongest first, then on a verify-fail races the distinct backends with the verifier picking the winner; the per-backend race_outcome events feed the ledger so it learns which backend wins on your codebase. Additive, default-off byte-identical. Adversarially reviewed — CLEAN on I2 / default-off / I3 / race-correctness / leaf-rule / contract; the one MEDIUM (an unguarded names[0]) is fixed.

Core (internal/agent, internal/trust)

  • agent.Selector seam (Select(ctx, Task, names) []string) — orders/filters which backend to run; the verifier still decides "done" and the race winner (I2). internal/trust.Selector implements it structurally (orders by the ledger's smoothed rank; nil/empty ledger ⇒ configured order), no agent import.
  • Orchestrator.{Backends, NewEnvFor, Selector}multiBackend() = len(Backends)>1 && NewEnvFor!=nil. executeSingle runs the trust-strongest first (NewEnvFor resolves the named backend fresh per worktree — no stale instance); raceEscalate races one candidate per distinct backend. Both gate on multiBackend(): unset ⇒ the existing NewEnv/Router/RaceN single path is byte-identical (existing tests git-diff-empty + a default-off test). orderBackends never returns empty (falls back to the configured set).

Wiring (cmd/nilcore)

  • -backends native,codex,claude-code on nilcore run (+ buildRunOrchestrator): empty ⇒ single -backend path (byte-identical); two+ ⇒ routing. Unknown name FATAL. multiEnvFactory reuses the per-worktree env machinery + swaps the backend by name via buildBackend; each backend resolves its own creds via the SecretStore seam, never to the model (I3). Selector = trust.NewSelector(trust.Replay(log)); a broken-chain replay falls back to configured order (never aborts). N× race cost capped by the budget meter.

Gate

make verify green · go test -race ./internal/{agent,trust}/... ./cmd/nilcore/... green · golangci-lint run ./... 0 issues · internal/backend contract + go.mod/go.sum unchanged (I1/I6) · default binary behavior byte-identical without -backends.

🤖 Generated with Claude Code

RNT56 and others added 4 commits June 21, 2026 03:48
…Selector)

Closes the Trust Ledger feedback loop — makes the shipped ledger LIVE — additively and
default-off byte-identical. When an operator configures >1 coding backend, the
historically-strongest is tried FIRST and a verify-fail races the DISTINCT backends with
the verifier as judge; the per-candidate race_outcome events then carry different backends,
which is the signal the ledger learns from.

- agent.Selector seam: Select(ctx, Task, names) []string — orders WHICH backend to run;
  the verifier still decides "done" and the race winner (I2). The ledger only biases.
- Orchestrator gains Backends []string, NewEnvFor func(dir,name) Env (resolves a NAMED
  backend FRESH per worktree — fixes the stale-instance problem), Selector. multiBackend()
  = len(Backends)>1 && NewEnvFor!=nil. executeSingle picks the best name (orderBackends)
  + builds it + emits a metadata-only `backend_select`; raceEscalate races one candidate
  per distinct ordered backend. Both gate on multiBackend(): UNSET => the existing
  NewEnv/Router/RaceN single path is byte-identical (existing tests are git-diff-empty +
  a default-off test proves it).
- trust.Selector implements the seam structurally (Order by smoothed rank; nil/empty ledger
  => names unchanged), no agent import (leaf rule, deps_test holds).

make verify green; -race green; golangci-lint 0; stdlib only; nothing wires the new fields
yet (the cmd -backends flag is the next commit) so the default binary is behavior-identical.

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

Activates the Trust Ledger's routing — the deferred follow-on now lands. ADDITIVE,
default-off byte-identical.

- `-backends native,codex,claude-code` (registerCommon): empty ⇒ the single `-backend`
  path (byte-identical); one name ⇒ same as `-backend`; two+ ⇒ multi-backend routing.
  Unknown name is FATAL. `-backends` wins over `-backend`. N× race cost capped by the meter.
- parseBackends (validate/dedup/order), multiEnvFactory (the NewEnvFor analogue: reuses the
  per-worktree env machinery — selectSandbox + the behavioral verifier — and swaps the
  backend by name via buildBackend; each name resolves its OWN provider/advisor and creds
  through the SecretStore seam, never to the model — I3), wireMultiBackend (the gate: <=1 ⇒
  leaves the new fields unset = byte-identical; >=2 ⇒ sets Backends/NewEnvFor/Selector from
  trust.Replay(logPath); a broken-chain Replay logs + emits trust_replay_error + falls back
  to configured order, never aborts; empty/missing log ⇒ configured order until evidence).
- Wired into runMain + buildRunOrchestrator. serve is out of scope (it builds no Orchestrator).

The verifier still governs every race + the final gate (I2 — the core enforces it). Tests:
default-off proof (all three fields nil without -backends), activation, per-name backend
swap, unknown-fatal. make verify green; -race green; golangci-lint 0; stdlib only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…es[0])

Review MEDIUM: a Selector is documented as ordering AND filtering, so a conforming one
could return zero names; executeSingle indexed names[0] unguarded. orderBackends now
falls back to the configured set (non-empty by multiBackend's len>1 precondition) when a
Selector drops everything — one guard defending both callers (raceEscalate already
guarded). + a regression test (empty Selector => runs the first configured backend, no panic).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 'locked into one model vendor' row + the nilcore trust line now note that with
-backends configured the Trust Ledger drives live verifier-judged backend routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@RNT56 RNT56 merged commit 990d367 into main Jun 21, 2026
@RNT56 RNT56 deleted the feat/p13-multibackend branch June 21, 2026 02:04
RNT56 pushed a commit that referenced this pull request Jun 21, 2026
… routing

Refresh the four in-scope docs to match merged Phase-13 work:

- HORIZON.md: add a "Shipped since" banner; mark A1 (Trust Ledger,
  PR #55/#57), A7 (trace explorer, PR #55) and live multi-backend
  strength-routing (PR #57) as SHIPPED; note A1's "honest boundary"
  (multi-backend not wired) is now CLOSED; update the ranked summary +
  the "next move" footer (A1 done → A2/A5 next). Remaining horizon ideas
  left intact.
- MULTI-AGENT.md: rewrite the stale §0 line-18 paragraph (one task /
  executePlanned not wired / dead-code budget Ledger / unhardened
  worktree.git) to today: single-task path + wired supervised project
  loop (ShouldSupervise → project.Loop), super.Supervisor bounded
  fan-out (caps), multi-backend routing (-backends + trust.Selector),
  swarm, charged budget meter, hardened git; add the wired-defaults caps.
- ARCHITECTURE.md: add the multi-backend strength-routing seam
  (agent.Selector + Orchestrator.{Backends,NewEnvFor} + trust.Selector +
  -backends, I2 preserved) to the routing row and the I-section seams;
  add internal/{trust,trace} to the layer map.
- CHANGELOG.md: close the trust+trace "Honest boundary" with a forward
  reference to the now-merged multi-backend routing-wiring entry.

make verify green (exit 0); doc-only, no code or invariant change.

Co-Authored-By: Claude Opus 4.8 <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