What four source-grounded research sweeps — ruflo's plugin history, the three
+hosts' extension models, Hermes at HEAD, and agentic-qe's parity constraints — teach us about
+building agentic-kit's host-adapter extension point without sacrificing quality, symmetry, or
+maintainability. Ends in one finalized mechanism recommendation and the decisions still open.
+
Prepared 2026-08-15 · companion to the Host & Provider Consistency master
+document · every claim below carries a citation from a live sweep of upstream source
+
+
+
+
1 · The manifesto correction
+
"Everything is a plugin" is not ruflo's manifesto. The phrase is the title of
+a blog post for a different rUv product — cognitum-open-design v0.8.0
+(apps/landing-page/…/open-design-0-8-0-everything-is-a-plugin.md).
+Training data conflates the two because both are rUv projects with plugin systems; treat any
+unsourced claim of a ruflo "everything is a plugin" manifesto as false.
+
Ruflo's actual founding document is
+v3/implementation/adrs/ADR-004-PLUGIN-ARCHITECTURE.md (Implemented,
+2026-01): "We will adopt a microkernel architecture with plugins for optional features."
+Core stays small (agent lifecycle, task execution, memory, coordination, MCP server); everything
+else is an optional plugin. Notably, one of its own success metrics was still unchecked at ship
+time: [ ] Community plugin contributed. That gap — a designed-for ecosystem that
+did not materialize on the designed timeline — frames every lesson below.
+
+
2 · Evidence base
+
+
Sweep
Grounded against
Headline
+
ruflo
ruvnet/ruflo@45e65b5 (HEAD 3.38.11; local CLI 3.38.8)
+
Two plugin layers: the Claude Code plugin catalog (38 plugins, real and disciplined) and
+ the internal IPlugin runtime framework (well-designed; trust layer honestly
+ self-documented as demo/stub).
+
hosts
Claude Code docs (2026-08-14) · Codex rust-v0.147.0 · OpenCode v1.18.18
+
Manifest-first everywhere mature; Codex's plugin system has shipped and converged
+ with Claude Code's hook taxonomy; nobody sandboxes or signs anything; every lifecycle has
+ real atomicity bugs.
+
hermes
NousResearch/hermes-agent@cb47f59 (245 commits past v0.20.1)
+
Every load-bearing adapter fact holds unchanged. New: a native ACP server (third driving
+ surface), a --tui statusline, three-tier hooks with consent allowlists. PyPI is
+ stale; the npm bridge now ships three bins.
+
agentic-qe
agentic-qe 3.13.10 (local = latest published)
+
Provider set closed at every layer; its real plugin system reaches QE domains only; the
+ vendorOf mirror is in sync; quality-gate anchors are a content-authoring task,
+ not a flag.
+
+
+
3 · Five load-bearing lessons
+
+
3.1 Manifests ship; trust layers stall
+
Across every system surveyed, the parts that work are declarative data: Claude Code's
+plugin.json, Codex's semver-required plugin.json, aqe's
+QEPluginManifest with its fail-closed install gate, ruflo's flat
+marketplace.json. The parts that stalled are the code-trust layers: ruflo's typed
+PluginPermissions shipped as types with zero runtime enforcement
+(verified: no "permission"/"capability"/"sandbox" occurrence in either shipped registry file);
+its Ed25519-signed IPFS marketplace fell back to a hardcoded demo list every time by its own
+ADR's admission; no host sandboxes extension code; no host signs anything.
+Design consequence: make the adapter a data artifact validated by the same
+validators built-ins use, and never rely on honor-system runtime capability checks.
+
+
3.2 The design→ship gap is the #1 failure mode — counter it with graded ADRs
+
Ruflo's best practice is the antidote to its own failure: ADR-015-v2 carries a dated
+self-graded table (Working / Demo / TBD per feature). The reliable predictor of "is
+this mechanism real" across the whole sweep was whether the ADR carried that table with test
+counts. Ruflo also produced two near-identical, never-reconciled security ADRs from separate
+agent runs (ADR-145 / ADR-337) — a governance failure to design against.
+Design consequence: agentic-kit's extension ADRs ship with a graded table and
+are not marked Accepted until the table is backed by dated evidence; each published contract
+gets its own ADR pinned to an ak version, with a CI check that fails when the pin goes stale
+(ruflo's per-plugin contract ADRs drifted ~32 minor versions unnoticed).
+
+
3.3 Fail-closed admission is the differentiator; per-adapter isolation is table stakes
+
Hosts degrade by default — and their open bugs show even that contract breaking (a removed
+Codex plugin's Stop hook keeps firing until restart
+openai/codex#38339; an OpenCode plugin segfault left installs
+permanently unusable anomalyco/opencode#26890; a Claude Code
+marketplace update reports success while doing nothing
+anthropics/claude-code#46594). Ruflo's admission is warn-only even
+for unsigned plugins. Nobody in this space is fail-closed at the door — agentic-kit already is
+(ADR-0023), and should stay so: an invalid adapter is refused by name, never warned past.
+Equally: one bad adapter must never brick ak — which is precisely the F-01 import-time-throw
+blocker, now with ecosystem-wide evidence behind the fix.
+
+
3.4 Hash-pinned consent is the strongest achievable integrity primitive
+
Codex hash-pins each trusted hook and invalidates trust automatically on any edit —
+the strongest integrity mechanism found anywhere, and cheap. Hermes independently converged on
+per-(event, command) consent allowlists for its shell hooks. Signing exists nowhere.
+Design consequence: record a content hash of the adapter manifest (and each
+declared hook command) at trust time; a changed manifest re-prompts. This composes with ak's
+existing trust-manifest machinery and with aqe's contentHash-frozen anchor
+precedent. Do not require signatures the ecosystem cannot produce.
+
+
3.5 Conformance means black-box subprocess tests against the installed layout
+
Ruflo's hook regression (flags that didn't exist; --success true silently
+recording the string "true" as a path — ADR-102) was
+caught only by a harness that drives the real CLI binary with synthetic stdin and
+asserts negative cases. Its counter-example: a plugin smoke test hardcoding a
+monorepo-sibling path fails for every real installed user
+(ruvnet/ruflo#2912).
+Design consequence: the conformance kit spawns real processes, asserts negative
+cases, runs against an installed topology (not the repo checkout), and returns a numeric
+pass/fail — the contract's graduation evidence.
+
+
4 · Hard constraints the design inherits
+
+
AQE's provider set is closed at every layer — 11 declared types, 10
+ constructible (onnx is declared-but-unconstructible); its real plugin system
+ (aqe plugin install) reaches QE domains only. No adapter may ever project into
+ any AQE surface. Phase 1's asymmetry decision already encodes this.
+
Two AQE "host" surfaces exist and must not be conflated: LLM-execution
+ binding (spawns claude/codex; closed) vs. platform/MCP installers
+ (8 hardcoded; closed). ak's extension point extends neither — it extends ak's own host axis.
+
Driving surfaces are plural now. Hermes ships a native ACP
+ (Agent Client Protocol) server alongside CLI oneshot — the contract must express how
+ a host is driven (cli-subprocess, ACP, MCP) as declared capability data, not assume
+ subprocess.
+
Detection can trust nothing indirect: Hermes's PyPI is two releases
+ stale while the unofficial npm bridge tracks releases within an hour and now ships three bins;
+ Claude Code plugin identity falls back through version→SHA→digest→unknown. Detect binaries and
+ verify independently; never infer identity or currency from a package registry.
+
Statusline claims must be scoped: Hermes's statusline exists only in its
+ new --tui mode; Codex has no tui.status_line free-text key at all.
+ The capability vocabulary already handles this (commandStatusline) — keep claims
+ mode-scoped and truthful.
+
The vendorOf mirror tracks upstream referee.ts — in sync at
+ aqe 3.13.10 with one documented fail-closed divergence; any upstream prefix-table change needs
+ a mirror update (no version gate exists beyond presence).
+
Ruflo cannot drive Hermes. Its repo description names "Hermes" as
+ integrated, but source shows only technique adoptions from hermes-agent
+ (prompt-caching pattern, reasoning-tag scrubbing, tool-loop breaker —
+ v3/@claude-flow/cli/__tests__/hermes-tier1.test.ts); no Hermes
+ execution backend exists. Hermes reaches ak only through ak's own adapter seam.
+
+
+
5 · Interfaces & anatomy — as-is and to-be
+
+
5.1 The interfaces ak actually has
+
Signatures below are read from source on this branch, not recalled. Everything in this table
+is host-neutral, validated, and test-enforced — this is the spine the extension point builds on.
+
+
Axis
Interface
Where
+
Registries
+
HOST_REGISTRY · PROVIDER_REGISTRY · PROJECTION_REGISTRY ·
+ OBSERVABILITY_REGISTRY — immutable, cross-axis-validated at construction
+ (validateRegistries throws at load); capability queries
+ hostIds(pred), primaryHostIds(), routableHostIds(),
+ managedHostIds(), hostsWithCapability(), defaultHostMap()
+
adapters/registries.mjs
+
Bindings
+
assertValidBinding (throwing) · validateBinding (error list —
+ wired to ak host status warnings) · resolveBinding ·
+ validateEndpoint (loopback-http / remote-https / secret-param rules)
Introspection says the codebase is not a hack job; it is a disciplined core wearing
+an accreted shell. Every contract above was built host-neutral and is enforced by
+validators and tests — several at module construction, which is stricter than most of the
+ecosystem surveyed. The debt is concentrated and countable: roughly ten dispatch sites where a
+command reaches a host by name instead of by lookup — the five
+OPENCODE_LIFECYCLE_ADAPTER named-import call sites (F-02), the frozen three-entry
+execution map whose invariant throws at import (F-01), status's 88-line opencode block (F-05),
+routing's {claude, codex} literals and binary escalation swap (F-23), guidance's
+closed four-target list (F-17), setup's claude-only permission pass (F-04), uninstall bypassing
+runLifecycle (F-03), the fixed quota record (F-10), the two statusline patchers,
+and the hand-mirrored npm package list (F-18). Phases 0–1 already moved attribution, host
+defaults, the binding migrator, and provider records from the shell to the spine. Phase 2's
+waves 1–2 are precisely the remaining shell-to-spine moves — deletion of hand-wiring, not new
+machinery.
+
+
+
+Fig 1 — as-is, conceptual. Two paths run between commands and the
+same contract core. The spine resolves hosts by registry lookup; the shell reaches specific
+hosts by name — each shell item is a finding, and each is a deletion target, not a rewrite.
+
+
+
+
+Fig 2 — as-is, sequence (ak sync wiring pass). The
+config half already runs on the spine. The lifecycle half runs through the full five-verb
+contract — but the adapter is a named import; the same is true at setup, host pick, and host
+off, while uninstall skips the contract entirely.
+
+
+
5.3 To-be: one path, one door
+
+
+
+Fig 3 — to-be, conceptual. The shell is gone: every command
+resolves hosts through one registry lookup. External adapters are data admitted through one
+fail-closed door; a refusal names its reason and cannot disturb built-ins. The contract core
+does not change — it was already right.
+
+
+
+
+Fig 4 — to-be, sequence. Admission happens once, up front, fail
+closed. After that an external host is indistinguishable from a built-in at every call site:
+same five verbs, same supervision, same independent verification, same undo path.
+
+
+
6 · The recommended mechanism — Q2, finalized
+
+
An adapter is data plus consented subprocess hooks. No third-party code runs inside ak.
+
Every line of evidence points the same way: the manifest-first layers succeed everywhere;
+the in-process-code trust layers failed at ruflo (types without enforcement), don't exist at
+any host (no sandboxing, no signing), and would make ak the only tool in this ecosystem running
+foreign code in-process on an honor-system cap. Hermes's own extension philosophy — "any CLI
+you already have becomes a plugin without writing code" — plus its subprocess shell hooks with
+consent allowlists, is the shape a Hermes-class adapter naturally wants.
+
+
+
Declarative core: an adapter package is a versioned manifest — the host
+ registry entry (validated by the same validateHostAdapter built-ins pass), plus
+ declarative detection (bin names, version argv + regex), lifecycle described as data where
+ possible, and declared trust changes.
+
Subprocess hooks for the remainder: lifecycle verbs that genuinely need
+ logic run as short-lived subprocess commands declared in the manifest — each hash-pinned at
+ consent time, re-consented on change, executed with ak supervising (timeout, captured output,
+ independent verify after).
+
Capability caps are structural, not runtime: the adapter manifest
+ schema cannot express canBePrimary, aqeProvider, or
+ managed-statusline claims — a cap enforced by shape is stronger than any runtime check, and
+ is precisely where ruflo's honor-system caps failed.
+
Fail-closed admission, isolated failure: a manifest that fails
+ validation, hash check, or contract version is refused with a message naming adapter and
+ reason; a refused or broken adapter never affects built-ins or other adapters (the F-01 merge
+ seam is the enabling refactor).
+
Independent verification: ak never trusts an adapter's (or host's)
+ reported success — post-apply verify is part of the lifecycle contract, per the
+ lifecycle-atomicity bugs found in every host.
+
+
+
7 · Contract sketch
+
+
Element
Shape
Precedent
+
Registration
kit.json hostAdapters: [{name, source, contract: 1}] — explicit, never scanned
+
ruflo PluginRegistry.register(); Claude Code --plugin-dir
Waves 1–3 unchanged (in-tree generalization, F-05 status hot zone, test
+ derivations) — every mechanism finding lands in wave 4's shape, not the refactors.
+
Wave 4 is now concrete: manifest schema + admission gate + hash-consent
+ + supervised hook runner + fixture adapter + conformance harness. No module loader for foreign
+ code — which shrinks the security surface the loader review must defend.
+
New wave-4 item: driving-surface vocabulary — capability data expressing
+ cli-subprocess / ACP / MCP driving, so the contract doesn't fossilize the subprocess
+ assumption (Hermes ACP evidence).
+
Phase 3 note: a truthful Hermes adapter today could declare
+ session-driving (oneshot or ACP), transcripts, scoped usage (--usage-file),
+ bidirectional MCP config, and tui-scoped statusline — with nativeGuidance
+ pending a definition check against Hermes vocabulary (AGENTS.md/SOUL.md injection exists;
+ a literal guidance artifact does not).
+
+
+
9 · Decision board
+
+
Settled
+
Ownership. agentic-kit authors and owns the design, ADRs, tests, and
+ implementation. PR #131 is credited as the originating proposal; the maintainer comments on
+ it once the Phase 2/3 approach is settled.
Q1 · Posture. Staged: in-tree generalization at GA; loader + contract
+ behind an experimental flag with written graduation criteria (recommended) — vs. fully
+ published day one, vs. in-tree only.
+
Open — recommendation stands
+
Q3 · Tier vocabulary. Capability-derived phrases in status/about/help
+ (recommended) — vs. tier nouns, vs. role nouns.
+
Open — recommendation stands
+
Q4 · Hermes adapter ownership. Fixture-only through Phase 2; revisit an
+ ak-org reference adapter once the contract freezes (recommended) — vs. ak-org adapter now,
+ vs. in-tree host code (rejected).
+
Open — recommendation stands
+
Q5 · Docs restructure depth. Targeted Phase 3 sweep of known offenders
+ with appendices, standing current-state-only rule preventing new violations (recommended) —
+ vs. full-corpus audit, vs. separate phase.
+
+
+
10 · Sources
+
Each sweep's full citation set lives in its research report; the load-bearing
+references:
agentic-kit · how hosts work, and how new ones join
+
Room for more hosts
+
The kit drives work through an AI coding assistant — a host. It ships
+ with three. This effort rebuilt the plumbing so all three are treated the same, and added a
+ door for a fourth, a fifth, a tenth — without rewriting the kit each time.
+
A plain-language explainer for people who use agentic-kit and for people who might
+ write a host adapter for it. Throughout, maintainer means the maintainer of
+ agentic-kit. Companion to ADR-0016 and ADR-0029.
+
+
+
+
+
1 · The one-sentence version
+
Adding a new assistant used to mean surgery on a dozen places inside the kit;
+now the three built-in assistants are described by data in one registry, and an outside assistant
+can be described the same way — as a signed-off manifest, never as code that runs inside the kit.
+
+
+
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.
+
+
+
2 · Two kinds of host
+
Everything the kit can drive falls into two buckets. The difference isn't the assistant — it's
+how the kit came to know about it.
+
+
+
+Two doors, one hallway. Built-in hosts are compiled into the kit and
+trusted by definition. External adapters arrive as data and must pass a fail-closed admission gate.
+Once inside, both are just entries in one registry — every command treats them the same, which is
+the whole point.
+
+
+
+
+
Built-in hosts here now
+
Claude Code · Codex · OpenCode
+
+
Ship inside the kit; nothing to install or trust separately.
Claude and Codex can lead; OpenCode is a supervised routing host.
+
This effort made all three consistent — same commands, same truthful reporting.
+
+
+
+
External adapters the foundation
+
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.
+
+
+
+
+
3 · Why this was worth doing
+
Before this effort, the three assistants were described in a registry but acted on
+by name, scattered across the codebase. Adding a fourth meant finding and editing roughly ten
+places that each hard-coded "claude / codex / opencode" — and missing one meant a new host that
+looked installed but silently misbehaved. That's the "open-heart surgery" problem.
+
+
+
+The value, in one picture. The commands stopped hard-coding host names
+and started asking the registry "who can do this?" That refactor is done for the three built-ins —
+and it's the same machinery an external adapter plugs into, which is why a fourth host no longer
+means touching ten files.
+
+
+
Along the way the same effort fixed a batch of honesty bugs that were quietly mis-labelling work —
+usage records filed under the wrong assistant, live sessions renamed to an internal placeholder,
+vendor-diversity checks that could be fooled. Truthful reporting is part of the value: a kit that
+supports more hosts has to account for them correctly, not just run them.
+
+
4 · What an external adapter actually is
+
for implementers The core safety idea: an adapter is
+description plus a few small scripts the kit runs at arm's length — never a plug-in
+loaded into the kit's own process. If a plug-in can crash or take over its host, this is deliberately
+not that.
+
+
+
+Data first, code at a distance. The manifest is validated by the same
+checks the built-in hosts pass, plus caps that make forbidden claims impossible to even write down.
+The only code that ever runs is a hook — a separate, supervised, shell-free subprocess with a
+timeout and a minimal environment. Nothing from an adapter runs inside the kit.
+
+
+
What the kit guarantees about an external host
+
+
Question
Built-in host
External adapter
+
Can it lead a session (be primary)?
yes*
not by self-declaring — earned only
+
Can it claim a billing / quota provider?
yes*
not by self-declaring
+
Can it claim a custom status line?
yes*
not by self-declaring
+
Does any of its code run inside the kit?
it is the kit
no — subprocess only
+
Can it install software silently?
only disclosed steps
no — may not even name a package
+
What if its manifest is edited after it's trusted?
n/a
trust is void until re-approved
+
What happens if it's broken or malicious?
n/a
refused by name; other hosts unaffected
+
+
* "yes" here means a built-in can hold these — which ones actually do varies
+(Claude: all three; Codex: leads and is an AQE provider, but uses a native, not command-backed,
+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
+(section 7).
+
+
Leading vs. being supervised
+
Two capabilities sound similar but are different axes, and the difference is the crux of why
+OpenCode is a full member in some ways and not others. Leading
+(canBePrimary) is a host's authority to anchor a run; being
+supervised (canRouteActivities) is a host's ability to execute an assigned
+step under the runner's control. A host can have one, both, or — for an external adapter that
+hasn't earned leading — just the second.
+
+
+
+One run, two roles. The supervisor turns "add feature" into a
+role-to-host plan and spawns each step as a bounded, supervised worker. The primary host
+(Claude by default; ak host pick --primary-host codex flips the lead) anchors the run —
+the reasoning roles route to it and a failed worker escalates toward it. Claude and Codex each appear
+as both a lead and a worker; OpenCode can only ever be a worker.
+
+
+
+
Dimension
Leading (primary)
Being supervised (routed worker)
+
Capability flag
canBePrimary
canRouteActivities
+
Which built-ins have it
Claude, Codex
Claude, Codex, OpenCode
+
What it means
Anchors the run: the default host, the reasoning roles route to it, and failed workers escalate toward it; it leads dual-host mode.
Executes one assigned activity (architecture, coding, testing, review…) that the runner spawned for it.
+
Chosen by
ak host pick --primary-host
the routing policy, per activity (or a run-local --route)
+
Degree of control
leads — decides the shape, anchors escalation
given a task, host, model, and deadline; runs; reports back
+
Bounded?
it is the session lead
yes — timeout, a bounded escalation ladder, a structured result
+
OpenCode
never (canBePrimary: false)
yes — a supervised worker
+
+
So "supervised, never primary" isn't a demotion from ak run — it's the opposite:
+OpenCode does run work in a pipeline, as a supervised worker like any other. What it can't
+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.
+
+
+
+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.
+
+
+
What can be expected today
+
+
The three built-in hosts work fully and consistently — that's the immediate,
+ 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.
+
+
+
+
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.
+
+
+
+
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):
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.
+
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.
+
+
+
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
+"a released version of agentic-kit ships Hermes as a first-class built-in." The governing idea —
+an external adapter is experimental until conformance graduates it. Conformance is
+the gate; the maintainer is the judge; there are two places a host can land.
+
+
+
+The full journey. A contributor can go all the way to step 3 alone —
+publish an adapter, and any user opts in behind the experimental flag. Getting into the kit
+starts at step 4: propose it to the maintainer with conformance evidence. The maintainer reproduces
+that evidence and reads the hook scripts (the only part that executes), then chooses a landing spot —
+a vetted external adapter, or a promotion into the built-in registry that ships in the next release
+with no flag and full parity.
+
+
+
What each actor actually does
+
+
Author the adapter.contributor Write the
+ manifest (host descriptor, detection, driving surfaces, lifecycle hooks, disclosed trust changes)
+ and the small hook scripts that implement each lifecycle verb. This is data and a few subprocess
+ scripts — in the contributor's own repo, their own language.
+
Self-test against the conformance kit.contributor
+ The kit ships a black-box harness: it admits the adapter through the real gate, runs its hooks as
+ real subprocesses, and refuses a corpus of bad manifests with exact reasons. The contributor
+ iterates until it reports a clean pass. Conformance is objective — the same harness the
+ maintainer will run, so there are no surprises at review time.
+
Publish.contributor They ship the manifest
+ from their own repo. At this point any user can already opt in — behind
+ AK_EXPERIMENTAL_HOST_ADAPTERS=1, with hash-pinned consent. This path needs
+ nothing from the maintainer; it's the permissionless escape valve. It stays experimental.
+
Propose it.contributor To get it vetted or
+ into the kit, they open a PR/issue attaching the conformance report — the numeric, reproducible
+ pass. That report is the currency of the conversation.
+
Verify.maintainer The maintainer re-runs
+ the conformance kit against the adapter (black-box, reproducible) and reads the hook scripts
+ — the one part that executes — with the same adversarial review discipline the rest of the kit
+ gets. Conformance evidence + an independent reproduction + a hook read = the basis for trust. The
+ maintainer never had to run the contributor's code inside the kit to evaluate it.
+
Decide the tier.maintainer Based on which
+ conformance tiers passed, the maintainer chooses where it lands:
+
+
Bless it as a curated external adapterstays external
+ — add it to a vetted list with its manifest hash pinned. Users still opt in themselves, but now
+ with the maintainer's vouch behind it. Lightweight; good for niche or long-tail hosts the kit
+ doesn't want to own. Still experimental, still capped by what it earned.
+
Promote it into the treebecomes built-in —
+ adopt its host descriptor as a built-in registry entry. Because of the registry-driven refactor
+ this whole effort delivered, that's a normal, small PR — an entry plus a lifecycle
+ adapter plus an About card, following the established pattern. No open-heart surgery.
+
+
+
Release.maintainer The promotion PR goes
+ through the ordinary release process — CI, review, merge. The next version of agentic-kit ships the
+ host as a first-class built-in: it appears for every user with no flag, no
+ manifest, no consent step, participating exactly like Claude, Codex, and OpenCode. The caps are gone
+ because it's now code the maintainer vouches for — that's what graduation means.
+
+
+
+
So, concretely, "a release with Hermes built in"
+
Hermes graduates: its author gets it passing the conformance kit and
+ proposes it; the maintainer reproduces the pass and reviews its hooks; the maintainer promotes its
+ descriptor into the built-in registry (a small PR, thanks to the groundwork); it rides a normal
+ release. From that version on, Hermes is one of the built-in hosts — indistinguishable from
+ Claude/Codex/OpenCode to a user — and its adapter code either ships as bundled hook scripts or gets
+ reimplemented as in-process glue, the maintainer's call. The experimental external-adapter door was
+ the proving ground; being built-in is the diploma.
+
+
+
8 · Some ceilings aren't ours to lift — the upstream path
+
agentic-kit doesn't stand alone. It sits downstream of two other systems: ruflo,
+which orchestrates the agent loop, and agentic-qe, which runs quality. A host's full
+participation touches all three layers — and that means a contributor chasing a conformance tier can
+hit a ceiling that agentic-kit cannot lift, because the missing capability lives upstream.
+When that happens, the answer isn't "blocked forever" — it's a deliberate request, carried upstream,
+that lights up in the kit once it ships.
+
+
+
+Downstream, but not powerless. When a conformance ceiling is really an
+upstream one, the kit doesn't fake it or bury it — it files a specific capability request against
+ruflo or agentic-qe, tracks the tier as "gated on upstream," and exposes the capability the moment
+the upstream release lands. The dashed gold arrows are the ask; the green arrows are the payoff.
+
+
+
Which layer owns the blocker?
+
The first diagnostic when a conformance tier can't be met: whose capability is missing?
+
+
The blocker
Owner
What happens
+
Can't run work through the host; no trust command; local-file manifests only
+
agentic-kit
+
The kit's to fix — a normal kit PR. This is the on-ramp work, no one to wait on.
+
Host wants to be a recognized AQE provider type
+
agentic-qe
+
Upstream — the provider list is a closed enum. Request a provider-plugin API. Interim: QE works through the model provider underneath the host, so quality isn't blocked, only the host's own AQE identity is.
+
Host wants to be a native ruflo backend (drive the loop, an ENABLE_* target)
+
ruflo
+
Upstream — backends are defined in ruflo. Request a documented backend-registration path. Interim: the host runs through the kit's own supervised execution, just not as a ruflo-native backend.
+
Host wants to participate in the QE quality gate
+
agentic-kit
+
Mostly the kit's — adopt an anchor set. Not blocked upstream.
+
+
+
+
The request path, and why it's credible
+
A capability request isn't a wish into the void. The kit's maintainer
+ champions it upstream — the same maintainer already contributes to these projects — with a
+ concrete extension point named, not a vague "please support us." The pending dependency is recorded
+ against the conformance tier (gated: agentic-qe#NNN), so anyone reading an adapter's
+ status can see exactly what it's waiting on and why. When the upstream release ships the capability,
+ the kit exposes it in the adapter contract and the tier flips from gated to earnable. This is what
+ keeps "no limitations after conformance" honest: the limitations that are real get a
+ documented route to disappear, layer by layer.
+
+
+
9 · OpenCode in practice — a worked example
+
All of this becomes concrete with a host that's already here. OpenCode is a built-in, ak
+installs and configures it, and a user can open the OpenCode CLI on a project and get real benefit —
+yet it isn't a full peer on every layer. Here's exactly what it gets, what it doesn't, and — the
+useful part — who owns each gap. It's the whole three-layer story in one host.
+
+
+
+
What OpenCode gets wired by ak
+
On an ak-configured project, opening the OpenCode CLI comes with:
+
+
Ruflo's full MCP server — memory, routing, swarm tools, registered in
+ OpenCode's own config and auto-approved.
+
RuvNet Brain MCP — grounding in rUv source.
+
Ruflo lifecycle hooks — OpenCode's own edit/tool/task events wired to
+ ruflo, so learning fires as work happens.
+
Managed ruflo agents, skills, and AGENTS.md guidance projected in.
+
+
The orchestration + grounding layers: fully first-class.
+
+
+
What it doesn't — and who owns it
+
+
It never leads (never primary) — by design; it's a
+ supervised host.
+
agentic-qe tools/skills aren't projected in (unlike Claude & Codex) —
+ ak's choice; ak could run aqe's OpenCode installer and doesn't yet.
+
It isn't an AQE provider type (analysis can't run on it) —
+ upstream; agentic-qe's provider list is a closed enum.
+
No command status-line footer, no quota surface, no cross-host bridge —
+ by design.
+
+
+
+
+
+
The pattern, in one host
+
OpenCode is a first-class ruflo + brain host, a
+ supervised (never-leading) execution host, and largely outside the
+ agentic-qe layer — and that last gap splits cleanly the way
+ section 8 describes: "no aqe tools projected in" is ak's to
+ close (a normal kit change), while "can't be an AQE provider type" is an upstream request
+ to agentic-qe. That's the entire model — host layer, lead vs. supervised, and the ak-vs-upstream
+ split — visible in a single assistant available today.
+
+
+
+
Built-in host behavior is authoritative in ADR-0016; the external-adapter extension
+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
+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.
+ Every claim below was verified against source: the ak side by a full surface sweep of the
+ repository, the Hermes side against NousResearch/hermes-agent HEAD (v0.20.0, 2026-08-13).
+ Finding IDs (F-nn) and decision IDs (D-n) are stable and cross-referenced by the roadmap
+ and the PR #131 review response.
+
+
+
+
0 · Executive brief — in plain language
+
For every consumer of agentic-kit — no engineering background needed.
+ Everything after this section is the technical evidence behind this summary.
+
+
What agentic-kit is
+
+ agentic-kit ("ak") is a set-up and housekeeping tool for AI coding assistants. Today it
+ manages three of them — Claude Code, Codex, and OpenCode. It installs them, keeps their
+ settings consistent, routes different kinds of work to the assistant best suited for it,
+ and reports on what they did and roughly what it cost.
+
+
+
The problem
+
+ A contributor asked us to support a fourth assistant — Hermes, notable because it drives
+ AI models that run entirely on your own computer instead of in a vendor's cloud. Studying
+ that request exposed something broader: the kit does not treat its three current
+ assistants evenly. Under the hood, roughly half of the kit treats "an assistant" as a
+ generic thing any newcomer could slot into; the other half has the three names written in
+ by hand. In practice, some features quietly apply to only one or two assistants, a
+ newcomer would be invisible to most of the kit — and, worst of all, a few reports file
+ activity from an unrecognized assistant under the wrong name instead of saying
+ "unrecognized."
+
+
+
The challenges
+
+
Adding an assistant today is open-heart surgery. The last time we added one it
+ touched roughly fourteen files and eight test suites — and permanently made the kit's
+ maintainer responsible for software they don't personally use.
+
Assistants differ in ways users must be told about. Example: in unattended
+ mode, Hermes approves every action automatically, by its own design — where OpenCode
+ pauses and asks. The kit has to state differences like that plainly, never average them
+ away.
+
The bookkeeping isn't honest yet. Usage and session reports currently mis-file
+ unknown assistants into an existing bucket rather than labeling them truthfully. That is
+ a correctness problem today, not a someday problem.
+
Changing course must happen on the record. An earlier design decision
+ deliberately said "no third-party plug-ins." Opening a plug-in door means revisiting that
+ decision explicitly, with its trade-offs written down — not quietly working around it.
+
+
+
The planned work, in phases
+
+
Phase 0 — Truthfulness fixes. Stop mis-filing unknown assistants; replace
+ silences with plain labels ("this assistant has no usage report"); remove duplicated and
+ dead configuration. These are owed no matter what else is decided.
+
Phase 1 — Local models, generically. Recognize any locally-run model server
+ (LM Studio, llama.cpp, MLX, vLLM, and whatever comes next) — not just one vendor — so
+ people running models on their own machines are first-class citizens.
+
Phase 2 — The plug-in door. Rework the kit's insides so an assistant is
+ something the kit looks up rather than something written into it. Then,
+ if the decision to open up is confirmed, publish one well-tested connection point so a
+ new assistant can be added by the people who want it.
+
Phase 3 — Hermes, and honest labels everywhere. Hermes arrives as the first
+ plug-in, maintained by its proposer rather than by us. Every status screen learns to say
+ what each assistant can and cannot do — and why.
+
Phase 4 — Parked, on purpose. Ideas that need more real-world evidence first
+ (deeper usage reporting for plug-ins, a stronger isolation model) are listed and
+ deferred, not forgotten.
+
+
+
What you get at the end
+
+ Whichever assistants you use: the same commands work the same way, reports name things
+ truthfully, and capability differences are stated rather than hidden. Adding tomorrow's
+ assistant no longer depends on one maintainer's spare time. And one thing deliberately
+ does not change: the kit never installs a plug-in on its own — you choose one
+ explicitly, and the kit tells you exactly what it is and what it is allowed to do before
+ anything runs.
+
+
+
+
+
+
1 · The state model: a bimodal host axis
+
+ The single most important structural fact: everything that validates a host is generic;
+ nearly everything that acts on a host is hardcoded by name.
+
+
+ The registry, capability vocabulary, and all six validators are host-agnostic and already
+ proven against synthetic hosts (capability-derived.test.mjs exercises a
+ future-host; trust-manifest.test.mjs:47-69 injects a
+ grok host end-to-end through setup disclosure). But dispatch, presentation,
+ attribution, guidance, teardown, and version tracking each carry literal
+ claude/codex/opencode branches. A conforming
+ fourth host registered today would validate cleanly, appear in exactly two surfaces
+ (the setup/sync install loop and the trust manifest), and be invisible — or actively
+ breaking — everywhere else.
+
+
+ This is why "inconsistent UX" is the right frame: the inconsistency is not a Hermes
+ problem. Several gaps below already bite OpenCode, the third built-in host.
+ The extensibility question (D-1) only raises the stakes; it does not create the debt.
+
+
+
2 · The de-facto host tiers
+
+ Three tiers already exist in behavior but not in vocabulary. The command surface calls all
+ of them "host," and ak host pick means something different at each tier.
+
+
+
Tier
Hosts
Can do
Cannot do
+
+
Session host
+
claude, codex
+
Drive the ruflo loop (ENABLE_*), be primary, statusline, quota channels, usage scorecard, MCP bridge
+
—
+
+
+
Supervised execution host
+
opencode
+
Route ak run activities; permission events intercepted (permission_required)
Route activities via an externally maintained adapter; host-declared usage sidecar
+
Everything tier 2 cannot, plus no interceptable permission event — hermes -z auto-approves by its own headless contract (disclosed, verified)
+
+
+
+ ADR-0020 already mandates tier 2's shape for OpenCode ("explicit, supervised, non-primary,
+ outside AQE provider routing, absent from vendor-diversity facts"). ADR-0029's capability
+ caps reproduce that shape for external hosts — tested policy, not new policy. What is
+ missing is the vocabulary: no surface tells the user which tier a host is in or why
+ a capability is absent (see D-2).
+
+
+
3 · Findings inventory
+
+ Severity: blocker crashes or corrupts;
+ major wrong or missing behavior a user will hit;
+ minor debt, dead code, or docs drift;
+ healthy baseline worth protecting.
+ Scope: today affects built-in hosts now;
+ ext manifests only if extensibility (D-1) proceeds.
+
+
+
3.1 Registry & validation — the healthy baseline
+
+
ID
Finding
Severity
Scope
+
F-00
Registry descriptors, closed capability vocabulary (registries.mjs:6-9,154-225), six host-agnostic validators, validateRegistries at construction, pure capability selectors, graceful runner degradation (runner.mjs:246-252), fully generic trust manifest (trust-manifest.mjs) and footprint install loop (footprint/install.mjs:170-178). Protect this.
healthy
today
+
+
+
3.2 Dispatch & lifecycle
+
+
ID
Finding
Severity
Scope
+
F-01
EXECUTION_ADAPTERS is a frozen 3-entry Map with a bidirectional invariant that throws at module import (execution/adapters.mjs:8-12,20-30). Any registered routable host outside the Map bricks ak run for everyone. ADR-0018 prose states the invariant in one direction only; the code enforces both. The single hardest extensibility gate — absent from ADR-0029 §8.
blocker
ext
+
F-02
Lifecycle adapter selection is a named import at five call sites (sync.mjs:11,195-197; x/host.mjs:19,326,617-619,642; setup.mjs:15,216). No lifecycle registry, no host.lifecycle field; validateHostAdapter doesn't accept one. A conforming third-party lifecycle has no dispatch path.
major
ext
+
F-03
ak uninstall bypasses runLifecycle entirely — calls retireOpencode(cfg) directly (uninstall.mjs:112-142). Third-party footprint would persist on disk permanently; even for built-ins, teardown has two code paths.
major
today
+
F-04
Setup permission handling authorizes only claude-host rules; removeUndisclosedPermissions (setup.mjs:95,102,120-128) would strip a third-party host's permissions and fail setup.
major
ext
+
+
+
3.3 Presentation
+
+
ID
Finding
Severity
Scope
+
F-05
status.mjs is split-brained: a generic host loop yields install+auth rows (:500-529), then hand-rolled blocks per host — codex MCP/bridge (:352-382), codex plugins (:387-400), an 88-line opencode block importing 8 symbols (:407-495), codex statusline row (:729-746), a claude-only blocks loop (:670). normalizedFacts is collected but consumed for exactly one field (:505-506) — everything else re-probes. Rendering from normalizedFacts is also the direction of the #129/#133/#136 status-vs-sync projection fixes.
major
today
+
F-06
About directory cards are a frozen literal (dashboard/about-directory.mjs:54-110) with a test-enforced registry↔directory parity invariant (about-directory.test.mjs:118-140,157-161,381-385; ddd/component-directory.md:164-165). Any registered host without an authored card fails CI; card matching also assumes npmPackage is non-null.
major
ext
+
F-07
Statusline is two host-specific patchers (claude helper, codex TOML) with cfg.statusline.codex as a literal config key; consistent with the commandStatusline cap — but statuslineSupported (hosts.mjs:37,53-55) has zero call sites: a dead capability consumer.
minor
today
+
+
+
3.4 Attribution & observability
+
+
ID
Finding
Severity
Scope
+
F-08
Usage scorecard needs seven per-host edits; worse, unknown hosts are misattributed, not omitted: non-codex sessions collapse to claude (usage-index.mjs:1407-1408), parseFile's 2-branch ternary falls through to parseClaude (:831-848), and a fourth root would collide in the single-flight cache (scanKey, :1169). Aggregation itself is generically string-keyed (:1083) — records just never reach it correctly.
major
today
+
F-09
Live sessions rewrite unknown hosts to 'internal' (live/event-schema.mjs:3,107) — namespace collision — and process-sessions.mjs:49-53 filters unknown binaries out entirely. No opencode transcript reader exists.
major
today
+
F-10
Quota is a fixed {claude, codex} record (quota.mjs:30-31,252-257). Consistent with ADR-0010's sanctioned channels — but hosts without a quota channel are silent rather than labeled ("no quota surface for this host").
minor
today
+
F-11
qeCourt.vendorOf is a literal prefix table (qeCourt.mjs:24-32); any unregistered host id maps to 'unknown', so N distinct third-party vendors collapse into one bucket — vendor-diversity findings can be spuriously tripped or spuriously satisfied.
major
ext
+
F-12
host.observability is declared-but-dead: OBSERVABILITY_REGISTRY is exported (registries.mjs:250) and imported nowhere; no dispatcher maps an observability id to a collector. It looks like the telemetry extension point and is validation metadata only.
minor
today
+
F-13
The provider-binding migrator's defaults map is {claude:'anthropic', codex:'openai'} (adapters/config.mjs:64-79); every other host is stamped unknown-via-<host>, provider null, provenance 'unknown' on every load. Already affects opencode.
major
today
+
+
+
3.5 Config & schema
+
+
ID
Finding
Severity
Scope
+
F-14
kit.json accepts unknown top-level keys silently (blind spread, config.mjs:99-124; envelope check only asserts integrations.hosts is an object, :52-82). A future hostAdapters key on an older ak round-trips untouched and does nothing — a silent no-op with no warning story.
major
ext
+
F-15
The DEFAULTS host map {claude:true, codex:false, opencode:false} is triplicated (config.mjs:28, providers.mjs:547, x/host.mjs:334).
minor
today
+
F-16
validateBinding with an unknown-host error code exists (bindings.mjs:72-84) and is called from nowhere in src/. The registry-aware validation the extension point needs is already written and dead.
minor
today
+
+
+
3.6 Guidance, paths, versions
+
+
ID
Finding
Severity
Scope
+
F-17
Guidance targets are a closed list of four literal names (blocks.mjs:280-292); customBlocks rows are generic over target names but retiredForTarget force-strips rows naming a fifth (:315). Meanwhile host.legacy.guidanceFile is declared, read once (hosts.mjs:34), and consumed by nothing. ADR-0030 §6 sidesteps this by reusing the agents target — the trap remains for any host whose file differs.
major
today
+
F-18
versions.mjs:70 hand-mirrors the host npm package list "to avoid an import cycle" — but footprint/install.mjs already imports HOST_REGISTRY, so the cycle is one-directional and solvable. A no-npm host is invisible to the drift nudge.
minor
today
+
F-19
lib/paths.mjs is named per-host constants with no hostDir(id) resolver; the registry carries no path metadata at all, so even a fully-looped consumer has nothing to loop over.
minor
ext
+
F-20
npmRoot(host.install.npmPackage) is called unguarded in footprint/install.mjs; the first host without an npm package breaks the census. (ADR-0029 §8 names this; trivially fixable independently.)
minor
ext
+
+
+
3.7 Routing & run
+
+
ID
Finding
Severity
Scope
+
F-23
Routing hardcodes HOST_PROVIDER = {claude, codex} (routing.mjs:25), DEFAULT_ROUTES (:203-216), MODEL_CATALOG (:56-72), and a strictly binary escalation swap — host === 'claude' ? 'codex' : 'claude' (:180,193,354). Plan eligibility itself is generic (:584-598). A third routable host is routable in name only: no ladder can name it and no seed includes it.
major
today
+
F-24
--primary-host validates generically against PRIMARY_HOSTS, but help text is literal (x/host.mjs:87-90) and the fallback unshifts 'claude' (:494).
minor
today
+
F-25
The Claude↔Codex MCP bridge is literal wiring (providers.mjs:622-681) — correct for a codex-specific feature, but there is no per-host "bridge capability" concept, so the asymmetry (codex delegable via MCP, opencode/hermes not) is nowhere stated to the user.
minor
today
+
F-26
Managed env emits only ENABLE_CLAUDE_CODE/ENABLE_CODEX (providers.mjs:686-698); hosts with no ruflo backend flag are simply absent, with no capability-derived explanation.
minor
today
+
+
+
3.8 Provider axis
+
+
ID
Finding
Severity
Scope
+
F-28
providerEntries derives capabilities from identity comparisons inside a .map (modelDiscovery: id === 'ollama'). Does not survive a second local provider. ADR-0028 §4's per-entry-data refactor is correct and needed regardless of the rest of PR #131.
major
today
+
F-29
AQE projection asymmetry: ollama is an AQE provider type; local-openai (ADR-0028) is deliberately not projected. Right call — but it must be stated in the ADR and surfaced in status, or it reads as a bug later.
minor
today
+
F-30
Hermes factual corrections required in ADR-0028/0030 (verified against source, see appendix): unofficial npm bridge exists; lmstudio is first-class, no mlx alias; api_mode: openai is invalid and silently dropped; --max-turns exists on chat -q; config dispatcher pointer wrong.
minor
ext
+
+
+
4 · Decision areas
+
+
+
D-1 · Extensibility posture the one genuinely user-owned call
+
Do we publish host adapters as an extension point (ADR-0029), stay closed, or absorb Hermes in-tree?
+
AAccept ADR-0029, amended. Publish the seam with the honest gate list (F-01…F-06, F-11, F-14, F-21), formally superseding ADR-0016's closed-registry clause. Cost: the in-tree refactors plus a loader and fixture adapter; contract consumers constrain refactors (bounded by the alpha-instability statement). Benefit: fourth-host requests answered once; conformance evidence about our own abstractions.
+
BDecline and reaffirm. Keep ADR-0016's closed registry, record the reaffirmation explicitly, and point Hermes at a fork. Cost: the fork drifts, no conformance evidence, the fifth-host request re-litigates this. Benefit: zero contract obligation while alpha churns.
+
CAbsorb Hermes in-tree (ADR-0017 style). ~14 files and ~8 test suites per host, permanent maintainer obligation for a CLI driven by another vendor's daily release cadence (40+ commits/day observed). The contributor themselves argues against this.
+
Recommendation — Option A, sequenced so the in-tree refactors land first and stand on their own (they delete host-specific code and fix bugs that bite OpenCode today). Critically: Phase 0 hygiene below is owed under every option, including B — misattribution and dead seams are not extensibility problems.
+
+
+
+
D-2 · Host tier vocabulary & UX
+
Three behavioral tiers exist (§2) with no product vocabulary. How do we make capability differences legible?
+
ACapability-derived labels everywhere. Status, about, and help render a tier label and per-capability explanations derived from the registry ("no quota surface for this host", "auto-approving — no permission event to intercept"). No new config; the registry already knows.
+
BDocs-only tiers. Name the tiers in HOST-SUPPORT.md and leave commands as-is. Cheaper, but the inconsistency the user experiences is in the commands, not the docs.
+
CFlatten. Pretend all hosts are equal. Rejected: it would misrepresent the Hermes approval posture, which ADR-0030 rightly insists on disclosing.
+
Recommendation — Option A. Every absence becomes a stated fact instead of silence: this is the single highest-leverage UX consistency move, it consumes the same capability flags the caps enforce, and it revives the dead consumers (F-07, F-12, F-26).
+
+
+
+
D-3 · Dispatch generalization depth
+
How far do the in-tree refactors go?
+
AMinimal: registry-driven lifecycle lookup only (F-02). Unblocks a loader but leaves status, uninstall, and guidance split-brained.
+
BFull generalization: lifecycle lookup (F-02) + uninstall through runLifecycle undo (F-03) + status rendered from normalizedFacts (F-05) + guidance targets derived from the registry (F-17) + the adapters.mjs invariant softened to built-ins with a merge seam (F-01) + setup permission authorization keyed by host (F-04).
+
Recommendation — Option B. Each piece deletes host-specific code, and the status refactor continues the #129/#133/#136 single-projection direction — it must preserve the configured-projection + repoRoot scope gate landed there. F-01's softer rule has in-corpus precedent (ADR-0019:75-76: a rung naming a host with no adapter records cli_unavailable and continues).
+
+
+
+
D-4 · Attribution policy for non-first-party hosts
+
Usage, live sessions, quota, and vendor facts currently misattribute or erase unknown hosts. What is the policy?
+
AExclude-and-label now. Unknown/uninstrumented hosts get an explicit bucket keyed by host id — never collapsed to claude (F-08), never rewritten to internal (F-09), never silently absent from quota (F-10). qeCourt.vendorOf returns a per-id unregistered:<host> verdict that never counts toward vendor diversity (F-11) — consistent with ADR-0020's "absent unless independently observed."
+
BIngestion contract later. A v2 adapter contract lets a host declare a usage sidecar (Hermes --usage-file is the model: host-declared, ADR-0021 configured-grade, written even on failure). Real value, but it needs the extension point to exist first and a provenance-grading decision.
+
CStatus quo. Rejected: it is not neutral — it actively corrupts the claude bucket and the internal namespace today.
+
Recommendation — A immediately (it is a correctness fix for the scorecard you already hardened in PR #60), B as a v2 contract item once D-1/Phase 2 lands.
+
+
+
+
D-5 · Config & schema hygiene
+
kit.json silently swallows the unknown; host defaults live in three places.
+
AWarn-on-unknown top-level keys (F-14) so a hostAdapters block on an older ak is a visible message, not a silent no-op; derive the DEFAULTS host map from the registry once (F-15); wire validateBinding into the load path or delete it (F-16); make the migrator's defaults registry-driven and stop re-stamping unknown-via-<host> on every load (F-13).
+
BStrict-reject unknown keys. Rejected: breaks forward-compatibility between ak versions sharing a kit.json — warn is the right strength.
+
Recommendation — Option A, all four items in one hygiene pass; each is small and none depends on D-1.
+
+
+
+
D-6 · Guidance, paths, versions derivation
+
Three registry fields are declared and consumed by nothing; three consumers hand-mirror what the registry knows.
+
AMake the registry the source: guidance targets derived from a real (non-legacy) registry field (F-17); HOST_PKGS derived from install.npmPackage (F-18); add path metadata / a hostDir(id) resolver or explicitly document paths as first-party-only (F-19); either implement observability dispatch or re-document the field as validation metadata (F-12).
+
BDelete the dead fields. Honest, but forfeits the derivation wins and re-opens each on the next host.
+
Recommendation — Option A for guidance/versions (both fix real drift hazards ADR-0017 §retrospective already named); for observability and paths, decide per field — a one-line ADR note ("validation metadata only") is an acceptable terminal state if no consumer is planned.
+
+
+
+
D-7 · Provider axis
+
ADR-0028 and the provider registry's shape.
+
AAccept ADR-0028 with the api_mode citation fix (F-30) and an explicit note on the AQE projection asymmetry (F-29); land the per-entry capability refactor (F-28) with it.
+
BVendor enumeration instead (mlx, lmstudio, …). Rejected in the ADR itself, and our verification strengthens the rejection: MLX has no Hermes alias either — users name their own providers.
+
Recommendation — Option A. This is the easiest accept in PR #131: small, independent, fixes F-28, and useful to the hosts already shipped.
+
+
+
5 · ADR amendments register
+
The prior decisions this work touches, and what happens to each. Nothing is amended silently.
+
+
ADR
Clause at stake
Action
Trigger
+
0016
"A closed, validated registry of built-in code, not an arbitrary third-party plugin runtime" (:59-61; non-goal :441-442)
Supersede that clause if D-1 = A; reaffirm explicitly if D-1 = B. Either way, decide on the record — ADR-0029 currently routes around it.
D-1
+
0018
Routability→adapter invariant stated one-directionally in prose; enforced bidirectionally at import (F-01)
Amend to the decided rule: bidirectional for built-ins, merge seam for registered adapters.
D-3
+
0019
cli_unavailable graceful degradation (:75-76)
Reaffirm; cite as the degradation norm F-01's softening follows.
D-3
+
0010
Sanctioned quota channels (statusline tee, codex app-server)
Amend: quota channels are a per-host capability; hosts without one are labeled, not silent (F-10).
D-2/D-4
+
0021
Provenance grades for inference evidence
Extend for host-declared usage sidecars (configured grade; Hermes --usage-file is the reference case).
D-4 (B)
+
0020
OpenCode's supervised, non-primary shape
Amend to name the host-tier taxonomy (§2) as product vocabulary.
D-2
+
0028
New — local OpenAI-compatible provider
Accept after the api_mode: chat_completions citation fix + F-29 note.
D-7
+
0029
New — extension point
Amend then accept (if D-1 = A): full gate list (F-01…F-06, F-11, F-14, F-21), ADR-0016 supersession, older-ak silent no-op mitigation.
D-1
+
0030
New — Hermes reference adapter
Correct then accept, contingent on 0029: F-30's four factual fixes. Its safety posture (YOLO disclosure, reverse-bridge exclusion) is verified and right.
D-1
+
0031 (new)
—
Author: "Host tiers and attribution surfaces" — records D-2 + D-4(A) as one decision, whichever way D-1 goes.
D-2/D-4
+
+
+
6 · Test & docs remediation
+
Tests that pin the host set (F-21, F-22)
+
+
Hard pins (7):hosts.test.mjs:10-11 (deepEqual(HOST_IDS, [...])); providers.test.mjs:222-225 (test name says "come only from registry capabilities", body hardcodes the set — fix the body to match the name); about-directory.test.mjs:118-140, 157-161, 381-385 (parity + order); execution-runner.test.mjs:459-469 (pins the import-time invariant); routing-primary.test.mjs:45-48 (stays green under the capability cap).
Fix pattern: derive expected sets from the registry (scoped to built-ins where the assertion is about built-ins), never from a fresh literal.
+
Sentinel hazard: five negative tests use gemini as the "unknown host" — they silently invert if that id ever registers (hosts.test.mjs:40-41,106-108; setup-host-flags.test.mjs:48-53; routing.test.mjs:155-158,163-169,212-217). Replace with an unmistakably synthetic id.
+
+
Docs stating a closed set as normative (F-27)
+
+
docs/HOST-SUPPORT.md — "the three execution hosts" + five fixed-arity matrices; consider generating the matrices from the registry.
+
docs/ddd/ubiquitous-language.md:143 — defines "host" by enumeration.
+
docs/PROVIDERS.md §3.5 — routing table prose.
+
docs/ddd/component-directory.md:164-165 — the parity invariant that is also test-enforced (F-06).
+
+
+
7 · Sequenced roadmap
+
+
+
Phase 0 — Consistency hygiene owed regardless of D-1
+
No decisions required · every item bites built-in hosts today · each independently shippable
+
+
F-08: unknown-host usage records → explicit per-id bucket, kill the collapse-to-claude and the scanKey collision.
+
F-09: live-session unknown hosts → explicit bucket, not 'internal'.
+
F-11: vendorOf → registry-driven with unregistered:<id>, never counted toward diversity.
+
F-13: migrator defaults registry-driven; stop restamping on every load.
+
F-15/F-16: single registry-derived DEFAULTS; wire or delete validateBinding.
+
F-07/F-12: remove or revive the dead capability consumers (statuslineSupported, observability field) — one-line ADR note if terminal.
+
F-20 + the plain-text subprocess capture: tiny, independently useful, and they de-risk Phase 3.
+
F-22: replace the gemini sentinels.
+
+
+
+
+
Phase 1 — Provider axis (D-7)
+
Accept ADR-0028 · independent of everything else
+
+
F-28: providerEntries → per-entry capability records (matches hostEntries).
+
local-openai row + user-declared bindings; F-29 asymmetry stated in ADR and surfaced in status; F-30 api_mode citation fix.
+
+
+
+
+
Phase 2 — The extensibility decision (D-1, D-3) and in-tree generalization
+
Gate: ADR-0016 supersession + amended ADR-0029 accepted · refactors delete host-specific code and stand alone even if the loader never ships
Subprocess/RPC adapter protocol (sandboxable, language-agnostic) — deferred per ADR-0029 §3 until a second adapter exists to generalize from.
+
Semver stability promise for the adapter contract — a GA-time decision (ADR-0020 companion).
+
+
+
+
8 · Appendix — Hermes verification summary
+
+ Verified against NousResearch/hermes-agent HEAD (v0.20.0, 2026-08-13; the files underlying
+ the claims last changed on or before 2026-08-08, so HEAD matches the ADRs' basis).
+ 5 of 7 claim groups confirmed at source level.
+
+
+
Claim group
Verdict
Notes
+
Python CLI, pip/uv, no npm
partial
Official: correct. But an unofficial npm bridge exists (hermes-agent by wyrtensi, v0.20.0, ships hermes/hermes-agent bins). ADRs must say "no official npm package"; detection must not infer identity/version from npm presence. PyPI carries 0.19.0.
+
YAML config, HERMES_HOME, profiles
partial
All confirmed; config actions are a superset (show|edit|get|set|unset|path|env-path|check|migrate). Pointer wrong: dispatcher is cmd_config in hermes_cli/subcommands/config.py, not config_command.
+
mcp add EOF-default silent no-op
confirmed
All five sub-claims; stronger than claimed — nohermes mcp subcommand except install can signal failure via exit code (returns swallowed at mcp_config.py:1073 → main.py:12872).
+
Oneshot -z behavior
confirmed
YOLO/accept-hooks env, devnull redirect, final-write channel, exit codes 0/1/2 exact, --usage-file on failure too. One correction: --max-turns exists on hermes chat (non-interactive via -q) — just not on -z.
+
Local provider aliases
partial
ollama/vllm/llamacpp → custom: confirmed (in auth.py, not runtime_provider.py). lmstudio is first-class with its own URL normalizer; no mlx alias. And api_mode: openai — as quoted in ADR-0028's reference config — is invalid and silently dropped; the valid value is chat_completions (newer key: transport).
+
mcp serve is a messaging bridge
confirmed
Exactly ten tools (conversations_list, messages_send, permissions_respond, …); zero delegation-shaped tools. The reverse-bridge exclusion is the right security call.
+
Docs corroboration
confirmed
Authoritative CLI reference is website/docs/reference/cli-commands.md (not the README). Local-model guides confirm MLX/llama.cpp via custom endpoints.
+
+
+
+ Bottom line. The contributor's evidence is accurate where it counts — every claim about
+ ak's internals was confirmed by the surface sweep, and both Hermes safety findings are real.
+ The gap is scope, not honesty: "a loader and two refactors that delete code" is nearer to
+ three blockers, eight majors, twelve test pins, and a docs corpus. Phase 0 is owed today under
+ every posture; the extensibility decision (D-1) is the only call that is genuinely yours alone.
+
+
+
+ Sources: ak surface sweep (file:line citations verified in-repo) · NousResearch/hermes-agent
+ source verification · docs/adr corpus · PR #131 diff. Companion: the PR #131 review response
+ references F-nn / D-n IDs from this document.
+
+
+
+
diff --git a/docs/adr/0029-host-adapter-extension-point.md b/docs/adr/0029-host-adapter-extension-point.md
index dd00f29..1998718 100644
--- a/docs/adr/0029-host-adapter-extension-point.md
+++ b/docs/adr/0029-host-adapter-extension-point.md
@@ -2,6 +2,13 @@
- **Status:** Accepted (experimental contract)
- **Date:** 2026-08-15
+- **Updated:** 2026-08-16
+- **Update note:** [ADR-0031](0031-capability-graduation-and-upstream-requests.md) amends this ADR's
+ "permanent caps" framing. The block on *self-declaring* `canBePrimary` / `aqeProvider` /
+ `commandStatusline` in the manifest is permanent (the safety invariant here), but the *capability*
+ is earnable through a conformance tier plus a maintainer grant recorded outside the manifest — up
+ to promotion to a first-party built-in. The schema, admission gate, consent model, and hook runner
+ in this ADR are unchanged.
- **Deciders:** agentic-kit maintainers
- **Related:** [ADR-0016](0016-capability-driven-integration-adapters.md) (closed-registry clause
superseded — see [Supersession](#supersession-of-adr-0016s-closed-registry-clause)),
diff --git a/docs/adr/0031-capability-graduation-and-upstream-requests.md b/docs/adr/0031-capability-graduation-and-upstream-requests.md
new file mode 100644
index 0000000..c912d9a
--- /dev/null
+++ b/docs/adr/0031-capability-graduation-and-upstream-requests.md
@@ -0,0 +1,191 @@
+# ADR-0031 — Capability graduation: earned parity for external host adapters, and the upstream request path
+
+- **Status:** Accepted (governance decision; implementation staged)
+- **Date:** 2026-08-16
+- **Deciders:** agentic-kit maintainers
+- **Related:** [ADR-0016](0016-capability-driven-integration-adapters.md),
+ [ADR-0018](0018-generalized-host-worker-execution.md),
+ [ADR-0019](0019-escalation-in-ak-run.md),
+ [ADR-0023](0023-fail-closed-operations-and-explicit-degradation.md),
+ [ADR-0029](0029-host-adapter-extension-point.md) (amends its "permanent caps" framing — see
+ [Amendment](#amendment-to-adr-0029))
+- **Amends:** ADR-0029, on one point only: the three capability caps are reframed from *permanent*
+ to *not self-declarable, but earnable*.
+
+## Context
+
+[ADR-0029](0029-host-adapter-extension-point.md) admitted external host adapters as a declarative
+manifest plus consented, subprocess-only hooks, behind `AK_EXPERIMENTAL_HOST_ADAPTERS=1`. To make
+the door safe, three capabilities were made **inexpressible** in the manifest schema —
+`canBePrimary`, `aqeProvider`, and `commandStatusline` — and ADR-0029 described that block as
+permanent.
+
+Two things push past that framing:
+
+1. **The product intent is full parity.** A host that clears conformance should be able to
+ participate exactly like a built-in — lead a run, own a status line, be accounted for in
+ quality — not sit permanently behind a glass wall. "Second-class forever" is not the goal;
+ "earn your way to first-class" is.
+
+2. **`ak` is downstream of two other systems.** [ruflo](https://github.com/ruvnet/ruflo)
+ orchestrates the agent loop and [agentic-qe](https://github.com/proffesor-for-testing/agentic-qe)
+ runs quality. Some parity ceilings are genuinely not `ak`'s to lift — they live upstream — and a
+ contributor chasing a conformance tier needs a real route to express what's missing, not a dead
+ end.
+
+This ADR resolves both. It keeps every safety property ADR-0029 established and adds the governance
+model that turns the caps from a wall into a ladder.
+
+## Decision
+
+### 1. Earned, never self-declared
+
+The manifest schema stays a **strict allow-list in which a capped capability is inexpressible**. An
+adapter can never *write down* that it is primary, an AQE provider, or a command-statusline owner.
+This is the safety invariant from ADR-0029 and it is permanent: self-declaration is the attack
+surface, so it stays closed forever.
+
+Parity comes through a **separate channel**. A capability is *earned* by passing a conformance tier
+and *granted* by the maintainer — recorded as a hash-pinned **capability grant** (the same
+edit-invalidation model as adapter consent), never as a field in the adapter's own manifest. The
+adapter never asserts the capability; conformance evidence plus an explicit maintainer grant confers
+it. `hostTierLabel()` and the registry already render behaviour from capabilities, so a granted
+capability lights up at every call site without special-casing.
+
+### 2. Tiered conformance
+
+Conformance becomes tiered rather than pass/fail. Each tier is a black-box test set — spawn the real
+host, assert the real behaviour, against an installed layout — that gates one capability:
+
+| Tier | Gates | Evidence shape |
+| ---- | ----- | -------------- |
+| `admission` | Registration through the fail-closed gate (ADR-0029, shipped) | manifest validates, admits, hooks run |
+| `session-driving` | `canDriveSession` — the host actually drives an interactive/oneshot session | a real session completes and is observed |
+| `activity-routing` | `canRouteActivities` — the host runs a supervised `ak run` worker to a structured result | a worker completes under the runner's contract (ADR-0018) |
+| `primary-eligible` | Grants `canBePrimary` — the host can *lead*: anchor routing, be escalated toward | leads a run and receives an escalation, per ADR-0019 |
+| `statusline` | Grants `commandStatusline` — renders a command-backed footer through supervised hooks | a footer renders and refreshes |
+
+Passing a tier records evidence; the maintainer's grant turns evidence into capability. A tier the
+adapter cannot meet because the capability is upstream is marked **gated** (§4), not failed.
+
+### 3. Two graduation destinations
+
+A conformed adapter lands in one of two places, the maintainer's call:
+
+- **Blessed external adapter** — added to a curated, hash-pinned list. It stays out-of-tree and
+ experimental, holding exactly the capabilities its tiers earned. Right for niche or long-tail
+ hosts the project does not want to own.
+- **Promoted built-in** — its host descriptor is adopted as a first-party registry entry. Because of
+ the registry-driven refactor delivered across Phases 0–2, this is a small, ordinary PR (a
+ registry entry, a lifecycle adapter, an About card). Once built-in, the caps no longer apply
+ *because it is now first-party code the maintainer vouches for* — that is what promotion means. Its
+ hook scripts either ship bundled or are reimplemented as in-process glue, the maintainer's choice.
+
+### 4. The upstream capability-request path
+
+When a conformance tier cannot be met, the first question is **whose capability is missing**:
+
+- **`ak`-local** (run execution, the trust CLI, remote manifest sources, quality-gate anchors) — the
+ project's to build. A normal `ak` change; no one to wait on.
+- **Upstream — agentic-qe** — being a recognized **AQE provider type** is not `ak`'s to grant.
+ agentic-qe's provider set is a closed, upstream-defined enumeration (verified against
+ `agentic-qe@3.13.10`: `ALL_PROVIDER_TYPES` plus a `createProvider` switch, extended only by an
+ upstream code change). The path: file a concrete capability request against agentic-qe (a
+ provider-plugin API), record the tier as `gated: agentic-qe#NNN`, and light it up when the upstream
+ release ships. *Interim:* quality still runs through the model provider underneath the host, so QE
+ is not blocked — only the host's own AQE identity is.
+- **Upstream — ruflo** — being a native ruflo **backend** (an `ENABLE_*` target that drives the loop)
+ is defined inside ruflo (grounded against `ruvnet/ruflo@45e65b5`: backend enablement is per-host
+ `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` / `ENABLE_GEMINI_MCP`, not an outside registration). The path:
+ request a documented backend-registration surface upstream. *Interim:* the host runs through `ak`'s
+ own supervised execution, just not as a ruflo-native backend.
+
+The maintainer champions the request upstream with a named extension point, tracks the gated tier so
+an adapter's status shows exactly what it waits on, and exposes the capability in the adapter contract
+once upstream releases it. This is what keeps "no limitations after conformance" honest: the
+limitations that *are* real get a documented, per-layer route to disappear.
+
+### 5. The contributor-to-built-in lifecycle
+
+Graduation runs on a fixed sequence. A contributor **authors** a manifest and hooks,
+**self-tests** against the conformance kit, and **publishes** — at which point any user may opt in
+behind the experimental flag with hash-pinned consent, needing nothing from the maintainer. To go
+further, the contributor **proposes** it with a conformance report; the maintainer **verifies** by
+re-running the conformance kit and reviewing the hook scripts (the only part that executes),
+**decides** the tier and destination (§3), and **releases**. Conformance is objective; the
+maintainer is the judge; trust rests on reproduced evidence plus a hook read, never on running the
+contributor's code inside `ak`.
+
+### 6. Freeze criteria
+
+`contract: 1` (ADR-0029) freezes and the experimental flag is dropped when a **real** external
+adapter (Hermes first) clears the full conformance kit and survives one release of soak. Graduation
+of a capability tier and the freeze of the contract are distinct: tiers can be earned while the
+contract is still experimental.
+
+## Amendment to ADR-0029
+
+ADR-0029 states the three caps are permanent. This ADR amends that: **the block on
+*self-declaration* is permanent; the *capability* is earnable** through a conformance tier and a
+maintainer grant (§1, §2). ADR-0029's schema, admission gate, consent model, and hook runner are
+unchanged — capability grants are additive and live outside the manifest. A matching update note is
+added to ADR-0029 pointing here.
+
+## Consequences
+
+- An external adapter has a documented, evidence-gated route to full parity, up to and including
+ shipping as a built-in — without ever loading third-party code into the `ak` process and without
+ ever letting a manifest self-assert a capability.
+- The maintainer's review burden is bounded and objective: reproduce a conformance report, read the
+ hooks, grant a tier. No new trust primitive beyond the hash-pinned grant.
+- Real upstream ceilings are neither hidden nor faked: they become tracked capability requests with
+ an honest interim behaviour and a light-up path.
+- `ak` positions itself as the integrator that *shapes* its substrates rather than only consuming
+ them — the upstream-request path is a first-class part of the model, not a footnote.
+
+## Implementation status
+
+Per the ADR discipline this repository adopted (a dated, self-graded table before an Accepted claim
+rests on delivery): the **governance decision** is accepted; the **machinery** is staged and mostly
+unbuilt. This table is the source of truth for what is real.
+
+| Piece | Status (2026-08-16) | Note |
+| ----- | ------------------- | ---- |
+| Admission gate, consent store, hook runner, conformance kit (`admission` tier) | **Working** | ADR-0029, merged (PR #149) |
+| `ak host adapters trust` CLI (records consent/grants) | **Proposed — not built** | Smallest next step; today admission refuses `consent-required` |
+| External execution (`ak run` drives an admitted host) | **Proposed — not built** | The seam is a comment-only lookup today |
+| External lifecycle execution wired into setup/sync/uninstall | **Proposed — not built** | Loops are built-in-scoped by design until generalized |
+| Tiered conformance harness (`session-driving` … `statusline`) | **Proposed — not built** | Extends the single conformance kit |
+| Capability-grant store + promotion command | **Proposed — not built** | Hash-pinned, mirrors consent |
+| Remote manifest sources (npm / URL) + resolve→hash ordering | **Proposed — not built** | File-path manifests only today |
+| Upstream request tracking (`gated: #NNN` against a tier) | **Proposed — not built** | Needs a place to record per-tier gating |
+| A real external adapter (Hermes) clearing the kit → contract freeze | **Not started** | Freeze criterion (§6) |
+
+## Alternatives considered
+
+- **Keep the caps permanent (ADR-0029 as written).** Rejected: it makes external hosts second-class
+ forever and contradicts the product intent of full parity after conformance.
+- **Let the manifest self-declare capabilities, checked at runtime.** Rejected outright. This is
+ ruflo's own cautionary tale, surfaced in the research sweep behind this work: a fully typed
+ capability-permission system shipped with *zero runtime enforcement*. Runtime checks on a
+ self-asserted capability are exactly the honor-system trap the strict-allow-list schema exists to
+ avoid. Self-declaration stays inexpressible; capability comes from earned evidence plus an explicit
+ grant.
+- **Treat every ceiling as `ak`-local and build around upstream.** Rejected as dishonest and
+ unmaintainable: agentic-qe's provider enum and ruflo's backend model are upstream facts. Faking a
+ local shim (e.g. projecting an unknown host into agentic-qe's config) would fabricate an identity
+ the upstream tool never declared it understands. The upstream-request path (§4) is the honest
+ alternative.
+
+## References
+
+- ADR-0029 (the extension point, schema, admission, consent, hook runner) and its amendment above.
+- ADR-0018 (supervised worker contract), ADR-0019 (bounded escalation) — the substance of the
+ `activity-routing` and `primary-eligible` tiers.
+- Upstream facts grounded in a source-cited research sweep: `agentic-qe@3.13.10`
+ (`ALL_PROVIDER_TYPES`, the `createProvider` switch, the closed provider enum) and
+ `ruvnet/ruflo@45e65b5` (`ENABLE_*` backend model). Re-verify against upstream HEAD before filing an
+ actual capability request.
+- Companion explainer for consumers and implementers:
+ [`docs/HOST-EXTENSIBILITY-EXPLAINER.html`](../HOST-EXTENSIBILITY-EXPLAINER.html); design dossier:
+ [`docs/ADAPTER-CONTRACT-DOSSIER.html`](../ADAPTER-CONTRACT-DOSSIER.html).
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 29b4842..c6d2bb9 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -38,6 +38,7 @@ Consequences**, and cites the grounded source it rests on where relevant.
| [0027](0027-shared-project-census.md) | One project census, four scopes, every count explains itself | Implemented |
| [0028](0028-local-openai-compatible-providers.md) | One generic local OpenAI-compatible provider, not a vendor enumeration | Accepted |
| [0029](0029-host-adapter-extension-point.md) | External host adapters: declarative manifest, subprocess hooks | Accepted (experimental contract) |
+| [0031](0031-capability-graduation-and-upstream-requests.md) | Capability graduation: earned parity for external adapters, and the upstream request path | Accepted (governance; implementation staged) |
Theme: ADRs **0001–0006** define **dual-host LLM routing and leadership** — how `ak` lets ruflo route
each development activity (architecture, implementation, testing, review, …) to the right host (Claude
@@ -213,3 +214,14 @@ routable-host invariant, uninstall-through-undo, permission authorization by hos
surfaces policy, and kit.json's unknown-key warning — landed in wave 1 and Phase 0; registry↔directory
test pins remain wave 3), and stays experimental until a real external adapter clears the
conformance kit and a release of soak.
+
+**0031** amends 0029 on one point and adds the governance around it. The three capability caps
+(`canBePrimary`, `aqeProvider`, `commandStatusline`) stay *inexpressible* in the manifest — that
+block on self-declaration is permanent — but the capability itself becomes *earnable*: passing a
+conformance tier plus an explicit maintainer grant (hash-pinned, outside the manifest) confers it,
+up to and including promotion to a first-party built-in with full parity. It also records the
+upstream-request path: some ceilings are not `ak`'s to lift (being an AQE provider type is
+agentic-qe's closed enum; being a native ruflo backend is ruflo's `ENABLE_*` model), so those become
+tracked capability requests with honest interim behaviour rather than pretended support. Accepted as
+a governance decision; the machinery (the trust CLI, external execution, tiered conformance, the
+grant store) is staged and self-graded in the ADR's implementation-status table.