Skip to content
Like what we’re building? Star on GitHub

Backend capability registry — seed and review contract ​

pkg/backend/backendcaps is the declarative leaf shared by compiler and runtime. The seed observes commit 9e5a31e2de75da413077bf5146432f6a470d77b3. It changes no permission, routing, tool, session or maturity verdict. Custom registries remain open: unknown names acquire neither capabilities nor compiler refusals. Lookups are exact; existing callers retain their normalization policy.

Shape and scope ​

registry.json has one row for each of the seven backends: claude_code, claw, codex, pi, grok, kimi, opencode. Each cell has a stable name, axis, verdict, evidence (repository file:line at the seed commit, or test name), and optional scope detail. The inventory records three different observations:

  • 168 implementation cells record existing wiring, using shipped, partial, typed-refused, inert, unwired (gap), unwired (no image) and explicit unknown. A partial capability still needs runtime checks.
  • 63 published proof cells preserve the nine columns of backends.md verbatim, including compound verdicts such as proven (events) · unknown (cost). A code path existing does not upgrade a published proof cell.
  • live_seed.json preserves all fields of the 57 target rows from the live ledger: 55 never, 2 pass (test:live:bot:rgaa-audit, test:live:claude-bg). There is no backend column in that ledger. A target pass is never assigned to every backend.

Rows also carry the established detection/preference ranks, system-prompt composition and native exclusion roster. Every returned slice/map is a copy. The package imports only the standard library and embeds its declarations; there is no repository-file read at runtime.

session-id-resume means consumption of Task.SessionID, independently of session-persist: claw offers persist but does not consume that ID; opencode consumes the ID but cannot pack its shared SQLite session store. Similarly, mcp-forwarding means delivery of Task.MCPServers, not all MCP support: claw resolves it in-process, and pi needs its RPC transport to serve forwarded servers. tool-list arrival is neither name resolution nor proof of an empty effective tool surface. The Claude roster stays at its existing 14 entries, including legacy Task; this does not resolve #1651.

image-presence is the one cell derived, not seeded: it answers "does a published image put this backend's executable on PATH?", and TestImagePresenceIsDerivedFromTheImageManifests measures it from the manifests that build those images (the CLI pins, the app image, the cloud runner, the sandbox bases and finalize). Seeded from prose, the cell certified the prose; derived, it certifies the image. Its evidence therefore names CURRENT lines rather than seed-commit lines, and the derivation — not the line number — is what holds. Two families differ: the runner images put the pinned node_modules/.bin on PATH, so every pinned CLI is reachable there, while the stock sandbox extends no PATH and only its explicit symlinks and the baked iterion binary are. A CLI reachable in one family and absent from the other is partial — codex is exactly that — and unwired (no image) means no image carries the executable at all, a gap in the image's contents that an operator image shipping the CLI lifts (#1643). The axis says nothing about multimodal image INPUT.

Because partial cannot say WHICH family serves, the cell carries the split structurally: scoped holds one verdict per published family (backendcaps.ScopedVerdict, read through UnservedByImageFamily) and subject names the executable an image must put on PATH — the adapter's own DefaultBinary, held to it by the same derivation. A reader that knows which image a run resolved asks the scope; one that does not reads the merged cell (UnservedByPublishedImages) and warns instead of refusing. Both are derived from the same measurement and folded, not declared twice, so the cell and its scopes cannot disagree.

The axes reference the still-open work in epic #1404: permission, resume, fork, session, MCP, subagents, workspace commands, allowed tools, bundle skills, image/presence, proxy/SSRF, metering, interaction and provider routing. None of its 19 sub-issues is implemented or closed by this seed. The claw ↔ claude_code doctrine is already settled; extending it to the five CLI-agent backends remains the epic's decision.

Enumeration inventory ​

The requested 14 core sites are all derived. Locations below name the pre-migration HEAD lines, so they remain reproducible with git show 9e5a31e2de75:<path> rather than drifting with this refactor. Function and variable names locate the current consumers.

#Site at seed HEADDerived fact
1pkg/dsl/ir/validate_fallbacks.go:52 — gateEnforcingModesPermission ask/deny membership
2pkg/dsl/ir/validate_fallbacks.go:67 — reasoningEffortBackendsEffort dial presence; sorted diagnostic output remains sorted
3pkg/dsl/ir/validate_fallbacks.go:163 — externalHookGateBackendsExternal gate hook requirement
4pkg/dsl/ir/validate_command.go:16 — commandIgnoringBackendsInert per-node binary override
5pkg/dsl/ir/validate_provider.go:57 — hintIgnoringBackendsInert provider chain
6pkg/dsl/ir/validate_async_backend.go:15 — validateAsyncBackendsExplicit async refusal; unknown routes still defer
7pkg/dsl/ir/validate_async_backend.go:62 — validateSyncInteractionBackendsInert sync interaction warning
8pkg/backend/toolcatalog/toolcatalog.go:50 — ConstrainsToolsRegistry name resolution
9pkg/backend/toolcatalog/declared.go:56 — ReceivesToolListTool-list delivery
10pkg/backend/delegate/claude_code.go:78 — claudeNativeToolsTwenty-name roster (the legacy 14 plus the six #1651 restored), alphabetical
11pkg/backend/detect/detect.go:87 — DefaultPreferenceOrderclaude_code,claw, unchanged
12pkg/backend/detect/detect.go:124 — detectBackendsFive probes in legacy order; kimi/grok remain unprobed
13pkg/backend/model/executor_retry.go:520 — sessionResumeBackendsSession-ID consumption
14pkg/backend/model/executor_persist.go:46 — SessionResumeCapabilityPortable persistent-session support

The search found additional mechanically derivable sites; 14 is not a claim that the repository contains only 14 enumerations:

Site at seed HEADDisposition
pkg/backend/model/executor_build_task.go:1561 — mcpForwardingBackendsDerived Task.MCPServers delivery
pkg/backend/automemory/automemory.go:57 — supportedBackendsBoth support and authored-prompt requirement derived
pkg/llmroute/policy.go:83 — HarnessesDerived names, preserving presentation order; both error-message lists also derive from this slice
pkg/backend/delegate/delegate.go:242 — SystemPromptModeForBackendDerived composition metadata; unknown remains standalone
pkg/backend/model/executor_retry.go:495 — providerFallbackEligibleDerived positive provider-chain predicate, matching the compiler's negative predicate
delegate.Backend*, detect.Backend*, llmroute.Harness*, toolcatalog.ClawBackendCompatibility aliases to the leaf's constants

These other name switches/tables remain deliberately owned by their existing seams; the reason is a different computation, not an unrecorded verdict:

SiteWhy it remains / follow-up
pkg/backend/delegate/registry.go:36 — DefaultRegistryConstructor wiring requires concrete backend types. The conformance witness asserts every declared delegate has an adapter; claw is injected by the model executor, not this constructor. A future factory seam can own construction.
pkg/backend/detect/detect.go — probe adapter mapCredential-specific functions remain adapters. The registry decides membership/order; missing adapter wiring fails loudly. Adding kimi/grok probes is a new implementation, outside this seed.
pkg/backend/ambient/ambient.go:187 — backendVerdictsPer-policy verdicts and refusal reasons form a richer matrix. A later registry schema can carry those policy dimensions without flattening them into a support boolean.
pkg/llmroute/policy.go — ConsumesProviderHint, HonoursProviderHintValue, and model twinsSingle-value, chain and value-sensitive provider semantics differ. The chain predicate derives; the other dimensions retain their existing conformance tests.
pkg/server/effort.go:234 — handleEffortCapabilitiesModel-dependent effort levels and live Codex model discovery. The registry owns dial presence; moving the resolver algorithms requires an adapter interface, not a boolean conversion.
pkg/backend/model/capabilities.go:58, capabilities_public.go:57Provider/model capabilities (context windows, reasoning, temperature), not backend membership. Keep their dynamic modelspecs overlay.
pkg/backend/toolcatalog/capabilities.go:51, toolcatalog.go:289, refused.go:36Tool-level classes, the registered built-in union, and refusal wording. These are not backend lists; actual tool-registry conformance remains their witness. The backend's subagent refusal is separately visible in the table.
pkg/backend/sessionpack/archive.go:240 — sessionFileNameMatchesBackend-specific archive filename grammar. Persist eligibility derives above; moving the grammar needs a packer adapter.
pkg/backend/model/slash_commands.go:65Claw's expansion algorithm; Claude's native implementation lives in the CLI. Shared capability availability cannot replace either implementation.
cmd/iterion/run.go:155, remote_runs.go:590, resume.go:145; studio/src/api/backends.ts:7CLI prose has distinct presentation orders/legacy qualifiers; the TypeScript union needs an exported/generated contract. A follow-up can generate these views from the registry without silently changing labels or transport schema.
pkg/dsl/ir/diag_catalog.go:198, validate_tools.go:261, validate_fallbacks.go:513Explanatory diagnostic prose also describes context-dependent remedies. Membership checks derive now; generated prose needs to preserve transport/sandbox qualifications.

Witnesses and how to mutate them ​

Run the free package guard with:

sh
devbox run -- go test -mod=vendor ./pkg/backend/backendcaps -count=1

registry.golden.json is the explicit review surface. Updating it is deliberate:

sh
devbox run -- go test -mod=vendor ./pkg/backend/backendcaps -run '^TestRegistryGolden$' -update-backendcaps

That command does not update the independent HEAD enumeration oracle, the documentation, or the ledger. A future implementation/proof change must reconcile those witnesses explicitly; editing the golden alone cannot waive the corresponding guard. The historical source commit remains historical.

WitnessProduction mutation that must turn it red
TestSeedZeroMoveChange codex permission-ask from typed-refused to shipped; replace Claude's Task roster entry with Agent; alter a probe/preference rank or prompt composition. It compares independent pre-refactor literals, not generated expectations.
TestRegistryGoldenChange any cell detail/verdict/evidence without updating the golden. The entire inventory and full live observation are compared.
TestLiveLedgerZeroMoveExchange a pass and a never between targets in live_seed.json, preserving the totals. The exact target-to-verdict and metadata comparison must fail.
TestPublishedProofZeroMoveUpgrade a proof cell from unknown to proven, or rename/re-axis a proof column while retaining its verdict. Both identity and value must agree with the nine published columns.
TestInventoryIntegrityMisspell an implementation verdict as shippped, duplicate a backend/cell/rank, remove evidence or cite an absent test.
TestCopiesAndUnknownsReturn inventory directly from Snapshot, or return the native roster without cloning it. Independent before-images detect either leak. Normalizing unknown names into a registered name must also fail.
TestImagePresenceIsDerivedFromTheImageManifestsDrop a CLI pin from docker/llm-clis/package.json, stop symlinking claude in sandbox/finalize/Dockerfile, or take the pinned node_modules/.bin off the runner PATH, and leave the cell as it reads: the registry then certifies an image fact that is no longer true. Registering an eighth backend without declaring the executable its image must ship fails the same guard.
TestImagePresenceMovesWithTheManifestsMake the derivation answer from a constant table instead of reading the manifests: the five scoped mutations — applied to a copy of the manifests, never the repository — stop moving their verdict, in both directions (a CLI taken out, and one put in).
model.TestBackendCapabilityConsumersConformReplace the provider-chain eligibility consumer with return false; the actual consumer must disagree with the registry on claude_code.

Existing consumer tests remain independent behavioral witnesses: the Claude native-roster literal assertion, IR permission/interaction diagnostics, session packer capability tests, detect credential isolation, and toolcatalog's real registry conformance. No live model is needed or run for this migration.

Epic wording and delivery boundary ​

The stream path plans/debt-leap/plan/streams/backend-capability-registry.md is absent from this worktree. GitHub's API is unreachable from this session, and the mission confines writes to this worktree. The remote epic body is therefore not edited. Its stale future wording can be replaced with:

The seven shipped backend registrations include opencode. The declarative capability registry records their existing implementation and proof cells; it does not extend the claw ↔ claude_code parity doctrine to the five CLI-agent backends. That doctrine and the 19 sub-issues remain open.

No issue is closed, no claim is posted remotely, and no commit is created.

Review and validation journal ​

  • Plan review: independent HEAD oracle, target-level live evidence, separate resume/persist predicates and exact-name semantics are retained. A reviewer from a different model family is unavailable in this session.
  • Round 1 (two surfaces; opening estimate 6, ceiling 20 at 24 files): the consumer review executes 384 independent comparisons against HEAD, including 54 backend spellings and byte-identical diagnostics. Two registry guard gaps are fixed: unchecked implementation vocabulary and proof-column identity. Three residual mechanical enumerations are derived as well.
  • Round 2: the fixed guards survive re-attack. A native-roster alias mutation exposes a missing assertion; an independently cloned before-image closes it. No high or critical finding remains in either review.
  • Round 3 (bounded terminal verification): the independent native-roster assertion and named metering evidence are re-read and tested; no new high or critical finding.
  • Ten executed mutations turn red on their intended assertions, then are restored: permission membership, native roster spelling, swapped live target verdicts, published proof upgrade, golden-only detail, Snapshot alias, invalid verdict vocabulary, proof-column identity, native-roster alias, and a disconnected provider-chain consumer. No compile failure is counted as a mutation witness.

Validation uses devbox, an owned scratch GOCACHE, and a per-process Git configuration isolated from the operator's signing settings. The validation commands and final results are recorded below; no paid live target runs.

CheckResult
go build -mod=vendor ./...Pass through devbox with owned GOCACHE. The read-only host module stat-cache warning is non-fatal.
Full suites: backendcaps, automemory, toolcatalog, detect, ir, llmroutePass.
Full delegate suitePass, 358.739 s, with GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1. The initial attempt inherits the unavailable operator GPG signer in two scratch-repository tests; no repository Git setting is changed.
Model consumer/session/MCP/provider regression testsPass, 21 tests, including the real task builder, optional-session degradation, provider fallback and new consumer conformance witness.
Full model suiteEnvironment-blocked: TestRefreshModelSpecs_UpdatesGlobalRegistry panics because httptest cannot bind [::1]:0 (socket: operation not permitted). Not reported as a green suite.
Targeted golangci-lint across all touched packagesPass, 0 issues.
Map freshness and mutation gatePass; generated package index includes backendcaps.
gofmt / git diff --checkClean.
task checkStops in go vet ./...: pre-existing pkg/runner/usage_cap_launch_vars_test.go:991 references undefined assertNodeDeathPark (also lines 1037/1040). The same references exist in seed HEAD; no runner file is changed. The earlier pnpm cache-path failure is resolved by using the installed Corepack cache.

These environment/baseline failures are not waived and no unrelated repair is included. Re-run the full model suite where local sockets are available and task check after the existing runner test helper is repaired. No live target runs, no ledger moves, no commit.