ADR-004 Execution engine selection
Generated — do not edit
This page is produced by scripts/generate-runtime-adrs.sh from
engineering-runtime/docs/04-design-decisions/adr-004-execution-engine-selection.md.
Source commit: 38eec00 (2026-08-04 UTC).
Hand edits are overwritten on the next sync and fail scripts/check-generated-adrs.sh.
Change the ADR in the runtime repo, then re-run the generator.
Relative links from the source ADR are rewritten to GitHub blob URLs.
Status¶
Adopted, and now implemented — see adr-005-provider-layer.md.
This ADR described an "Execution Planner" that selects the Engine for an operation, and honestly noted it did not exist. It exists now, in the form ADR-005 introduced: the Provider is the Execution Planner, scoped per platform rather than centralised. That refinement fits this ADR's reasoning better than a central planner would, for the same reason a central command registry fitted badly — the knowledge required to choose an engine is platform-specific, so it belongs with the platform.
The boundary this ADR exists to protect is preserved exactly: the planner lives in the runtime binary, never in the AI/Capability layer; AI cannot select an engine, override Binary Governance, or skip the lifecycle.
Complements adr-002-engine-boundaries.md (the runtime core carries no domain knowledge) and adr-003-ai-interface.md (AI never executes) with a sharper, engine-level boundary: AI decides what needs to happen; the runtime decides how — including which Engine performs it.
Context¶
Modern engineering platforms often provide more than one way to perform the same operation. For example, creating a GitHub repository can go through the GitHub REST API or through gh repo create (Command Engine); listing Kubernetes pods can go through the Kubernetes API or through kubectl get pods. A single engineering intent can have multiple possible execution paths, and the architecture needs a consistent rule for who decides which path is taken.
Decision¶
The AI layer must not directly select or control low-level execution mechanisms — which Engine (REST, Command, or File) performs an operation, or the mechanics of that Engine (retries, auth handshake, output capture, error handling). AI is responsible for understanding user intent and creating or selecting a Capability; the Engineering Runtime is responsible for determining the appropriate execution engine for each of that capability's steps and executing deterministically.
Architecture flow¶
User Intent
↓
AI Reasoning Layer
↓
Capability
↓
Engineering Runtime (auth, config, policy, dispatch)
↓
Provider ← the Execution Planner (ADR-005)
↓
Transport Selection (per operation, compiled, not guessed)
↓
┌─────────────────┐
│ REST Engine │
├─────────────────┤
│ GraphQL Engine │
├─────────────────┤
│ Command Engine │
├─────────────────┤
│ File Engine │
└─────────────────┘
↓
Platform (GitHub REST / GraphQL / gh CLI / filesystem)
Component responsibilities¶
AI Reasoning Layer — responsible for understanding user intent, interpreting engineering goals, selecting or generating capabilities, and providing required inputs. Not responsible for deciding execution engines, directly invoking providers, or bypassing runtime controls.
Capability Layer — capabilities represent engineering outcomes (e.g. github repo create, github pr list), describing what the user wants to achieve, required inputs, and expected outcome. Capabilities should not tightly couple users to implementation details. See ../02-architecture/capability-model.md.
Engineering Runtime — responsible for authentication, capability execution, execution planning, engine selection, security controls, result handling, and deterministic execution. Provides a controlled execution environment; see ../02-architecture/architecture-overview.md for the full lifecycle.
Execution Engines:
| Engine | Used when |
|---|---|
| REST Engine | The operation is a simple, stable, well-shaped API call — e.g. github repo list via GET /user/repos |
| GraphQL Engine | One round trip replaces several REST calls, or only GraphQL exposes the data — e.g. github repo summary |
| Command Engine | The vendor CLI is genuinely the better surface — e.g. github pr list via gh pr list, which resolves the repo from the working directory |
| File Engine | The operation manipulates local files |
(Engine list and selection criteria updated by ADR-005, which added the GraphQL Engine and made these the published rules a Provider applies when choosing a transport.)
See ../02-architecture/engine-model.md for the full Engine reference.
Example execution scenario¶
User request: "Create a GitHub repository."
- AI reasoning — AI identifies the intent and writes a capability step naming the operation:
provider: github,args: [repo, create, name=my-repo]. - Runtime dispatch — the runtime authenticates, resolves context, evaluates policy on the operation, and hands the arguments to the GitHub Provider.
- Provider decision — the provider resolves
repo createand returns a REST invocation (POST /user/repos). Nothing above it participated in that choice. - Execution — the REST Engine performs the call; the audit record notes
transport=rest.
If that operation were better served by the CLI, the provider would return a gh repo create invocation instead, and steps 1, 2 and 4 would be byte-for-byte identical. That substitutability is the property this ADR exists to guarantee — and ADR-005 is what finally made it real.
Why AI does not choose the engine¶
- Non-deterministic behaviour — different AI responses (or models) could choose different execution paths for the identical request, breaking "same request, same outcome." See
../03-core-concepts/deterministic-execution.md. - Reduced governance — if AI picks the engine, the runtime loses control over security policies, execution rules, auditing, and approvals tied to that choice.
- Tight coupling — capabilities would become dependent on AI's decisions rather than on stable runtime rules, undermining the portability a Capability is supposed to have (see
adr-001-capabilities.md).
Benefits¶
- Deterministic execution — the same capability follows predictable runtime behaviour every time.
- Provider abstraction — users interact with capabilities, never with provider implementation details.
- Better governance — the runtime maintains control over execution policies regardless of which capability or AI model is involved.
- Easier evolution — new engines can be added (e.g. a future Workflow Engine) without changing the AI reasoning model or the capability model:
Capability → Runtime → Workflow Engine, same as any other engine.
Design principles¶
- AI should focus on reasoning.
Intent → Capability. - Runtime should focus on execution.
Capability → Engine → Provider. - Execution decisions must remain deterministic. Same capability + same runtime rules = predictable execution.
What this looks like in the reference implementation today¶
Updated by ADR-005 — the mechanism described above is now built.
- The Provider is the Execution Planner.
runtime github repo listdispatches to the GitHub Provider, which decides that operation is served over REST;runtime github repo summaryis served over GraphQL;runtime github pr listshells out togh. The caller names an operation and never sees the choice. - The choice is compiled Go, not a runtime guess — declared per operation in the provider's operation table alongside the reasoning. Same request, same path, every time, which is what this ADR requires.
- The runtime core maps transport to engine (
engineFor()ininternal/runtime/runtime.go) and records it in the audit log. It never branches on it, and nothing above the provider may select it. - Capability grammar changed accordingly: a step names
provider:+args:, never an engine. See../02-architecture/capability-model.md.
Two of this ADR's original trade-offs are resolved:
- ~~"Capability authors must pre-select the engine shape today."~~ They no longer do.
- ~~"AI still needs enough knowledge to pick a valid step shape."~~ AI names operations from a published, discoverable table (
runtime github --help), validated before execution.
Still not built, deliberately: automatic fallback (try REST, fall back to CLI on failure). A provider's transport choice is fixed per operation at compile time. Reactive fallback would make the same request take different paths on different days — exactly the non-determinism this ADR exists to prevent. If fallback is ever added it must be explicit and declared, not reactive. See ../06-roadmap/future.md.
Consequences¶
Positive¶
Restates the load-bearing outcomes from "Benefits" above in decision-record terms — see that section for the full list:
- Deterministic execution and provider abstraction hold regardless of which AI (or human) authored the capability.
- Governance (policy, audit) applies uniformly to every execution path, because the runtime — not AI — is what picks the path.
- New engines can be introduced later without retraining or reshaping the AI reasoning layer.
Trade-offs¶
(Two original trade-offs were resolved by ADR-005 — see "What this looks like in the reference implementation today" above. What remains:)
- No automatic fallback exists, by choice. If the transport a provider chose is unavailable (a REST endpoint is down but the CLI would work), nothing falls back automatically. This is deliberate rather than pending: reactive fallback breaks "same request, same outcome."
- The transport choice is invisible at the call site. A caller cannot tell that
github pr listneedsghinstalled whilegithub repo listdoes not.runtime github --helpandruntime config validateexist to close that gap; the audit log records it after the fact. - The provider now carries the judgement. Choosing well per operation is a real design responsibility that used to sit with the capability author. A provider that chooses badly (CLI where REST would do, forcing a binary dependency for no gain) degrades everyone downstream, and nothing in the runtime catches that.
Final decision, restated¶
| Layer | Responsible for |
|---|---|
| AI Layer | Reasoning and intent understanding |
| Engineering Runtime | Execution planning and deterministic execution |
| Engines | Interacting with providers |
The runtime remains the trusted execution layer while AI remains the intelligence layer.