diff --git a/docs/AUTHORING-HOST-ADAPTERS.md b/docs/AUTHORING-HOST-ADAPTERS.md
new file mode 100644
index 0000000..9918adb
--- /dev/null
+++ b/docs/AUTHORING-HOST-ADAPTERS.md
@@ -0,0 +1,393 @@
+# Authoring a host adapter
+
+A walkthrough for adding a new agent CLI to `ak` without touching `ak`. The running example is
+**Hermes** — the fourth-host candidate this contract was designed against, and the adapter that
+would graduate it. Substitute your own host throughout.
+
+Read this alongside [ADR-0029](adr/0029-host-adapter-extension-point.md) (the contract) and
+[ADR-0031](adr/0031-capability-graduation-and-upstream-requests.md) (how a capability is earned).
+For what the kit does with a host once it knows about one, see
+[HOST-SUPPORT.md](HOST-SUPPORT.md) and [PROVIDERS.md](PROVIDERS.md).
+
+> **Experimental.** The whole surface is inert unless `AK_EXPERIMENTAL_HOST_ADAPTERS=1` is set, and
+> `contract: 1` can still change between alpha releases. See [The freeze](#9-the-freeze-and-why-you-matter).
+
+## 1. What you're actually building
+
+Two things:
+
+- **One JSON manifest** — a description of your host. Every value is JSON-serializable. A manifest
+ that cannot express a closure cannot smuggle one in.
+- **A handful of small hook scripts** — the only part that ever executes.
+
+**Nothing you write runs inside the `ak` process.** Every hook is a subprocess `ak` spawns,
+supervises, and owns the termination of. `ak` never calls `import()` or `require()` on a path you
+supply, and registering your adapter adds no npm dependency to `@pacphi/agentic-kit`.
+
+That constraint is also your leverage. ADR-0029 records what adding a host used to cost: a dedicated
+owner module, native surfaces reverse-engineered from scratch, and edits across roughly a dozen
+files and eight test suites — plus a permanent maintenance obligation on a maintainer who may not
+run your CLI. That collapses to a manifest and some hooks. You write no `ak` code, own no module in
+this repository, and reverse-engineer no internals.
+
+What you give up in exchange is real and is covered in [section 8](#8-what-earning-actually-gets-you-today).
+
+## 2. Write the manifest
+
+Name it whatever you like as a file; name it exactly `ak-adapter.json` at the package root if you
+publish it on npm. Here is a complete, valid one — this shape validates against `ak`'s real
+validator:
+
+```json
+{
+ "name": "hermes",
+ "version": "0.1.0",
+ "contract": 1,
+ "host": {
+ "id": "hermes",
+ "label": "Hermes",
+ "install": { "bin": "hermes", "externalInstallPolicy": "detect-never-overwrite" },
+ "capabilities": {
+ "canDriveSession": false,
+ "canBePrimary": false,
+ "canRouteActivities": true,
+ "commandStatusline": false,
+ "transcripts": false,
+ "usage": false,
+ "nativeMcpConfig": false,
+ "nativeGuidance": false
+ },
+ "trust": { "approvalPolicy": "unchanged", "changes": [] },
+ "enabledByDefault": false,
+ "configProjection": "ruflo",
+ "observability": []
+ },
+ "detection": {
+ "bin": "hermes",
+ "versionArgs": ["--version"],
+ "versionPattern": "\\d+\\.\\d+\\.\\d+"
+ },
+ "driving": { "surfaces": ["cli-subprocess"] },
+ "lifecycle": {
+ "detect": { "hook": { "command": ["node", "detect-hook.mjs"], "timeoutMs": 5000 } }
+ },
+ "execution": {
+ "run": { "hook": { "command": ["node", "run-hook.mjs"], "timeoutMs": 120000 } }
+ },
+ "trust": {
+ "changes": [
+ {
+ "id": "hermes-subprocess-hooks",
+ "kind": "third-party-adapter",
+ "scope": "project",
+ "owner": "hermes",
+ "value": "subprocess hooks",
+ "effect": "run consented lifecycle and execution hooks for hermes"
+ }
+ ]
+ }
+}
+```
+
+Field by field:
+
+| Field | What it's for |
+| --- | --- |
+| `name` / `host.id` | Your host's id. They must agree, and must not collide with a built-in. |
+| `host.label` | Human-readable name shown in status output. |
+| `host.install.bin` | Your CLI's binary name. `externalInstallPolicy: "detect-never-overwrite"` is the honest posture — `ak` finds your CLI, never installs or upgrades it. |
+| `host.capabilities` | All eight keys are **required**. Declare what your host legitimately does. |
+| `host.trust` / `trust.changes` | Up-front disclosure of what your adapter touches. `trust.changes` is what the user reads before consenting. |
+| `detection` | How `ak` proves your CLI is present: the binary, the version arguments, and a regular-expression source for the version. |
+| `driving.surfaces` | Declare `cli-subprocess`. See below. |
+| `lifecycle` / `execution` | Your hooks ([section 3](#3-write-the-hooks)). Both are optional; a manifest with neither is a pure description. |
+
+### Driving surfaces
+
+The vocabulary has three names — `cli-subprocess`, `acp`, `mcp` — but **`cli-subprocess` is the only
+one with a working implementation.** Declare an `execution` block without `cli-subprocess` in
+`driving.surfaces` and you get no execution adapter at all — the registration is refused
+`surface-unsupported`, never silently downgraded to a surface you didn't test against. `acp` and
+`mcp` are reserved for forward compatibility, and nothing drives them today.
+
+### The three capabilities you cannot claim
+
+`canBePrimary`, `commandStatusline`, and `aqeProvider` are **not yours to assert**:
+
+- `canBePrimary` and `commandStatusline` are required keys, and the schema accepts them **only at
+ `false`**. Writing `true` is rejected before anything runs (`cap-can-be-primary`,
+ `cap-command-statusline`). You cannot write down the claim; you **earn** the capability through a
+ conformance tier plus an explicit maintainer grant, recorded outside your manifest entirely
+ (ADR-0031 §1).
+- `host.legacy.aqeProvider` must be absent — `cap-aqe-provider` refuses any value. This one is not
+ earnable at all through `ak`: agentic-qe's provider set is a closed upstream enumeration, so being
+ an AQE provider type is upstream's to grant. See [section 9](#9-the-freeze-and-why-you-matter).
+
+Self-declaration is the attack surface, so it stays closed permanently. The *capability* is a ladder;
+the *declaration* is a wall.
+
+### One structural coupling worth knowing
+
+Declaring `execution` while `canRouteActivities` is `false` is a contradiction the schema refuses
+outright (`execution-not-routable`). The converse is fine: a routable host with no `execution` block
+is legal and degrades honestly at run time as `cli_unavailable`.
+
+## 3. Write the hooks
+
+Hooks are ordinary executables. They read stdin, read a few environment variables, print to stdout,
+and exit with a code. No SDK, no imports from `ak`.
+
+### The `detect` hook (lifecycle)
+
+Reports that your host is present and describes what it found. It prints a JSON object on stdout;
+`ak` parses it and returns it verbatim as the `detect` result.
+
+```js
+// detect-hook.mjs
+process.stdout.write(JSON.stringify({
+ observed: { host: 'hermes', bin: 'hermes', version: '1.4.2' },
+}));
+```
+
+`detect`, `plan`, `apply`, `verify`, and `undo` are the five lifecycle verbs. Declare only the ones
+you need — an undeclared verb is an honest no-op, not a fabricated success. `apply` and `undo` are
+what wire (and unwire) your host's own configuration when the user runs `ak setup` / `ak uninstall`.
+
+### The `execution.run` hook
+
+This is the one that actually drives your host as a worker under `ak run`.
+
+- **stdin** carries the worker prompt.
+- **the environment** carries `AK_WORKER_ID`, `AK_WORKER_ACTIVITY`, `AK_WORKER_ROLE`,
+ `AK_WORKER_MODEL`, and `AK_WORKER_CWD` (the repository being worked on — your hook does *not*
+ spawn there, see below).
+- **stdout** carries either a JSON object — `{summary, observedModel, provider, usage}`, all
+ optional — or plain text, which is taken as the summary.
+- **the exit code** is the sole authority for success.
+
+```js
+// run-hook.mjs
+let prompt = '';
+process.stdin.setEncoding('utf8');
+process.stdin.on('data', (chunk) => { prompt += chunk; });
+process.stdin.on('end', async () => {
+ const outcome = await driveHermes(prompt, {
+ cwd: process.env.AK_WORKER_CWD,
+ model: process.env.AK_WORKER_MODEL || undefined,
+ });
+ if (outcome.needsLogin) process.exit(78);
+ process.stdout.write(JSON.stringify({
+ summary: outcome.text,
+ observedModel: outcome.model,
+ provider: 'hermes',
+ }));
+});
+```
+
+### Exit codes
+
+| Code | Meaning | What the runner does |
+| --- | --- | --- |
+| `0` | Success | Reads your stdout payload. |
+| `77` | Permission refused — you need consent you don't have | Records `blocked` / `permission_required` and **never escalates**. Escalating around a consent boundary is the safety violation ADR-0019 forbids. |
+| `78` | Authentication required | Records `failed` / `auth_required`; the runner may retry on another host. |
+| other non-zero | Ordinary failure | Records `failed` / `worker_error`; may be escalated. |
+
+Codes `77` and `78` exist so you can say "I refused" or "I'm not logged in" honestly, instead of a
+bare non-zero exit that reads as a generic failure.
+
+### Rules `ak` enforces on every hook
+
+These aren't suggestions — they're the supervision that makes running your code safe, and they shape
+how you write it:
+
+- **Anchored working directory.** A hook spawns with its cwd pinned to *your adapter's own resolved
+ directory*, never `ak`'s. So `["node", "run-hook.mjs"]` resolves to **your** `run-hook.mjs`, and a
+ file planted in the operator's cwd is unreachable. This is why `AK_WORKER_CWD` exists: it's how
+ you learn which repository to work on. A *remote*-sourced manifest (`npm:` / `https://`) has no
+ local directory to anchor to, so a relative command from such a source is refused
+ (`execution-unanchored` / `lifecycle-unanchored`) — publish remotely and you must use absolute
+ paths or bare PATH binaries.
+- **Minimal environment.** Your hook gets `PATH`, `HOME`, and whatever `ak` injects for that verb —
+ never `ak`'s full environment. Don't expect to inherit the operator's secrets.
+- **Bounded output.** Captured output is capped at 256 KB and truncated with a marker beyond that.
+- **Bounded time.** Your declared `timeoutMs` and `ak`'s own budget both apply, and the tighter one
+ wins; with neither, a hook gets 30 seconds. On timeout the whole process group is killed — there is
+ no grace period, because the budget is already spent.
+- **No shell.** `command` is an argv array, spawned directly. There is no shell to quote against.
+- **stderr is never promoted.** Diagnostics you write to stderr are captured for the operator but
+ never folded into a downstream worker's prompt. Practical consequence: **write your JSON payload
+ to stdout only** — stderr chatter won't corrupt a valid JSON parse, but a plain-text summary is
+ discarded if you also wrote to stderr.
+- **Results never launder trust.** A `provider` you declare in your payload is stamped `inferred`,
+ never `observed` — `ak` didn't verify it against anything.
+
+## 4. Publish it, and let a user opt in
+
+Ship the manifest any of three ways:
+
+| Source form | Example | Note |
+| --- | --- | --- |
+| File | `~/.config/ak/adapters/hermes.json` | Fully anchored; relative hook commands work. |
+| npm | `npm:@you/ak-adapter-hermes` | Must ship `ak-adapter.json` at the package root. `ak` reads it from the tarball to stdout — nothing is extracted to disk and your package scripts never run. |
+| URL | `https://example.com/hermes.json` | No redirects, bounded time and bytes, no credentials attached. |
+
+A user then registers it in their own `kit.json`:
+
+```json
+{
+ "hostAdapters": [
+ { "name": "hermes", "source": "~/.config/ak/adapters/hermes.json", "contract": 1 }
+ ]
+}
+```
+
+and opts in:
+
+```bash
+export AK_EXPERIMENTAL_HOST_ADAPTERS=1
+ak host adapters trust hermes
+```
+
+`trust` prints the full validated manifest — every hook command that will spawn is right there in it
+— then asks for confirmation before pinning a hash of that content. **Edit the manifest afterwards
+and consent invalidates**: the adapter is not admitted again until the user re-confirms the new
+content. Consent lives outside your code, attached to a specific byte sequence, never to a name your
+content could drift underneath.
+
+Three more notes for your install docs:
+
+- Unattended consent to a **remote** source (`npm:` / `https://`) requires `--yes --expect-hash
+ `. `--yes` alone is refused for a non-file origin, so a CI run can never blanket-consent to
+ whatever the remote happens to serve. Publish the hash alongside your adapter.
+- With the flag unset, a `hostAdapters` entry is parsed and preserved but never admitted. It is
+ reported present-but-inactive, not silently dropped.
+- Your **lifecycle** hooks only run during `ak setup` / `ak sync` / `ak uninstall` when the user has
+ *also* explicitly enabled your host under `integrations.hosts` in `kit.json`. There is no pick-UI
+ for external hosts yet.
+
+## 5. Self-test with the conformance kit
+
+```bash
+ak host adapters conformance hermes
+```
+
+This runs the tiered black-box harness against your **real** host — spawning your actual hooks — and
+prints an honest per-tier verdict. It warns first, listing every hook command it is about to run as
+a real subprocess. Here is a real run against the repository's conformance fixture
+(an adapter shaped exactly like the manifest in section 2):
+
+```text
+host adapter conformance — acme (56fa107674d2)
+admission passed host id 'acme', contract 1
+session-driving skipped not declared — nothing to prove
+activity-routing passed registered for 'acme'
+primary-eligible passed 'acme' completed a direct (non-escalated) run to a succeeded result, and…
+statusline gated ak-local: awaiting maintainer grant for 'commandStatusline' (not an…
+```
+
+(Detail columns elided at the right margin; the real ones run longer.)
+
+What each tier means for you:
+
+| Tier | What it proves | What to expect |
+| --- | --- | --- |
+| `admission` | Your manifest validates, admits through the fail-closed gate, joins the registry, and your `detect` hook runs as a real subprocess. | **Can genuinely pass.** A failure here short-circuits every downstream tier, so nothing gets laundered. |
+| `session-driving` | Gates `canDriveSession`. | `skipped` if you don't declare it. **`gated` if you do** — `ak` has no external session-driving path, and being a native ruflo backend is upstream-owned. |
+| `activity-routing` | Gates `canRouteActivities`. A real one-worker `ak run` routed to your host returns a succeeded `WorkerResult`. | **Can genuinely pass.** |
+| `primary-eligible` | Earns `canBePrimary`. Your host anchors a real run *and* receives a genuine ADR-0019 escalation onto itself — a second real subprocess. | **Can genuinely pass**, with no pre-existing grant. |
+| `statusline` | Earns `commandStatusline`. | **`gated`.** There is no admitted-host footer-render path yet, so even a granted capability has nothing real to drive. |
+
+**A `gated` or `skipped` result on `session-driving` and `statusline` is expected, not your adapter
+failing.** The harness never fabricates a pass, and there is no injection seam through which a caller
+could substitute one. Only `failed` means something is wrong with your adapter.
+
+Evidence is hash-pinned to your manifest, so any edit voids it. And it's a two-way street: if a
+grant-bearing tier later re-runs `failed` at the same manifest hash, the stored evidence *and* the
+live capability are auto-voided.
+
+## 6. Propose it for graduation
+
+You don't need a maintainer to ship. Publish, and any user can opt in behind the flag with pinned
+consent — that path is entirely yours.
+
+To go further, hand a maintainer your conformance report. They:
+
+1. **Re-run the conformance kit** themselves — conformance is objective, and trust rests on
+ reproduced evidence rather than your report.
+2. **Read your hook scripts** — the only part that executes.
+3. **Decide** the tier and the destination.
+
+Two destinations, the maintainer's call:
+
+- **Blessed external adapter** — `ak host adapters bless hermes ` (`grant` is the same
+ command). Your adapter stays out-of-tree and experimental, holding exactly the capabilities its
+ tiers earned. A grant is refused unless the gating tier is recorded `passed` at the current
+ manifest hash, and it's re-checked at read time, not just at write time.
+- **Promoted built-in** — your host descriptor is adopted into the first-party registry. This is now
+ an ordinary PR: a registry entry, a lifecycle adapter, an About card. Once built-in, the caps no
+ longer apply, because it is first-party code the maintainer vouches for. That's what promotion
+ means.
+
+A tier you can't clear because the capability lives upstream is recorded as
+**`gated: #NNN`**, not failed:
+
+```bash
+ak host adapters gate hermes session-driving ruvnet/ruflo#1234
+ak host adapters status hermes
+```
+
+The maintainer champions that request upstream with a named extension point, and `status` shows
+exactly what your adapter is waiting on.
+
+## 7. The commands, in one place
+
+```bash
+ak host adapters list # configured adapters and their consent state
+ak host adapters trust # disclose the manifest, confirm, pin the hash
+ # (--yes --expect-hash for unattended/remote)
+ak host adapters revoke # withdraw consent (works with the flag off)
+ak host adapters conformance # run the tiered harness
+ak host adapters status # per-tier state + granted capabilities
+ak host adapters grant # maintainer: confer an earned capability (alias: bless)
+ak host adapters revoke-grant [cap] # withdraw a granted capability
+ak host adapters gate # record an upstream blocker against a tier
+```
+
+## 8. What earning actually gets you today
+
+Be clear-eyed about this, because the machinery is ahead of its consumers:
+
+- **A granted `canBePrimary`** raises the capability on your host's effective registry entry, so
+ `hostTierLabel` and the primary-eligible host set reflect it. But **no path yet *selects* an
+ external host as primary** — `ak host pick` is still built-in-scoped. You show as eligible; nothing
+ acts on it.
+- **A granted `commandStatusline` is currently inert.** There is no runtime reader for an
+ admitted host's command-backed footer. The grant records what you earned; nothing renders it yet.
+
+Both gaps are disclosed at grant time, and both are `ak`-local work rather than upstream ceilings —
+they will light up without needing anything from you. What works end-to-end today is
+`activity-routing`: a real `ak run` worker on your host, supervised, with a structured result.
+
+## 9. The freeze, and why you matter
+
+Two ceilings are genuinely not `ak`'s to lift:
+
+- **Native ruflo backend** (`session-driving`). ruflo's backend enablement is per-host and defined
+ inside ruflo, not through an outside registration surface. *Interim:* your host runs through `ak`'s
+ own supervised execution — just not as a ruflo-native backend.
+- **agentic-qe provider type** (`aqeProvider`). agentic-qe's provider set is a closed upstream
+ enumeration, extended only by an upstream code change. *Interim:* quality still runs through the
+ model provider underneath your host, so QE isn't blocked — only your host's own AQE identity is.
+
+Neither is faked, hidden, or shimmed. Each gets a tracked capability request and an honest interim
+behaviour ([ADR-0031 §4](adr/0031-capability-graduation-and-upstream-requests.md)).
+
+And the contract itself stays experimental until a **real external adapter clears the full
+conformance kit and survives one release of soak in the field with no contract-shape change**. That
+adapter could be yours. Until then, `contract: 1` may change between alpha releases — bounded
+instability that exists precisely so the first real adapter's conformance run can surface shape
+problems cheaply.
+
+So a real Hermes isn't just a beneficiary of this machinery. It's the test that graduates the
+contract from experimental to frozen.
diff --git a/docs/HOST-ADAPTER-FREEZE-CHECKLIST.md b/docs/HOST-ADAPTER-FREEZE-CHECKLIST.md
new file mode 100644
index 0000000..5004312
--- /dev/null
+++ b/docs/HOST-ADAPTER-FREEZE-CHECKLIST.md
@@ -0,0 +1,76 @@
+# Host-adapter contract freeze checklist (ADR-0029 §graduation, ADR-0031 §6)
+
+The external host-adapter contract (`contract: 1`) is **experimental** and behind
+`AK_EXPERIMENTAL_HOST_ADAPTERS=1`. This is the falsifiable checklist for freezing it —
+turning `contract: 1` into a guaranteed-stable shape and dropping the flag. **Do not claim
+the freeze until every box is genuinely checked against a real adapter.** The freeze is not
+a maintainer's assertion; it is an earned, evidenced event.
+
+## The freeze criterion (ADR-0031 §6, ADR-0029 graduation gate)
+
+Freeze requires **all** of:
+
+1. A **real external adapter** — maintained *outside this repository, by someone other than
+ an agentic-kit maintainer* (Hermes is the expected first) — passes the full conformance
+ kit against its real host.
+2. That adapter completes **one full release's worth of soak** in the field with **no
+ contract-shape change** required to keep it working.
+3. Only then does the manifest **contract integer** freeze: `contract: 1` becomes stable,
+ and any later breaking change ships as `contract: 2`, admitted alongside `contract: 1`.
+
+Tier graduation and the contract freeze are distinct: an adapter can earn capability tiers
+while the contract is still experimental.
+
+## Readiness — machinery the freeze rests on (all shipped; verify before soak)
+
+- [ ] Admission gate, hash-pinned consent, subprocess hook-runner, `admission` tier
+ (ADR-0029).
+- [ ] `ak host adapters trust` / `list` / `revoke` (+ `--expect-hash`).
+- [ ] Remote manifest sources (file / `https` / `npm:`), resolve-before-hash.
+- [ ] `ak run` drives an admitted routable host (cwd-anchored, exit-code authority,
+ reserved 77/78, no trust laundering).
+- [ ] Admitted lifecycle execution through setup / sync / uninstall.
+- [ ] Tiered conformance kit: `admission`, `activity-routing`, `primary-eligible` pass
+ against a real fixture; `session-driving` / `statusline` honestly gated.
+- [ ] Capability-grant store + `grant`/`bless`, `gate`, `status`, `revoke-grant`; earned
+ capability enforced at read time; granted caps live in the effective registry.
+
+## Freeze gate — the real-adapter run (fill in with evidence, not intent)
+
+- [ ] **Real external adapter identified**, maintained outside this repo by a non-maintainer.
+ Adapter: `__________` Maintainer: `__________` Source: `__________`
+- [ ] **Conformance report captured** from `ak host adapters conformance ` against the
+ real host (attach or link the per-tier verdict).
+ - [ ] `admission` passed
+ - [ ] `activity-routing` passed
+ - [ ] `primary-eligible` passed *(or consciously out of scope for this adapter)*
+ - [ ] `session-driving` — passed **iff** the upstream ruflo backend-registration request
+ (`docs/upstream-requests/ruflo-backend-registration.md`) has shipped; otherwise
+ legitimately `gated` and recorded via `ak host adapters gate`.
+ - [ ] `statusline` — `gated` remains acceptable at freeze (its render path is a later wave);
+ the freeze is of the **contract shape**, not of every tier passing.
+- [ ] **Hooks read** by a maintainer (the only executing part).
+- [ ] **Grant/bless decision** recorded (blessed external adapter, or promoted built-in).
+- [ ] **Soak: one full release** elapsed with the adapter in the field and **no
+ contract-shape change** required. Release soaked through: `__________`.
+- [ ] **No `contract: 1` shape change** was needed during soak (if one was, the clock resets).
+
+## Only when every box above is genuinely checked
+
+- [ ] Set the manifest `contract` shape to frozen; document that a breaking change now ships
+ as `contract: 2` admitted alongside `contract: 1`.
+- [ ] Drop `AK_EXPERIMENTAL_HOST_ADAPTERS` gating for the frozen surface (or flip its default),
+ per the ADR-0029/0031 freeze decision.
+- [ ] Update ADR-0029 (graduation gate) and ADR-0031 §6 status to **frozen**, dated, with the
+ adapter + release that earned it.
+
+## Honest ceilings that do NOT block the freeze
+
+The freeze is of the **contract shape**, not of universal tier passage. These stay open by
+design and are recorded as gated, not as failures:
+
+- `session-driving` until ruflo ships a backend-registration surface (upstream request drafted).
+- An `aqeProvider` identity until agentic-qe ships a provider-plugin API (upstream request drafted).
+- `statusline` runtime rendering (no `ak` render surface for a third-party TUI yet).
+- Grant *consumption* last-mile: selecting an external host as primary, and a `commandStatusline`
+ runtime reader.
diff --git a/docs/HOST-EXTENSIBILITY-EXPLAINER.html b/docs/HOST-EXTENSIBILITY-EXPLAINER.html
index 5c4fd3a..f079c33 100644
--- a/docs/HOST-EXTENSIBILITY-EXPLAINER.html
+++ b/docs/HOST-EXTENSIBILITY-EXPLAINER.html
@@ -199,7 +199,7 @@
Is this what lets us use Gemini CLI, Hermes, and others?
-
Yes — this is the foundation that makes those possible.
- It is not yet a finished on-ramp an external assistant can be driven across today. What exists now:
- the kit can recognize and register an external host from a manifest, safely and
- reversibly, behind an experimental switch. What comes next is turning that recognition into
- running work through it. The staging is spelled out in
- section 5 — honestly, so nobody expects more than is there.
+
Yes — and the on-ramp is open. Behind an experimental switch, an
+ external host described by a manifest can be approved
+ (ak host adapters trust discloses it and pins its exact content), run
+ (ak run drives it as a supervised worker, as a real subprocess), and
+ certified (a tiered conformance kit exercises it against your actual CLI). What's
+ still genuinely ahead is narrower than it used to be: two conformance tiers remain gated
+ — session-driving waits on upstream, statusline on a render surface that isn't
+ built — and an earned capability is only partly consumed, since nothing yet selects an external
+ host as primary. The one thing the kit can't supply is the adapter itself: no Hermes or Gemini CLI
+ manifest exists yet, so using one of those still starts with someone writing it
+ (the authoring guide is the walkthrough).
+ Section 5 has the honest limits, stated plainly.
2 · Two kinds of host
@@ -308,13 +314,14 @@
Built-in hosts here now
-
External adapters the foundation
+
External adapters experimental
Hermes · Gemini CLI · anything CLI-shaped
Described by a manifest the adapter author publishes — not bundled in the kit.
Enter only through the admission gate, only behind an experimental switch.
Cannot self-declare that they lead, bill, or own a status line.
-
Today: can be recognized and registered. Running work through them is the next step.
+
Today: trusted, run via ak run, and able to earn capabilities through
+ conformance — behind the experimental flag.
@@ -476,8 +483,8 @@
What the kit guarantees about an external host
status line; OpenCode: none). "Primary" means leading the session — a separate axis from
taking part in ak run, which OpenCode does as a supervised worker (unpacked in
"Leading vs. being supervised" just below). The safety point stands:
-an external adapter cannot self-declare any of these — the schema has no field to say so —
-but a capability can be earned via conformance
+an external adapter cannot self-declare any of these — the schema refuses the claim outright,
+before anything runs — but a capability can be earned via conformance
(section 7).
Leading vs. being supervised
@@ -572,36 +579,39 @@
Leading vs. being supervised
do is lead one — be the default, own the reasoning roles, or be the host escalation resolves
toward. That authority stays with a host that can be primary.
-
5 · Where we are — and what's honestly next
-
The door is built and its safety is proven. But a door a host can be registered through is
-not yet a door work can be run through. Here is the truthful staging.
+
5 · Where we are today
+
The door is built, its safety is proven, and work now runs through it. What remains is not
+plumbing — it's evidence: a real outside adapter has to walk the whole path before the
+contract can stop being experimental. Here is the truthful staging.
-Foundation laid, on-ramp staged. Only the first box is done. Each later
-box is a small, self-contained increment behind the same experimental switch — so nobody is exposed
-to a half-built external host by accident.
+On-ramp open; the proof is what's left. Admitting, approving, and running
+an external host all work today. The last two boxes can't be built — they have to be
+earned, by a real outside adapter clearing the conformance kit and then surviving a release
+in the field. The experimental switch stays on until that happens, so nobody meets a half-proven
+external host by accident.
What can be expected today
@@ -631,36 +643,42 @@
What can be expected today
shipped payoff, and it needs no flag.
The internals are ready for more — a new host is a registry entry or a
manifest, not a rewrite.
-
An external host can be recognized and registered from a manifest, safely and
- reversibly, behind AK_EXPERIMENTAL_HOST_ADAPTERS=1.
+
An external host can be trusted, run, and can earn capabilities — from a
+ manifest, safely and reversibly, behind AK_EXPERIMENTAL_HOST_ADAPTERS=1:
+ ak host adapters trust records hash-pinned consent, ak run drives it as a
+ supervised subprocess, and the conformance kit certifies it tier by tier.
Limitations — read before expecting an external host to "just work"
-
Work can't be run through an external host yet. The kit can admit one into
- its registry, but driving a task through it (the ak run path) is not built — an
- admitted external host currently reports "no execution available" rather than running.
-
There's no command to approve one yet. Approval is checked at admission, but
- the ak host adapters trust command that would record it isn't built — so in practice
- admission refuses until that lands. This is the intended next step.
-
Manifests are local files only. Fetching an adapter from npm or a URL isn't
- built; a manifest is a file path today.
An external host can't self-declare that it's primary, an AQE provider, or a
status-line owner. That block on self-declaration is permanent — it's the safety
invariant. But the capability itself is earnable: pass the matching conformance tier and
- get a maintainer's grant, and an adapter reaches full parity (the graduation ladder in
- section 7). One honest exception — being an AQE provider
- type isn't the kit's to grant; agentic-qe's provider list is defined upstream
- (section 8).
-
It's experimental. The contract version can still change; nothing about the
- external-adapter surface is promised stable until the final "Open" stage above.
+ get a maintainer's grant (the graduation ladder in section 7). One
+ honest exception — being an AQE provider type isn't the kit's to grant; agentic-qe's
+ provider list is defined upstream (section 8).
+
Two conformance tiers can't be cleared yet, by design.session-driving
+ is gated on an upstream ruflo backend, and statusline is gated
+ because the kit has no footer-render surface for a third-party host. They report
+ gated/skipped honestly — never a fabricated pass.
+
An earned capability isn't fully consumed yet. A granted
+ canBePrimary makes a host show as primary-eligible, but no path yet selects
+ an external host as primary (ak host pick stays built-in-scoped); a granted
+ commandStatusline is currently inert (nothing reads it at runtime).
+
It's experimental.contract: 1 can still change between alpha
+ releases; nothing about the external-adapter surface is promised stable until a real external
+ adapter clears the full kit and soaks — the final "Open" stage above.
6 · For implementers: the shape of an adapter
-
When the on-ramp is complete, authoring a host adapter will mean shipping one manifest and a few
-small hook scripts. The manifest looks like this (illustrative):
+
Authoring a host adapter means shipping one JSON manifest and a few small
+hook scripts — nothing else, and no code of yours ever runs inside the kit's own process.
+The step-by-step walkthrough lives in
+docs/AUTHORING-HOST-ADAPTERS.md; this is the
+shape of it.
+
The manifest (abridged — the guide has a complete, validating one):
+]
+
+$ export AK_EXPERIMENTAL_HOST_ADAPTERS=1
+$ ak host adapters trust hermes # discloses the manifest, then pins its hash
+
A manifest can also be published as an npm package shipping ak-adapter.json at its
+root, or as an https URL.
-
The conformance kit is the contract. A committed test harness admits a real
- fixture adapter, runs its hooks as real subprocesses, and refuses a corpus of bad manifests with
- exact reasons — the same bar a real adapter (Hermes first) must clear to graduate.
+
The hooks are the only code that runs — as supervised subprocesses. A hook
+ reads the worker prompt on stdin, gets the worker's identity in its environment, prints a JSON
+ result, and exits: 0 for success, 77 to refuse on a consent boundary
+ (never escalated), 78 for "not authenticated". The kit pins each hook's working
+ directory to the adapter's own bundle, hands it a minimal environment, bounds its output and its
+ time, and never promotes its stderr into another worker's prompt.
Approval is pinned to the bytes. Consent is a hash of the exact manifest;
edit one character and the kit asks again. Content is never approved unseen.
Refusals are specific. An unknown field, a forbidden capability, a bad
contract version, a name that collides with a built-in — each is refused by name, and one bad
adapter never disturbs the others or the built-ins.
+
Self-test before you ask anyone for anything.
+ ak host adapters conformance hermes runs the tiered harness against your real host and
+ reports each tier honestly. admission, activity-routing and
+ primary-eligible can genuinely pass — the last one by driving a real run and receiving
+ a real escalation onto your host. session-driving and statusline come back
+ gated (or skipped, if you never declared the capability) because those paths are
+ not built; that is expected, not your adapter failing. The harness never fabricates a pass.
+
+
What an author should not expect yet
+
+
cli-subprocess is the only working driving surface.
+ acp and mcp are named in the vocabulary for forward compatibility;
+ declaring one is refused, never silently downgraded.
+
An earned canBePrimary is only partly consumed. A grant does
+ raise the capability, so the host shows as primary-eligible — but no path yet selects an
+ external host as primary. An earned commandStatusline is currently inert: there is no
+ runtime reader for an admitted host's footer.
+
The contract is still experimental.contract: 1 can change
+ between alpha releases, and freezes only once a real external adapter clears the full kit and
+ survives a release of soak.
+
+
7 · From a contributor's idea to a built-in host
This is the part that ties it together: the exact sequence from "someone wants to add Hermes" to
@@ -1010,8 +1066,8 @@
What it doesn't — and who owns it
point and its safety model are ADR-0029 (which supersedes ADR-0016's original "closed registry"
stance, narrowly — nothing under a manifest is ever loaded into the kit's process). The graduation
ladder — experimental external adapter, earned capabilities via conformance tiers, promotion to
-built-in, and the upstream capability-request path to ruflo and agentic-qe — is the proposed next
-decision (a prospective ADR-0031), not yet ratified. Upstream facts (agentic-qe's closed provider
+built-in, and the upstream capability-request path to ruflo and agentic-qe — is ADR-0031, accepted as
+a governance decision with its machinery staged and its own implementation-status table. Upstream facts (agentic-qe's closed provider
enum; ruflo's ENABLE_* backend model) are grounded in a source-cited research sweep of
agentic-qe@3.13.10 and ruvnet/ruflo@45e65b5. Hermes and Gemini CLI are named
here as illustrative candidates for the external door, not as hosts the kit supports today.
diff --git a/docs/HOST-SUPPORT.md b/docs/HOST-SUPPORT.md
index 2c9cdb1..46c5cdb 100644
--- a/docs/HOST-SUPPORT.md
+++ b/docs/HOST-SUPPORT.md
@@ -6,11 +6,18 @@ RuvNet Brain without treating those independent layers as interchangeable.
Behind an experimental flag, agentic-kit can also admit **external host adapters**
that extend this set with a host not shipped in-tree — see
-[External host adapters](PROVIDERS.md#external-host-adapters-experimental) and
-[ADR-0029](adr/0029-host-adapter-extension-point.md). An admitted external host
-picks up the same capability-driven treatment described here, but it is not one
-of the three built-ins this reference compares, and it can never claim
-primary-host, AQE-provider, or status-line status.
+[External host adapters](PROVIDERS.md#external-host-adapters-experimental),
+[AUTHORING-HOST-ADAPTERS.md](AUTHORING-HOST-ADAPTERS.md),
+[ADR-0029](adr/0029-host-adapter-extension-point.md), and
+[ADR-0031](adr/0031-capability-graduation-and-upstream-requests.md). An admitted
+external host picks up the same capability-driven treatment described here, but it
+is not one of the three built-ins this reference compares. It can never
+**self-declare** primary-host, AQE-provider, or status-line status — that ban is
+permanent — while `canBePrimary` and `commandStatusline` are **earnable** through
+a passed conformance tier plus an explicit maintainer grant. `aqeProvider` stays
+upstream-owned and is never `ak`-grantable. See
+[External host adapters](#external-host-adapters) below for what a grant does and
+does not buy today.
Evidence cutoff: **2026-08-04**. The comparison was checked against agentic-kit
`4.0.0-alpha.36`, Ruflo `3.34.0`, agentic-qe `3.13.x`, RuvNet Brain `4.0.7`,
@@ -218,6 +225,42 @@ subagent permission rules being ignored
`AGENTS.md` guidance being forgotten
([#40348](https://github.com/anomalyco/opencode/issues/40348)).
+## External host adapters
+
+An external adapter is data, not code: a hash-pinned manifest plus subprocess
+hooks, admitted only behind `AK_EXPERIMENTAL_HOST_ADAPTERS=1` and only after
+`ak host adapters trust ` discloses the full validated manifest and records
+your consent. `contract: 1` is still experimental and **not frozen** — the freeze
+waits on a real external adapter clearing the conformance kit and soaking.
+
+`ak host adapters conformance ` reports each graduation tier honestly:
+
+| Tier | Status today | Gates | Why |
+| --- | --- | --- | --- |
+| `admission` | Genuinely passes | — | Manifest validation and consent are built |
+| `activity-routing` | Genuinely passes | — | Real supervised subprocess worker via `ak run` |
+| `primary-eligible` | Genuinely passes | `canBePrimary` | Observes a real escalation |
+| `session-driving` | **Gated** | — | Being a native Ruflo backend is upstream's to grant |
+| `statusline` | **Gated** | `commandStatusline` | `ak` has no render surface for it yet |
+
+The two gated tiers are honest ceilings, not failures — they report `gated` or
+`skipped` and never `passed`, with `ak host adapters gate #NNN`
+recording the upstream issue each waits on.
+
+What a maintainer grant (`ak host adapters grant`, alias `bless`) buys today, stated
+narrowly: the capability goes live in the effective host registry from the next
+flagged invocation, so the host's tier label reflects it and it joins
+primary-eligibility. No path yet **selects** an external host as primary — `ak host
+pick` stays built-in-scoped — and `commandStatusline` has no runtime reader, so a
+granted `commandStatusline` is currently inert. Grants are withdrawable with
+`revoke-grant`, and every tier result is stale-marked the moment the manifest
+changes.
+
+A graduated adapter ends in one of two places: a **blessed external adapter** that
+stays out-of-tree holding exactly the capabilities its tiers earned, or a
+**promoted built-in** whose descriptor a maintainer adopts as a first-party registry
+entry — an ordinary pull request, not a command.
+
## Known contract discrepancies
These are intentionally visible rather than hidden behind an over-broad “supported”
@@ -239,7 +282,12 @@ ADR-0020 is **Implemented** as of 2026-07-30; ADR-0021 is **Accepted** and was
updated 2026-08-03. See [ADR-0017](adr/0017-opencode-host.md),
[ADR-0018](adr/0018-generalized-host-worker-execution.md),
[ADR-0020](adr/0020-ga-stable-surfaces.md), and
-[ADR-0021](adr/0021-inference-provider-provenance.md).
+[ADR-0021](adr/0021-inference-provider-provenance.md). For the external-adapter
+section above, [ADR-0029](adr/0029-host-adapter-extension-point.md) is the
+extension point and [ADR-0031](adr/0031-capability-graduation-and-upstream-requests.md)
+amends it with capability graduation — replacing ADR-0029's permanent
+capability caps with the earn-then-grant model, except for the permanent ban on
+self-declaring them.
## Operational guidance
diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md
index 3a22058..11ff5c9 100644
--- a/docs/PROVIDERS.md
+++ b/docs/PROVIDERS.md
@@ -88,14 +88,58 @@ like Hermes, say? Set `AK_EXPERIMENTAL_HOST_ADAPTERS=1` and declare it as **data
```
An adapter is a manifest plus a handful of subprocess hooks — nothing an adapter declares ever
-runs inside the `ak` process itself. Registering one asks you to confirm a content hash of the
-manifest; edit the manifest afterward and that consent is invalidated until you confirm again. A
-broken adapter is reported and skipped — it never takes down the hosts that already work.
-
-An external adapter can never claim to be the primary host, an AQE provider, or the status-line
-owner; those stay first-party. Nothing here installs itself: you declare the adapter, you consent
-to it, and teardown remains reversible. See
-[ADR-0029](adr/0029-host-adapter-extension-point.md).
+runs inside the `ak` process itself. `source` may be a local file path, an `https://` URL (HTTPS
+only, no redirects, bounded), or `npm:[@version]`, fetched with `npm pack --ignore-scripts`
+and extracted to stdout — nothing runs and nothing lands on disk. The source is resolved *before*
+hashing, so a mutated remote surfaces as `consent-stale` rather than sliding in quietly.
+
+Consent is explicit and hash-pinned. `ak host adapters trust ` discloses the full validated
+manifest and records your consent against its content hash (`--expect-hash` pins it
+non-interactively); `list` shows trust state, and `revoke` works even with the flag off. Once a
+host is admitted *and* explicitly enabled in `kit.json`, its lifecycle hooks run through
+`ak setup`, `ak sync`, and uninstall — a failed detect or plan aborts the apply. A broken adapter
+is reported and skipped; it never takes down the hosts that already work.
+
+### What an external adapter can earn
+
+An external adapter can never **self-declare** that it is the primary host, an AQE provider, or
+the status-line owner. That ban is permanent — it is the safety invariant the whole extension
+point rests on. But `canBePrimary` and `commandStatusline` are **earnable**: passing the gating
+conformance tier is evidence, and `ak host adapters grant ` (alias `bless`) is
+a maintainer's explicit grant, refused unless that tier is recorded passed at the adapter's
+current manifest hash. `aqeProvider` stays upstream-owned and is never `ak`-grantable.
+
+`ak host adapters conformance ` runs the tiered black-box kit:
+
+| Tier | Status today | Gates |
+| --- | --- | --- |
+| `admission` | Genuinely passes against a real adapter | — |
+| `activity-routing` | Genuinely passes — real supervised subprocess worker | — |
+| `primary-eligible` | Genuinely passes — observes a real escalation | `canBePrimary` |
+| `session-driving` | **Gated** — a native Ruflo backend is upstream's to grant | — |
+| `statusline` | **Gated** — `ak` has no render surface for it yet | `commandStatusline` |
+
+The two gated tiers are honest ceilings, not failures: they report `gated`/`skipped` and never
+`passed`. `ak host adapters gate #NNN` records the upstream issue the ceiling
+is waiting on, `status [name]` shows per-tier state (stale-marked the moment the manifest changes)
+alongside granted capabilities, and `revoke-grant [capability]` withdraws one or all.
+
+Be precise about what a grant buys **today**. A granted capability goes live in the effective host
+registry from the next flagged invocation — the host's tier label reflects it and it joins
+primary-eligibility. But no path yet *selects* an external host as primary (`ak host pick` stays
+built-in-scoped), and `commandStatusline` has no runtime reader. So a granted `canBePrimary` is
+visible and eligible, while a granted `commandStatusline` is currently inert.
+
+Graduation has two destinations: a **blessed external adapter** stays out-of-tree holding exactly
+the capabilities its tiers earned, or a maintainer **promotes it to a built-in** by adopting its
+descriptor as a first-party registry entry — an ordinary PR, not a command.
+
+Nothing here installs itself: you declare the adapter, you consent to it, and teardown remains
+reversible. `contract: 1` is still experimental and **not frozen** — freezing waits on a real
+external adapter clearing the conformance kit and soaking. Writing one? See
+[AUTHORING-HOST-ADAPTERS.md](AUTHORING-HOST-ADAPTERS.md). The governing decisions are
+[ADR-0029](adr/0029-host-adapter-extension-point.md) and
+[ADR-0031](adr/0031-capability-graduation-and-upstream-requests.md).
---
@@ -391,6 +435,8 @@ just makes the good default automatic and the customization reversible.
- Capability-driven integration axes, bindings, and provenance:
[ADR-0016](adr/0016-capability-driven-integration-adapters.md).
- The generic local OpenAI-compatible provider: [ADR-0028](adr/0028-local-openai-compatible-providers.md).
-- External host adapters (experimental): [ADR-0029](adr/0029-host-adapter-extension-point.md).
+- External host adapters (experimental): [ADR-0029](adr/0029-host-adapter-extension-point.md),
+ amended by [ADR-0031](adr/0031-capability-graduation-and-upstream-requests.md) — capability
+ graduation. Authoring guide: [AUTHORING-HOST-ADAPTERS.md](AUTHORING-HOST-ADAPTERS.md).
- Host env flags (`ENABLE_CLAUDE_CODE` / `ENABLE_CODEX`): upstream ruflo
ADR-034, "Optional MCP Backends".
diff --git a/docs/upstream-requests/agentic-qe-provider-plugin.md b/docs/upstream-requests/agentic-qe-provider-plugin.md
new file mode 100644
index 0000000..02c2ac2
--- /dev/null
+++ b/docs/upstream-requests/agentic-qe-provider-plugin.md
@@ -0,0 +1,72 @@
+
+
+# Feature request: a provider-plugin registration API for `AQE_LLM_PROVIDER`
+
+## Summary
+
+agentic-qe's LLM provider set is a closed, upstream-defined enumeration. A downstream
+integrator (agentic-kit) can select any *existing* provider via `AQE_LLM_PROVIDER`, but
+cannot introduce a *new* provider type without an upstream code change. We'd like a
+documented registration surface so a host that a downstream tool supervises can be
+recognized as its own provider identity, rather than only borrowing the model provider
+underneath it.
+
+## Where this stands today (please re-verify against HEAD)
+
+Grounded against `agentic-qe@3.13.10` (as cited in agentic-kit's ADR-0031):
+
+- `ALL_PROVIDER_TYPES` is a fixed union of provider type strings.
+- `createProvider` is a `switch` over those types; an unrecognized type has no arm.
+- So the provider set is extended only by editing that enum + switch — there is no
+ runtime/plugin registration path.
+
+If any of this has changed on HEAD (a registration hook, an open provider map, a plugin
+entrypoint), this request may already be satisfied — please close it as such.
+
+## The concrete ask
+
+A minimal, documented way for a downstream tool to register an additional provider type at
+runtime, e.g.:
+
+- a `registerProvider(type, factory)` entrypoint (factory conforming to the same interface
+ `createProvider` returns), and inclusion of registered types in `ALL_PROVIDER_TYPES`
+ for validation; **or**
+- a documented "external provider" adapter interface a downstream package can implement
+ and hand to the `HybridRouter` / `ProviderManager`.
+
+We are not asking to widen the *default* provider set — only for a sanctioned extension
+point so `AQE_LLM_PROVIDER=` can resolve to a provider we supply, instead of us
+either forking the enum or projecting our host onto an unrelated provider identity (which
+would misrepresent which vendor served the work).
+
+## Why (the downstream context)
+
+agentic-kit admits external host adapters (declarative manifest + consented subprocess
+hooks) and runs a tiered conformance kit against them. Quality still runs through the
+*model provider underneath* a host, so QE is never blocked — but a host cannot earn an
+**AQE-provider identity** of its own, because that identity is yours to define, not ours to
+extend. agentic-kit deliberately refuses to fabricate one (projecting an unknown host into
+your config would assert an identity you never declared you understood). This request is
+the honest alternative: a real extension point, so the capability can light up when you
+release it.
+
+## Non-goals
+
+- Not asking agentic-kit's hosts to be bundled into agentic-qe.
+- Not asking to bypass any provider validation — registered providers should be validated
+ exactly like built-in ones.
+
+## References
+
+- agentic-kit ADR-0031 §4 (the upstream-request path) and ADR-0029 (the host-adapter
+ contract): the design that motivates this.
+- `AQE_LLM_PROVIDER` / `HybridRouter` / `createProvider` / `ALL_PROVIDER_TYPES` in
+ agentic-qe (re-verify paths against HEAD).
diff --git a/docs/upstream-requests/ruflo-backend-registration.md b/docs/upstream-requests/ruflo-backend-registration.md
new file mode 100644
index 0000000..c9f46e2
--- /dev/null
+++ b/docs/upstream-requests/ruflo-backend-registration.md
@@ -0,0 +1,65 @@
+
+
+# Feature request: a documented backend-registration surface for host CLIs
+
+## Summary
+
+ruflo's set of agent **backends** (the host CLIs that drive the loop) is enabled per-host
+through fixed `ENABLE_*` flags. There is no documented way for a downstream tool to
+register an *additional* backend, so a host CLI that a downstream integrator supervises
+cannot become a ruflo-native backend without an upstream code change. We'd like a
+documented registration surface for that.
+
+## Where this stands today (please re-verify against HEAD)
+
+Grounded against `ruvnet/ruflo@45e65b5` (as cited in agentic-kit's ADR-0031):
+
+- Backend enablement is per-host and fixed: `ENABLE_CLAUDE_CODE`, `ENABLE_CODEX`,
+ `ENABLE_GEMINI_MCP` (ADR-034 "Optional MCP Backends").
+- A new backend is added by defining a new `ENABLE_*` target inside ruflo — there is no
+ outside/plugin registration path.
+
+If HEAD already exposes a backend-registration API or a documented plugin entrypoint, this
+request may already be satisfied — please close it as such.
+
+## The concrete ask
+
+A documented way to register an additional agent backend from outside the ruflo tree, e.g.:
+
+- a backend descriptor interface (how ruflo detects the CLI, launches an interactive /
+ oneshot session, and observes completion) that a downstream package can implement and
+ register, honored by the same loop the `ENABLE_*` backends drive; **or**
+- a documented convention for an external backend entrypoint ruflo will discover and drive.
+
+The goal is that a host CLI can *drive a ruflo session* as a first-class backend, not only
+run under a downstream tool's own supervision.
+
+## Why (the downstream context)
+
+agentic-kit admits external host adapters and certifies them with a tiered conformance kit.
+The `session-driving` tier — "the host actually drives an interactive/oneshot session" —
+is the one an external adapter cannot clear on its own, because being a ruflo-native
+backend is defined inside ruflo, not registered from outside. agentic-kit's honest interim
+is to run such a host through its *own* supervised execution (`ak run`), and to mark the
+tier `gated` on this request rather than fake a pass. A documented backend-registration
+surface upstream is what lets that tier genuinely light up.
+
+## Non-goals
+
+- Not asking to bundle agentic-kit's hosts into ruflo.
+- Not asking to loosen the trust model — a registered backend should be subject to the same
+ enablement/consent posture as the built-in `ENABLE_*` backends.
+
+## References
+
+- agentic-kit ADR-0031 §4 (the upstream-request path) and ADR-0029 (the host-adapter
+ contract).
+- ruflo ADR-034 "Optional MCP Backends" and the `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` /
+ `ENABLE_GEMINI_MCP` backend model (re-verify paths against HEAD).