Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_KEY collides 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_KEY for 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, agent or decision, 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_one and decisions join 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_env to the role's variable, CRUCIBLE_INFERENCE_KEY_AGENT or CRUCIBLE_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_KEY or ANTHROPIC_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_INFERENCE included.

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_URL or CRUCIBLE_INFERENCE_WIRE_API from 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 decisions binding, 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.