ADR-0065: The controller hands the engine its models as the inference document
Status: accepted | Date: 2026-10-07
References: RFC-0001:C-INFERENCE, RFC-0001:C-ROUTE, ADR-0036
Context
The controller's provider registry decides which model a dispatch reaches: a launch's pin, else the domain's default for its workload class, else the platform's. Until now that answer reached the engine as harness-specific environment on the pod: the key under OPENAI_API_KEY or ANTHROPIC_API_KEY, a custom endpoint under OPENAI_BASE_URL or ANTHROPIC_BASE_URL, and the Codex wire API under CRUCIBLE_INFERENCE_WIRE_API, delivered through the per-run Secret of ADR-0036.
The engine already defines one typed document for this, the resolved inference bindings of RFC-0001:C-INFERENCE, with a role per binding. The controller never wrote it. Route tasks (RFC-0001:C-ROUTE) need a decision binding, so a deployed controller could not run a model-decided route at all.
Adding a decision key the old way does not work:
- A decision key projected as
OPENAI_API_KEYcollides with an OpenAI agent provider's key, and the per-run Secret refuses the collision. - With no agent binding, the engine reads the ambient
OPENAI_API_KEYfor opencode, pi and Codex, so a decision key under that name reaches the agent sandbox.
Registered OpenAI providers already hold the credential that OpenAI's Decisions API takes.
Decision
The controller renders CRUCIBLE_INFERENCE from the registry for every pod dispatch (a playbook run, a scope or rank turn, a grounded rank), with one binding per resolved role, and stops projecting harness variables.
- Roles. A dispatch default names a role,
agentordecision, alongside its scope and workload class. A decision default is accepted only for the playbook class, since autoresearch runs no route task. A launch still pins only its agent; the decision role resolves from defaults. - Which provider serves which role. Anthropic and Vertex serve agents through Messages. OpenAI serves agents through Responses and decisions through its Decisions API, with no URL. A custom provider serves whatever its protocol does, and
system_oneanddecisionsjoin the custom protocols as decision-only. A default, a fallback or a pin naming a provider that cannot serve its role is refused at the API and fails at resolution. - Keys. A provider that spends a secret binds
key_envto the role's variable,CRUCIBLE_INFERENCE_KEY_AGENTorCRUCIBLE_INFERENCE_KEY_DECISION, which no harness reads. The engine relays the agent's key into the sandbox under the names the harness reads (RFC-0001:C-INFERENCE), and keeps the decision key in its own process. In a credentials map, the entry the protocol's clients read (OPENAI_API_KEYorANTHROPIC_API_KEY) is the key. For an agent, the map's other entries are projected under their own names. For a decision, they are dropped. A System One secret is one bare key. - Local runs. The local executor reads no secret values, so it does not render the document. A local run keeps the
CRUCIBLE_*environment the controller was started with,CRUCIBLE_INFERENCEincluded.
Consequences
- A deployed controller can run model-decided routes on a registered OpenAI or System One provider, chosen per domain like an agent model.
- An agent and a decision model can share one OpenAI provider and its secret: the two roles never share a variable or a Secret key.
- Agent turns are configured by the document, as the contract intended. A pod no longer carries
OPENAI_API_KEY,OPENAI_BASE_URL,ANTHROPIC_BASE_URLorCRUCIBLE_INFERENCE_WIRE_APIfrom the registry, so a pack command that read those variables from the pod no longer sees them. - Dispatch images must run an engine at contract 1.15.0 or later. An older engine refuses a document with a
decisionsbinding, and an engine older than 1.7.0 ignores the document and starts agent turns without their key. - A decision default on autoresearch, a per-launch decision pin, and a fallback chain longer than primary plus fallback are not available. Adding a pin later needs a column on each launch table, not a new resolution path.
Alternatives Considered
Render the inference document from the registry for every pod dispatch, keys under role variables (accepted)
- Pros: One delivery path for both roles, through the document the engine already validates., No key lands under a name a harness reads, so a decision key never reaches a sandbox and two roles never collide., A decision provider is registered, owned, keyed and defaulted like any other provider.
- Cons: Every dispatch image must run an engine at contract 1.15.0 or later., Pack commands lose the harness variables they could read from the pod.
Add a decision binding and keep projecting harness variables for agents (rejected)
- Cons: Two delivery paths for one registry answer., The decision key still has to avoid every name any harness reads.
A separate decision-provider registry (rejected)
- Cons: Duplicates the ownership, secret reference, enablement and fallback rules the providers already have.
Pin a decision model per launch now (rejected)
- Cons: No launch needs it yet; defaults per domain cover the packs that route today.