From 6e79a462af8fb38cb14639fb6ccb97c2c7011974 Mon Sep 17 00:00:00 2001 From: Chris Phillipson Date: Sun, 16 Aug 2026 07:57:13 -0700 Subject: [PATCH 1/2] docs: ADR-0031 ratifies the capability-graduation model and the upstream request path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accepts, as a governance decision, that external host adapters reach full parity by EARNING capabilities through tiered conformance plus an explicit maintainer grant — never by self-declaring them (that block stays permanent, the safety invariant from ADR-0029, which this amends). Records the two graduation destinations (blessed external, promoted built-in), the contributor-to-built-in lifecycle, and the upstream capability-request path: some ceilings aren't ak's to lift (AQE provider-type identity is agentic-qe's closed enum; native ruflo backend is ruflo's ENABLE_* model), so those become tracked requests with honest interim behavior. Carries a dated Working/Proposed self-grade — the machinery (trust CLI, external execution, tiered conformance, grant store) is staged, not built. ADR-0029 gets a matching amendment note. --- docs/adr/0029-host-adapter-extension-point.md | 7 + ...bility-graduation-and-upstream-requests.md | 191 ++++++++++++++++++ docs/adr/README.md | 12 ++ 3 files changed, 210 insertions(+) create mode 100644 docs/adr/0031-capability-graduation-and-upstream-requests.md 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. From 506974bf519aa9618e1df9706924c47b36fe81bc Mon Sep 17 00:00:00 2001 From: Chris Phillipson Date: Sun, 16 Aug 2026 07:57:13 -0700 Subject: [PATCH 2/2] docs: local snapshots of the three companion artifacts, each linking its origin The host-extensibility explainer (consumer + implementer walkthrough), the adapter-contract dossier (the research synthesis behind ADR-0029/0031), and the host/provider consistency master document now live in docs/ as self-contained HTML, each carrying an origin banner back to its live Claude artifact so the canonical source is one click away. --- docs/ADAPTER-CONTRACT-DOSSIER.html | 698 ++++++++++++++++ docs/HOST-EXTENSIBILITY-EXPLAINER.html | 1022 ++++++++++++++++++++++++ docs/HOST-PROVIDER-CONSISTENCY.html | 613 ++++++++++++++ 3 files changed, 2333 insertions(+) create mode 100644 docs/ADAPTER-CONTRACT-DOSSIER.html create mode 100644 docs/HOST-EXTENSIBILITY-EXPLAINER.html create mode 100644 docs/HOST-PROVIDER-CONSISTENCY.html diff --git a/docs/ADAPTER-CONTRACT-DOSSIER.html b/docs/ADAPTER-CONTRACT-DOSSIER.html new file mode 100644 index 0000000..ca0dd2e --- /dev/null +++ b/docs/ADAPTER-CONTRACT-DOSSIER.html @@ -0,0 +1,698 @@ + + + + + +Adapter Contract Dossier + + + +
+
📎 Origin. Authored as a Claude artifact; this file is the repository’s local snapshot for review. Live version: https://claude.ai/code/artifact/d611cd1c-03a4-4c4b-9b9c-94d129b7ee0d
+ +

agentic-kit · phase 2 design input · grounded 2026-08-14/15

+

Adapter Contract Dossier

+

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

+
+ + + + + + + + + +
SweepGrounded againstHeadline
rufloruvnet/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).
hostsClaude Code docs (2026-08-14) · Codex rust-v0.147.0 · OpenCode v1.18.18Manifest-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.
hermesNousResearch/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-qeagentic-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.

+
+ + + + + + + + + + + + + + + + + + + + + + +
AxisInterfaceWhere
RegistriesHOST_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
BindingsassertValidBinding (throwing) · validateBinding (error list — + wired to ak host status warnings) · resolveBinding · + validateEndpoint (loopback-http / remote-https / secret-param rules)adapters/bindings.mjs · adapters/config.mjs
LifecycleLIFECYCLE_OPERATIONS = [detect, plan, apply, verify, undo] · + validateLifecycleAdapter · runLifecycle (dry-run gate on apply) · + ownership()/mayUndo() teardown-safety receiptsadapters/lifecycle.mjs · adapters/ownership.mjs
ExecutionvalidateExecutionAdapter — 8 required methods + [readiness, prepare, launch, observe, interpret, summarize, cancel, cleanup] · + validateWorkerResult · EXIT_CATEGORIES incl. + cli_unavailable graceful degradation · + createSubprocessExecutionAdapter · + createJsonlSummaryCapture / createPlainTextSummaryCaptureexecution/schema.mjs · execution/subprocess.mjs
FactsnormalizedFacts (schemaVersion: 1, provenance-bearing) · + provenance()/mergeProvenance() · mergeFacts · + normalizeIntegrationFactsadapters/facts.mjs
TrustPer-host trust.changes declarations (kinds: auto-approve, mcp-registration, + lifecycle-extension, host-integration; scoped, operation-gated) — disclosed before first + mutation; synthetic-host injection already test-proven end-to-endadapters/registries.mjs · tests/kit/trust-manifest.test.mjs
ConfigmigrateIntegrationConfig (registry-derived native providers, idempotent, + ambiguity-guarded) · versioned integrations/routing envelopesadapters/config.mjs · lib/config.mjs
+ +

5.2 The honest verdict: not spaghetti — bimodal

+

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.

+ +
+ + + + + + + + + + + + + commands + setup · sync · host + run · status · usage + uninstall · live · about + + + the shell — reach-by-name (the debt) + OPENCODE_LIFECYCLE_ADAPTER named import ×5 · F-02 + frozen 3-host execution map, throws at import · F-01 + status: 88-line opencode block · F-05  ·  routing literals · F-23 + guidance: 4 literal targets · F-17  ·  uninstall skips undo · F-03 + claude-only permission pass · F-04  ·  fixed quota record · F-10 + + + the spine — reach-by-lookup (the base) + capability queries · defaultHostMap() · migrator derivation + validators on every load · trust disclosure · facts merge + truthful attribution (usage · live · court) — moved here + by Phases 0–1 + + + contract core + registries + validators + lifecycle · 5 verbs + execution · 8 methods + facts · schemaVersion 1 + bindings + endpoints + trust manifest + construction-enforced, + host-neutral, tested + + + + + + by name + by lookup + + +
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.
+
+ +
+ + + + + + + + + + + + ak sync + kit.json config + adapters core + opencode adapter + (named import — F-02) + host surfaces + + + + + + + + loadKitConfig() + + migrateIntegrationConfig() + + integrations v2 — registry-derived defaults + + runLifecycle(detect · plan · apply · verify) — reached by name, not lookup + + probe bins · write configs (backup-first) + + facts (provenance-graded) + + lifecycleResult · ownership receipt + + the five verbs are host-neutral — but only one host's adapter is reachable, and only by its name + + +
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

+ +
+ + + + + + + + + + + + + adapter package + manifest.json — data only: + registry entry · detection + lifecycle data · hook argv + declared trust changes + + + admission gate — fail closed + same validators as built-ins + caps by schema shape — + canBePrimary inexpressible + hash consent · re-consent on edit + contract: 1 version match + refused ⇒ named reason, + + + registry + built-ins + admitted + external entries + (experimental flag) + + + every command + setup · sync · run · status + uninstall · usage · guidance + + + contract core + lifecycle · 5 verbs + execution · 8 methods + facts · provenance + bindings · trust + unchanged — + already the spine + + + refused — built-ins unaffected + + + admit? + + merge + + + one lookup path + + drives + + +
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.
+
+ +
+ + + + + + + + + + + ak sync + admission gate + registry + adapter hook + (subprocess, supervised) + host surface + + + + + + + kit.json hostAdapters[] + + validate · caps · hash · contract ⇒ merge (or refuse, named) + + runLifecycle(host) — registry lookup, any admitted host + + spawn hook — timeout · captured · consented + + apply (backup-first) + + claimed result + + independent verify — ak reads the surface itself; a host's success claim is never trusted + + uninstall runs the same path with undo — the ownership receipt decides what may be removed + + +
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

+
+ + + + + + + + + + + + + + + +
ElementShapePrecedent
Registrationkit.json hostAdapters: [{name, source, contract: 1}] — explicit, never scannedruflo PluginRegistry.register(); Claude Code --plugin-dir
ManifestOne JSON document: registry entry + detection + lifecycle descriptors + declared trust changes + hook commands; semver'd; content-hashedCodex plugin.json (semver required); aqe QEPluginManifest
AdmissionValidate → cap-check (schema-structural) → hash-verify → contract-version match → per-adapter admit/refuse with named reasonaqe checkPluginSecurity() fail-closed install; ADR-0023
Trustthird-party-adapter trust-manifest kind; hash-pinned hooks, edit re-consent; disclosure before first mutationCodex hook hash-pinning; Hermes consent allowlist; ADR-0021/0023
LifecycleFive verbs (detect/plan/apply/verify/undo) — declarative where possible, supervised subprocess hooks where not; uninstall routed through undo (F-03)ak's own validateLifecycleAdapter; OpenCode's low-trust/high-trust tier split
ConformanceFixture adapter in tests/ + black-box harness: real spawns, installed layout, negative assertions, numeric pass/fail; graduation gate for freezing contract: 1ruflo ADR-102 harness + smoke-as-contract (and its #2912 counter-example)
ADR disciplineOne contract ADR per published seam, pinned to an ak version with CI staleness check; graded Working/Demo/TBD table; Accepted only with dated evidenceruflo per-plugin contract ADRs + ADR-015-v2's self-grading table
+ +

8 · Impact on the Phase 2 plan

+
    +
  • 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.

+
Settled — this dossier +

Q2 · Mechanism. Declarative manifest + consented subprocess hooks; no + in-process third-party code; structural capability caps; hash-pinned trust; fail-closed + admission with per-adapter isolation.

+
Open — recommendation stands +

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:

+
    +
  • ruflo: v3/implementation/adrs/ADR-004, ADR-015-v2 · v3/docs/adr/ADR-102, ADR-145/337 · v3/@claude-flow/plugins/src/** · plugins/ruflo-*/docs/adrs/0001-* · issues #1859/#1862/#2870/#2912/#2971/#2254 @ 45e65b5
  • +
  • hosts: code.claude.com/docs (plugins, skills, hooks, mcp, settings) · learn.chatgpt.com/docs + developers.openai.com/codex/plugins @ rust-v0.147.0 · opencode.ai/docs @ v1.18.18 · issues cited inline
  • +
  • hermes: hermes_cli/{oneshot,mcp_config,runtime_provider,_parser,config_migrations}.py · acp_adapter/ · ui-tui/ · agent/shell_hooks.py @ cb47f59
  • +
  • agentic-qe: shared/llm/router/* · plugins/manifest.d.ts · skills/qe-court/referee.js · validation/anchor-set.js @ 3.13.10
  • +
+ +
+ + + diff --git a/docs/HOST-EXTENSIBILITY-EXPLAINER.html b/docs/HOST-EXTENSIBILITY-EXPLAINER.html new file mode 100644 index 0000000..5c4fd3a --- /dev/null +++ b/docs/HOST-EXTENSIBILITY-EXPLAINER.html @@ -0,0 +1,1022 @@ + + + + + +Room for More Hosts + + + +
+
📎 Origin. Authored as a Claude artifact; this file is the repository’s local snapshot for review. Live version: https://claude.ai/code/artifact/931c19be-6ce3-45bb-a51d-d8289832cea6
+ +
+

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.

+ +
+ + + + + + + + + BUILT-IN HOSTS — here today + + Claude Code + can lead + + Codex + can lead + + OpenCode + routing only + Shipped in the kit — trusted because they ARE the kit. + + + + host registry + read by every command + + + + every command + setup · status · run + sync · uninstall + + + EXTERNAL ADAPTERS — the door (experimental) + + Hermes + candidate + + Gemini CLI + candidate + + + Described by a manifest the author ships — never code that runs in the kit. + + + + admission gate + validate · cap-check + consent · fail closed + + + + + compiled in + + + + + if admitted + + + + +
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.
  • +
  • Fully wired: setup, status, running work, teardown.
  • +
  • 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.

+ +
+ + + + + + + + + BEFORE — reach each host by name + + new host + + dispatch + status + uninstall + permissions + routing + guidance + usage + …and more + + + + + + + + ~10 edits · miss one and the host silently breaks + + + + + AFTER — describe it once, read everywhere + + one entry + registry or manifest + + host registry + capability queries + + + dispatch + status + uninstall + permissions + routing + guidance + + + + + + they ask the registry — never a name + + +
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.

+ +
+ + + + + + + + + the manifest + one JSON file — pure data + + name, version, contract + host { capabilities } + detection { bin } + driving { surfaces } + lifecycle { hooks } + trust { changes } + + Any unknown field is rejected — + the schema is a strict allow-list. + + + + admission gate + + ✓ shape is valid + ✓ no forbidden claims + ✓ contract version matches + ✓ not shadowing a built-in + ✓ consent matches these bytes + + any miss → refused, by name + + + + + host registry + joins the built-ins + + + + + the hooks — run at arm's length + + separate process + no shell · env allow-list + + time-boxed + killed if it hangs + + when a step needs logic + + +
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

+
+ + + + + + + + +
QuestionBuilt-in hostExternal 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 kitno — subprocess only
Can it install software silently?only disclosed stepsno — may not even name a package
What if its manifest is edited after it's trusted?n/atrust is void until re-approved
What happens if it's broken or malicious?n/arefused 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.

+ +
+ + + + + + + + + + ak run + "add feature" + + + + + supervisor (runner) + spawns each worker bounded: + timeout · escalation · result + + + + architecture → + claude + · PRIMARY + supervised worker · leads the run + + + implementation → + codex + supervised worker + + + testing → + codex + supervised worker + + + review → + claude + supervised worker + + + + + + + + + + on failure, + escalates to + the primary + + + An enabled OpenCode would appear here as + a supervised worker too — never the PRIMARY row. + + Leading = anchors the run (routing + escalation). Supervised = executes one assigned step, bounded. + + +
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.
+
+ +
+ + + + + + + + +
DimensionLeading (primary)Being supervised (routed worker)
Capability flagcanBePrimarycanRouteActivities
Which built-ins have itClaude, CodexClaude, Codex, OpenCode
What it meansAnchors 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 byak host pick --primary-hostthe routing policy, per activity (or a run-local --route)
Degree of controlleads — decides the shape, anchors escalationgiven a task, host, model, and deadline; runs; reports back
Bounded?it is the session leadyes — timeout, a bounded escalation ladder, a structured result
OpenCodenever (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.

+ +
+ + + + + + + HERE NOW + Admit + register an + external host + from a manifest + + + + Trust + a command to + approve an adapter + (smallest next step) + + + Run + actually drive + work through it + + + Prove + a real adapter + (Hermes) passes + the conformance kit + + + Open + freeze the contract, + drop the flag + + + + + + the experimental switch stays ON until the last step + + +
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):

+
{
+  "name": "hermes",
+  "version": "0.1.0",
+  "contract": 1,
+  "host": {
+    "id": "hermes",
+    "label": "Hermes",
+    "capabilities": {
+      "canDriveSession": true,
+      "canRouteActivities": true
+      // canBePrimary / commandStatusline: not allowed — omitted by force
+    },
+    "install": { "bin": "hermes", "externalInstallPolicy": "detect-never-overwrite" }
+  },
+  "detection": { "bin": "hermes", "versionArgs": ["--version"] },
+  "driving": { "surfaces": ["cli-subprocess"] },
+  "lifecycle": {
+    "detect": { "hook": { "command": ["hermes", "doctor", "--json"] } }
+  },
+  "trust": { "changes": [ /* what the adapter will touch, disclosed up front */ ] }
+}
+

It is registered in the user's own kit.json, and its exact bytes are approved:

+
// kit.json
+"hostAdapters": [
+  { "name": "hermes", "source": "~/.config/ak/adapters/hermes.json", "contract": 1 }
+]
+
    +
  • 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.

+ +
+ + + + + + + + + + CONTRIBUTOR — permissionless, no maintainer needed to start + + 1 · Author + manifest + + hook scripts + + + 2 · Self-test + run the conformance + kit until green + + + 3 · Publish + ship the manifest + (their own repo) + + + 4 · Propose + open a PR with the + conformance report + + + + + + + + any user can opt in now + experimental flag + consent + + + + + PR + conformance evidence ↓ + + + MAINTAINER — the judge + + 5 · Verify + reproduce conformance + + review the hook scripts + + + 6 · Decide + which tier? + + + + + Bless — stays external + curated list, hash-pinned; + maintainer vouches, users opt in + + + + + Promote — into the tree + becomes a registry entry — + a normal PR, now easy + + + + + 7 · Release — built-in + next version ships it, no flag — + full parity, like Claude / Codex + + + + + Experimental ▸▸▸ every step right is more conformance proven and more trust earned ▸▸▸ full built-in parity + + + + + contributor + + maintainer + + external / experimental outcome + + built-in / shipped outcome + + + +
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

+
    +
  1. 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.
  2. +
  3. 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.
  4. +
  5. 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.
  6. +
  7. 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.
  8. +
  9. 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.
  10. +
  11. Decide the tier. maintainer Based on which + conformance tiers passed, the maintainer chooses where it lands: +
      +
    • Bless it as a curated external adapter stays 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 tree becomes 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.
    • +
    +
  12. +
  13. 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.
  14. +
+ +
+
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.

+ +
+ + + + + + + + + + Contributor's adapter + reaching for a conformance tier + + conformance + + + + agentic-kit — the integrator + the host registry · the adapter contract · execution · consistency + Most conformance gaps are fixed right here — a normal agentic-kit PR. + + + + ruflo — orchestration substrate + drives the loop · memory · routing · swarms + host backends are ENABLE_* targets, + defined inside ruflo, not from outside + ask: register a new host backend + + + + agentic-qe — quality substrate + test gen · coverage · quality court + the provider list is a closed upstream + enum — a host can't self-add as one + ask: a provider-plugin API + + + + capability request ↓ + + + ships → lights up ↑ + + + + capability request ↓ + + + ships → lights up ↑ + + +
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 blockerOwnerWhat happens
Can't run work through the host; no trust command; local-file manifests onlyagentic-kitThe 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 typeagentic-qeUpstream — 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)rufloUpstream — 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 gateagentic-kitMostly 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.

+ +
+ + + diff --git a/docs/HOST-PROVIDER-CONSISTENCY.html b/docs/HOST-PROVIDER-CONSISTENCY.html new file mode 100644 index 0000000..44025a9 --- /dev/null +++ b/docs/HOST-PROVIDER-CONSISTENCY.html @@ -0,0 +1,613 @@ + + + + + +Host & Provider Consistency — agentic-kit + + + +
📎 Origin. Authored as a Claude artifact; this file is the repository’s local snapshot for review. Live version: https://claude.ai/code/artifact/db1bae65-adf9-45f1-80f1-163dcc1af50c
+ +
+

agentic-kit · master consistency document

+

Host & provider axis — findings, gaps, options, recommendations

+

+ Prepared 2026-08-13 · rev 2 (executive brief added) + Trigger: PR #131 (ADR-0028/0029/0030, adrianco) + Repo: pacphi/agentic-kit @ main (8aad47b) +

+

+ 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. +

+
+ + + + + + + + + + + + + + + + + + + +
TierHostsCan doCannot do
Session hostclaude, codexDrive the ruflo loop (ENABLE_*), be primary, statusline, quota channels, usage scorecard, MCP bridge
Supervised execution hostopencodeRoute ak run activities; permission events intercepted (permission_required)Primary, AQE provider, statusline, vendor-diversity credit, usage/quota/live-session attribution
External execution host proposedhermes (ADR-0030)Route activities via an externally maintained adapter; host-declared usage sidecarEverything 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

+
+ + +
IDFindingSeverityScope
F-00Registry 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.healthytoday
+ +

3.2 Dispatch & lifecycle

+
+ + + + + +
IDFindingSeverityScope
F-01EXECUTION_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.blockerext
F-02Lifecycle 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.majorext
F-03ak 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.majortoday
F-04Setup 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.majorext
+ +

3.3 Presentation

+
+ + + + +
IDFindingSeverityScope
F-05status.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.majortoday
F-06About 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.majorext
F-07Statusline 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.minortoday
+ +

3.4 Attribution & observability

+
+ + + + + + + +
IDFindingSeverityScope
F-08Usage 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.majortoday
F-09Live 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.majortoday
F-10Quota 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").minortoday
F-11qeCourt.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.majorext
F-12host.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.minortoday
F-13The 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.majortoday
+ +

3.5 Config & schema

+
+ + + + +
IDFindingSeverityScope
F-14kit.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.majorext
F-15The DEFAULTS host map {claude:true, codex:false, opencode:false} is triplicated (config.mjs:28, providers.mjs:547, x/host.mjs:334).minortoday
F-16validateBinding 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.minortoday
+ +

3.6 Guidance, paths, versions

+
+ + + + + +
IDFindingSeverityScope
F-17Guidance 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.majortoday
F-18versions.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.minortoday
F-19lib/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.minorext
F-20npmRoot(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.)minorext
+ +

3.7 Routing & run

+
+ + + + + +
IDFindingSeverityScope
F-23Routing 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.majortoday
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).minortoday
F-25The 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.minortoday
F-26Managed 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.minortoday
+ +

3.8 Provider axis

+
+ + + + +
IDFindingSeverityScope
F-28providerEntries 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.majortoday
F-29AQE 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.minortoday
F-30Hermes 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.minorext
+ +

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.

+
+ + + + + + + + + + + +
ADRClause at stakeActionTrigger
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
0018Routability→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
0019cli_unavailable graceful degradation (:75-76)Reaffirm; cite as the degradation norm F-01's softening follows.D-3
0010Sanctioned 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
0021Provenance grades for inference evidenceExtend for host-declared usage sidecars (configured grade; Hermes --usage-file is the reference case).D-4 (B)
0020OpenCode's supervised, non-primary shapeAmend to name the host-tier taxonomy (§2) as product vocabulary.D-2
0028New — local OpenAI-compatible providerAccept after the api_mode: chat_completions citation fix + F-29 note.D-7
0029New — extension pointAmend 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
0030New — Hermes reference adapterCorrect 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).
  • +
  • DEFAULTS literal pins (5): setup-host-flags.test.mjs:93-101, provider-cli.test.mjs:242,279, routing-config.test.mjs:162-163, integration-config.test.mjs:176-177.
  • +
  • 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

+
    +
  • F-01: adapters.mjs invariant → built-ins-bidirectional + merge seam (ADR-0019 precedent).
  • +
  • F-02: lifecycle registry field + five call sites → registry lookup; OpenCode passes unchanged.
  • +
  • F-03: uninstall through runLifecycle undo.
  • +
  • F-04: setup permission authorization keyed by host.
  • +
  • F-05: status rendered from normalizedFacts — preserving the #129/#133/#136 projection + scope-gate behavior.
  • +
  • F-17: guidance targets derived from the registry.
  • +
  • F-06: default host card generation or built-in-scoped parity.
  • +
  • F-14: warn-on-unknown kit.json keys (the hostAdapters silent no-op mitigation).
  • +
  • F-21: test derivations from the registry.
  • +
  • The loader: explicit hostAdapters registration, contract: 1, capability caps, per-adapter fail-closed admission, third-party-adapter trust-manifest kind, fixture adapter in tests/.
  • +
+
+ +
+

Phase 3 — Hermes as first external consumer (ADR-0030) + tier vocabulary (D-2)

+

Adapter externally maintained by its proposer · ak carries no hermes-specific code

+
    +
  • ADR-0030 corrections (F-30) then acceptance; conformance evidence flows back.
  • +
  • D-2 capability-derived tier labels in status/about/help; F-10 quota labeling; F-25/F-26 stated asymmetries.
  • +
  • Docs corpus update (§6), HOST-SUPPORT matrices generated from the registry.
  • +
+
+ +
+

Phase 4 — Deferred, deliberately

+

Each needs evidence that doesn't exist yet

+
    +
  • D-4 (B): v2 ingestion contract for host-declared usage sidecars (ADR-0021 extension).
  • +
  • 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 groupVerdictNotes
Python CLI, pip/uv, no npmpartialOfficial: 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, profilespartialAll 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-opconfirmedAll five sub-claims; stronger than claimed — no hermes mcp subcommand except install can signal failure via exit code (returns swallowed at mcp_config.py:1073main.py:12872).
Oneshot -z behaviorconfirmedYOLO/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 aliasespartialollama/vllm/llamacppcustom: 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 bridgeconfirmedExactly ten tools (conversations_list, messages_send, permissions_respond, …); zero delegation-shaped tools. The reverse-bridge exclusion is the right security call.
Docs corroborationconfirmedAuthoritative 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. +
+ + +
+ +