Skip to content

Phase 13: model-driven work-routing + earned backend selection#58

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

Phase 13: model-driven work-routing + earned backend selection#58
RNT56 merged 4 commits into
mainfrom
feat/p13-routing

Conversation

@RNT56

@RNT56 RNT56 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Two fixes, one philosophy — don't let the harness hard-code a judgment the model/evidence should own, but keep the deterministic bounds. Adversarially reviewed (CLEAN on I2 / default-off / I3); the confirmed quality findings are fixed.

Fix A — the model classifier is authoritative for work-routing

NilCore's North Star puts coding-fluency judgment in the model, but a ≥40-word / 8-keyword string function (chatShouldSupervise) was overruling the frontier-model classifier that already proposes the route. internal/session/router.go reconcile() now returns the classifier's parseable route as-is; the heuristic survives only as the no-model / unparseable-output fallback (plus an OFF-by-default ClampDownToNative backstop). The classifier prompt gained a capability + cost manifest so it sizes by work, not wording. New -auto-supervise (default off) gives plain nilcore run the optional supervised seam (same caps as nilcore build; trigger is the same classifier when a native provider exists, else the heuristic). Default-off byte-identical; the verifier still judges every route (I2); blast-radius caps untouched.

Fix B — earn backend strength, don't assume native

New preferred_backend config + -prefer-backend flag, and -backend auto / -backends auto / config backend: auto: the system picks the best available backend (native / codex / claude-code — those whose CLI + key are present), ordered preference-first then by the verifier-judged Trust Ledger (trust.Replay → Order). A fresh install honors the stated preference; evidence overtakes it as races accrue — the ledger needs zero change because Order already preserves input order among unproven backends. Per-backend creds via the SecretStore seam (I3); the ledger/preference only order which backend runs — the verifier still judges (I2). Default-off byte-identical.

Review fixes (1 medium + 3 low, all opt-in paths)

  • availableBackends native probe now mirrors resolveProvider("native") exactly (honors NILCORE_MODEL + the default model) so -backend auto can't wrongly exclude a usable native backend (+ regression tests + a hermetic TestMain).
  • -prefer-backend validation moved to the single chokepoint (preferredBackendName) so every auto path rejects a typo uniformly.
  • -auto-supervise + a delegated backend with no native key now skips the seam gracefully instead of fataling mid-run.
  • corrected a stale chatShouldSupervise doc comment.

Gate

make verify green · go test -race green · golangci-lint run ./... 0 issues · internal/backend contract + go.mod/go.sum + internal/trust/internal/agent untouched (I1/I6) · default binary behavior byte-identical without the new flags. Built in an isolated worktree off origin/main — contains zero of the concurrent browser session's WIP.

🤖 Generated with Claude Code

RNT56 and others added 4 commits June 21, 2026 05:11
NilCore's North Star puts coding-fluency judgment in the model, but a >=40-word /
8-keyword string heuristic (chatShouldSupervise) was OVERRULING the frontier-model
classifier that already proposes the route. This stops the overrule.

internal/session/router.go:
- reconcile() now returns a PARSEABLE classifier work-route as-is (removed the
  heuristic upgrade native->supervise / downgrade supervise|project->native arms).
  The string heuristic survives ONLY as the no-classifier / unparseable-output
  fallback (fallback() unchanged). An optional, default-OFF one-directional
  ClampDownToNative backstop is the sanctioned operator lever (inert by default).
- classifierSys gains a capability + COST manifest: native = cheapest single loop;
  supervise = bounded fan-out (up to the operator caps), verifier-judged; project =
  supervised loop under an outer budget/deadline. The model sizes by work, not
  wording, and picks the cheapest honest route. Output contract {route,reason} and
  the 256-token cap unchanged; routing still logged metadata-only (route+reason_len,
  I5/I7); RouteContinue stays local (no model call).
- router_test.go: the three overrule cases now assert the classifier proposal wins
  (short no-keyword "rewrite the auth subsystem" -> RouteSupervise; 40 words of
  trivial chatter -> RouteNative); fallback + clamp-backstop covered.

cmd/nilcore/main.go: new -auto-supervise (default false) gives `nilcore run` the
optional supervised seam (wireAutoSupervise reuses the build path's supervisor +
project loop + caps verbatim; trigger is the same model classifier when a native
provider exists, else chatShouldSupervise). OFF => Project/ShouldSupervise stay nil,
`nilcore run` byte-identical (proven by TestWireAutoSuperviseDefaultOff).

No blast-radius bound touched: caps, budget wall, verifier (I2) all unchanged; the
verifier still judges every route. stdlib only; contract files untouched.

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

Stops treating native as the privileged default. Neither codex nor claude-code is
better a priori — so let the operator state a preference and let the SYSTEM pick the
best AVAILABLE backend, seeded by that preference and learned from verifier-judged
evidence (the Trust Ledger). Default-off byte-identical.

internal/onboard: Config gains `preferred_backend` (the durable prior) and Backend now
also accepts "auto" (the durable "always decide for me"). Validate(): Backend ∈
{native,codex,claude-code,auto}; PreferredBackend ∈ {native,codex,claude-code} (auto
rejected — a preference must name a concrete backend). wizard onPath → exported OnPath.

cmd/nilcore:
- availableBackends(cfg,cred): the subset usable on THIS host — native iff an executor
  model + its key resolve; codex iff `codex` on PATH + CODEX_API_KEY; claude-code iff
  `claude` on PATH + ANTHROPIC_API_KEY (creds via the SecretStore seam — I3).
- resolveAutoBackend: orders available PREFERENCE-FIRST (-prefer-backend > cfg
  > native) then by trust.Replay(log).Order() — the existing ledger ranks earned
  backends ahead and PRESERVES the preferred-first order among unproven ones, so the
  cold-start needs ZERO trust-leaf change; evidence overtakes preference as races
  accrue. Broken chain ⇒ logs trust_replay_error + keeps preferred order (fail-soft,
  never aborts). Emits a metadata-only `backend_auto` event (names only). Empty avail
  ⇒ a clear `nilcore init` error, not a panic.
- `-backend auto` resolves to one best backend before resolveProvider (runMain +
  serve + buildRunOrchestrator, so config backend:auto flows everywhere). `-backends
  auto` expands to ALL available (ledger-ordered competition), collapsing to the
  single path on a native-only host. New `-prefer-backend` flag.

I2: preference/ledger only ORDER which backend RUNS; the verifier still judges every
race + the final gate (internal/trust + the verifier untouched). No auto + no
preference ⇒ exact current behavior (TestRunDefaultNoAutoIsByteIdentical). stdlib only;
backend.go (I1) + go.mod untouched. make verify green; golangci-lint 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adversarial review came back CLEAN on I2 / default-off / I3 (all refuted); these
close the confirmed quality findings (1 medium + 3 low, all on the opt-in paths).

- MEDIUM: availableBackends' native probe was STRICTER than resolveProvider("native")
  — it keyed only off cfg.Executor and ignored NILCORE_MODEL + the built-in default
  model, so `-backend auto` could wrongly exclude a native backend that `nilcore run`
  resolves fine (spurious "no usable backend" or a delegated mis-pick on a
  NILCORE_MODEL-only / default-model host). Now resolves the SAME spec
  (modelSpec(NILCORE_MODEL, cfg.Executor)) and gates on its provider key — the two
  paths agree. + regression tests (default-model + NILCORE_MODEL cases) and a TestMain
  that unsets NILCORE_MODEL so the probe is host-deterministic.
- LOW: -prefer-backend typo was validated only in run/serve, not the
  buildRunOrchestrator commands (watch/schedule/propose-edit) — a typo was silently
  dropped. Validation moved into preferredBackendName, the single chokepoint every
  auto path flows through, so it fails loudly + uniformly.
- LOW: -auto-supervise + a delegated backend (codex/claude-code) with no native key
  fataled mid-run when a goal sized complex. The supervised seam is an enhancement,
  never required, so it now logs auto_supervise_skipped + continues as a single-task
  run instead of aborting.
- LOW: corrected chatShouldSupervise's stale doc comment (the router no longer
  upgrades/downgrades the classifier — the heuristic is fallback-only now).

I2/I3 intact (verifier still sole authority; events name-only). make verify green;
golangci-lint 0; stdlib only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
README (auto/preferred backend + classifier-decides-routing + -auto-supervise),
CHANGELOG [Unreleased] entry, a minimal ARCHITECTURE note on the work-route authority
+ auto-backend seam, and the CONVERSATIONAL §3.4 router (classifier authoritative,
heuristic = fallback).

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