From c7f7beecfb3aef298cd50e914aef351a43525da8 Mon Sep 17 00:00:00 2001 From: Chris Phillipson Date: Tue, 25 Aug 2026 20:02:36 -0700 Subject: [PATCH 1/3] fix: harden dual-host qe-court transport --- MAINTAINER.md | 7 +- claude/dual-mode-reference.md | 29 ++-- claude/providers-reference.md | 14 +- docs/HOST-SUPPORT.md | 9 +- docs/PROVIDERS.md | 31 +++- docs/SETUP.md | 14 +- docs/UPGRADING.md | 16 +- ...001-one-routing-policy-many-projections.md | 4 +- ...-capability-driven-integration-adapters.md | 4 + .../0018-generalized-host-worker-execution.md | 6 +- docs/adr/0026-about-component-directory.md | 4 +- ...dex-mcp-and-bound-qe-court-participants.md | 91 +++++++++++ docs/adr/README.md | 5 +- docs/ddd/component-directory.md | 2 +- docs/ddd/routing-and-orchestration.md | 9 +- docs/ddd/ubiquitous-language.md | 2 +- package.json | 3 + src/commands/setup.mjs | 19 ++- src/commands/status.mjs | 60 ++++++-- src/commands/sync.mjs | 8 +- src/commands/x/host.mjs | 46 +++--- src/lib/adapters/registries.mjs | 1 - src/lib/blocks.mjs | 2 +- src/lib/dashboard/about-directory.mjs | 6 +- src/lib/execution/process-tree.mjs | 16 +- src/lib/execution/subprocess.mjs | 9 +- src/lib/hosts.mjs | 18 +-- src/lib/mcp.mjs | 71 ++++++++- src/lib/nudge.mjs | 5 +- src/lib/opencode.mjs | 2 +- src/lib/providers.mjs | 80 ++++++---- src/lib/qeCourt.mjs | 51 +++++++ src/lib/routing.mjs | 2 +- tests/kit/codex-mcp.test.mjs | 53 ++++++- tests/kit/host-cli-migration.test.mjs | 2 +- tests/kit/hosts.test.mjs | 7 +- tests/kit/process-tree.test.mjs | 50 +++++++ tests/kit/provider-cli.test.mjs | 10 +- tests/kit/qeCourt.test.mjs | 55 ++++++- tests/kit/routing-projection.test.mjs | 77 +++++++++- tests/kit/setup-command.test.mjs | 4 +- tests/kit/status-command.test.mjs | 36 ++++- tests/kit/subprocess-execution.test.mjs | 2 + .../qe-court-participant-transport.test.mjs | 141 ++++++++++++++++++ 44 files changed, 892 insertions(+), 191 deletions(-) create mode 100644 docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md create mode 100644 tests/live/qe-court-participant-transport.test.mjs diff --git a/MAINTAINER.md b/MAINTAINER.md index 37decc3..f7db82d 100644 --- a/MAINTAINER.md +++ b/MAINTAINER.md @@ -95,7 +95,9 @@ docs/ `docs/INSTALLATION.md`, `docs/MODELS.md`, `docs/PROVIDERS.md`, `docs/SETUP.md`, `docs/TROUBLESHOOTING.md`, `docs/UPGRADING.md`, `docs/CODEX-STATUSLINE.md`, `docs/adr/0015-managed-codex-native-statusline.md`, -`docs/adr/0032-model-lifecycle-intelligence.md`, and +`docs/adr/0032-model-lifecycle-intelligence.md`, +`docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md`, +`tests/live/qe-court-participant-transport.test.mjs`, and `docs/ddd/model-lifecycle-intelligence.md`. Generated workspace state under the shipped source trees is explicitly excluded. Nothing else ships — verify with `npm pack --dry-run` before a release if you touch `files`. @@ -105,7 +107,8 @@ split between `integrations.hosts` (which hosts are enabled) and top-level `rout (`version`, `primaryHost`, and the per-activity `routes`). `routing.mjs` is pure (defaults, primary-host mirroring, validation, and projections to AQE `agentOverrides` and `ak run`); `providers.mjs` does the I/O (host/auth detection, -environment wiring, both MCP-bridge directions, AQE router file). Seeded/healed by +environment wiring, Codex's independent Ruflo integration, legacy MCP retirement, and the AQE +router file). Seeded/healed by `setup` + `sync` + `x host pick`, surfaced by `status` + `dashboard`. Design records: ADRs [0001–0006](docs/adr/); user guide: `docs/PROVIDERS.md`. diff --git a/claude/dual-mode-reference.md b/claude/dual-mode-reference.md index e05d12f..e91b6a4 100644 --- a/claude/dual-mode-reference.md +++ b/claude/dual-mode-reference.md @@ -9,10 +9,9 @@ ## Ambidextrous dual-host mode (claude + codex) -Both frontier CLIs are enabled, so `ak` runs **ambidextrous**: the same tools, memory, -and quality gates are available whichever agent is in the driver's seat, and each host can -reach the other. Work flows complementarily — Claude and Codex are peers, not primary and -fallback. +Both frontier CLIs are enabled, so `ak` runs **ambidextrous**: the same Ruflo/AQE tools, +memory, and quality gates are available whichever agent is in the driver's seat. Work flows +complementarily — Claude and Codex are peers, not primary and fallback. ### `ak run` — canonical activity pipelines @@ -37,17 +36,21 @@ ak run refactor "extract the payment module" --dry-run - **`--json`** — emit machine-readable output while executing; combine it with `--dry-run` for a machine-readable preview. -### The Claude ↔ Codex bridge (bidirectional MCP) +### Cross-host integration -The two hosts see each other as MCP servers, so either can delegate to the other mid-task: +Managed cross-host work uses one bounded path: -- **Claude → Codex** — Codex is exposed as an MCP server (`codex mcp-server`); Claude - reaches it through the **`mcp__codex__codex`** tool to hand a subtask to Codex. -- **Codex → ruflo** — Codex registers ruflo's MCP via `[mcp_servers.ruflo]` in +- **Claude-led or Codex-led execution** — use `ak run`; every worker has one absolute + deadline and process-tree cleanup. +- **Codex → Ruflo/AQE tools** — Codex registers Ruflo's MCP via `[mcp_servers.ruflo]` in `~/.codex/config.toml`, so Codex-driven sessions get the same memory, routing, and swarm - tools Claude has. + tools Claude has. Agentic-QE owns its separate Codex MCP/platform integration. +- **Optional Claude → Codex interactive delegation** — OpenAI's Claude Code plugin uses + Codex App Server and remains user-owned. Agentic-kit never silently installs it. -Register (or repair) both directions with `ak sync`; inspect with `ak status`. +OpenAI deprecated `codex mcp-server` on 2026-08-24. `ak sync` removes only the legacy +project registration that agentic-kit owns; user-owned entries are preserved and warned. +Inspect the effective topology with `ak status`. ### Per-activity routing + escalation ladders @@ -68,6 +71,8 @@ ambidextrous experience with the roles flipped. `ak status` marks the primary an Two enabled hosts do not by themselves prove two inference vendors. qe-court diversity must be grounded in the providers that actually serve the selected roles. Prefer independently evidenced providers for review/security activities and treat configured host/model routes as intent, not -vendor proof. +vendor proof. `primaryHost` mirrors `ak run` activity routes; it does not reverse QE-Court seats. +Until Agentic-QE ships a host-neutral court runner and complete Codex projection, the reciprocal +live check is a **participant-transport regression**, not a court verdict. diff --git a/claude/providers-reference.md b/claude/providers-reference.md index 21d7bfc..4eacb8d 100644 --- a/claude/providers-reference.md +++ b/claude/providers-reference.md @@ -62,17 +62,17 @@ ak host off # reset to claude-only default; strip managed env keys `pick` persists your choice to `kit.json` and applies it: it writes the ruflo backend flags (`ENABLE_CLAUDE_CODE` / `ENABLE_CODEX`) and `AQE_LLM_PROVIDER` into -`.claude/settings.local.json` `env` (merge-not-clobber, backup-first), maintains the -Claude↔Codex bridge when both hosts are enabled, and registers any API-key providers with ruflo. +`.claude/settings.local.json` `env` (merge-not-clobber, backup-first), maintains Codex's +independent Ruflo integration, and registers any API-key providers with ruflo. `ak sync` reapplies the same choice idempotently; `ak status` shows **hosts** and **providers** rows and flags drift. At the claude-only default nothing is written — behavior is unchanged until you opt in. -When **both** hosts are enabled, `ak` also seeds a **per-activity routing policy** and wires a -**two-way Claude↔Codex MCP bridge** (Claude reaches Codex via `mcp__codex__codex`; Codex reaches -ruflo via `[mcp_servers.ruflo]`). `--primary-host claude|codex` chooses which host leads. See the -ambidextrous dual-host reference block and `docs/PROVIDERS.md` §3.5 for the routing table and -canonical `ak run` execution. +When **both** hosts are enabled, `ak` also seeds a **per-activity routing policy** and gives Codex +Ruflo access via `[mcp_servers.ruflo]`. `--primary-host claude|codex` chooses which host leads +bounded `ak run` pipelines. OpenAI deprecated the former Claude→`codex mcp-server` projection; +its Claude Code plugin is an optional user-owned interactive path. See the ambidextrous dual-host +reference block and `docs/PROVIDERS.md` §3.5. ### Install & update (install-method-aware) diff --git a/docs/HOST-SUPPORT.md b/docs/HOST-SUPPORT.md index 0beff94..00bc601 100644 --- a/docs/HOST-SUPPORT.md +++ b/docs/HOST-SUPPORT.md @@ -53,7 +53,7 @@ served it. See [Providers and hosts](PROVIDERS.md) for those axes in detail. | agentic-kit standing | Default-enabled; primary-eligible | Opt-in; primary-eligible | Opt-in; explicitly non-primary | | `ak run` execution | Native CLI adapter | Native CLI adapter | Managed supervised-server adapter | | Automatic activity routes | Yes | Yes | No; explicit routes only | -| Ruflo support | Reference/native surface | Strong, with bridge and parity gaps | Managed compatibility layer | +| Ruflo support | Reference/native surface | Strong, with integration and parity gaps | Managed compatibility layer | | AQE support | Default and fullest path | Strong platform path; one direct-provider gap in `ak` | Upstream platform assets, not an AQE inference provider | | RuvNet Brain | Native plugin, hooks, MCP, console | Native plugin, hooks, MCP, skills | Managed search MCP and guidance; no native Brain plugin | | Managed command status line | Yes | No; Codex's native built-in fields only | No | @@ -106,14 +106,15 @@ Official extension references: [Claude hooks](https://code.claude.com/docs/en/ho | Ruflo capability | Claude Code | Codex | OpenCode | | --- | --- | --- | --- | -| Upstream host orientation | **Native:** primary/reference CLI surface | **Native + managed:** upstream backend/plugin pieces plus agentic-kit bridge | **Managed:** no equivalent upstream backend flag | +| Upstream host orientation | **Native:** primary/reference CLI surface | **Native + managed:** upstream backend/plugin pieces plus agentic-kit integration | **Managed:** no equivalent upstream backend flag | | Ruflo MCP tools | Native registration | Managed Ruflo MCP registration | Connected managed MCP; compact lazy `ak_ruflo_*` provider projection | | Shared Ruflo memory | Same project store | Same project store | Same project store when pointed at the same Ruflo server | | Agents and skills | Upstream Claude assets | Codex-compatible skills/plugin assets and generated guidance | Receipt-owned lazy profile catalogue through one stock `ak-specialist`; stock skills loaded on demand | | Lifecycle hooks | Native Claude hooks | Codex hooks/plugin surfaces | OpenCode events translated by `ruflo-hooks.js` | | Inference-backend flag | `ENABLE_CLAUDE_CODE` | `ENABLE_CODEX` | None | -| Cross-host bridge | Claude can call the Codex MCP server | Codex can call Ruflo MCP | No equivalent peer bridge | -| Upgrade convergence | `ak sync` heals managed assets | `ak sync` heals bridge/guidance | `ak sync` regenerates the embedded catalogue and repairs exact-receipted plugins/config | +| Cross-host execution | `ak run` can lead bounded Codex workers | `ak run` can lead bounded Claude workers | Explicit `ak run` routes only | +| Interactive peer path | Optional user-owned OpenAI Codex plugin/App Server | No supported inverse Claude plugin | None | +| Upgrade convergence | `ak sync` heals managed assets | `ak sync` heals Ruflo/AQE access and retires owned legacy MCP | `ak sync` regenerates the embedded catalogue and repairs exact-receipted plugins/config | | Teardown | Managed blocks and registrations | Receipt-based managed teardown | Value- and hash-receipt teardown; user-owned values survive | Ruflo MCP access and Ruflo-backed inference are different contracts. In diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index f82bd30..efd4bd6 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -266,7 +266,7 @@ An interactive picker (or flags for scripts). Enable `codex` and `ak`: - installs it if it's missing (`npm i -g @openai/codex`) — but leaves an existing mise/brew/native install alone, -- maintains the Claude↔Codex bridge and generated host guidance, +- maintains shared Ruflo/AQE access and generated host guidance, - writes `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` into `.claude/settings.local.json`. > [!NOTE] @@ -383,13 +383,28 @@ is aborted and reported as `permission_required`; `ak` never auto-approves it. O not written to AQE `agentOverrides`, cannot become `primaryHost`, and do not count as a separate AQE vendor. `ak run` is the only execution surface for an OpenCode route. -**QE-Court validation stays upstream-owned.** `agentic-qe` 3.13.3 corrected its shipped -QE-Court panel and now enforces the configured anti-collusion policy before convening. -`ak status` and `ak host status` surface that result read-only; `ak sync` never rewrites +**QE-Court validation stays upstream-owned.** Agentic-kit's local check proves only configured +vendor diversity and writer/jury separation; it does not prove that provider seats or the court +runtime are executable. `ak status` and `ak host status` state that boundary explicitly, and +`ak sync` never rewrites `.claude/skills/qe-court/config.json`. If a config created by 3.13.2 or earlier still seats both `defense` and `jury` on Cognitum tiers, regenerate it with 3.13.3+ or change `defense` to `claude-code` so the jury and defense use distinct vendors. +`primaryHost` controls the mirrored `ak run` activity policy, not QE-Court roles. The source-tree +live regression exercises bounded participant transport from both directions without claiming a +court verdict: + +```bash +pnpm test:qe-court-live # one Claude-led + one Codex-led trial +AK_QE_COURT_TRIALS=5 pnpm test:qe-court-live # POSIX soak +``` + +Each seat retrieves a private Ruflo-memory proof, emits a validated bounded handoff, and must +terminate within its absolute deadline. The check fails on repository mutation or orphaned state. +Full court parity remains blocked until Agentic-QE ships a supported host-neutral runner and a +self-contained Codex QE-Court projection. + Defaults (all overridable; your edits are marked `custom` and never re-seeded): | Activity | Host | Default model | @@ -447,9 +462,9 @@ weigh, cleared with `ak x host refresh` if you want the newer default. set as authoritative. Excluding a routing host removes it from persisted routes and escalation ladders before AQE is reprojected; seeded entries are removed silently, while a user-pinned route prints a warning naming the disabled host. It also removes stale agentic-kit-curated AQE overrides -while preserving foreign override keys. Excluding Codex additionally retires only the two -marker-owned Codex MCP bridges; user-registered MCP servers are left alone. Re-enabling Codex -converges those bridges again. +while preserving foreign override keys. Excluding Codex retires only marker-owned integrations; +user-registered MCP servers are left alone. The deprecated Claude→Codex MCP projection is retired +even while Codex remains enabled, while the independent Ruflo-in-Codex registration converges. `routing.routes` intentionally names a host and model, not an inference provider. Provider resolution is a separate binding lookup; absent grounded evidence remains unknown or explicitly inferred. @@ -467,7 +482,7 @@ The native config stores each knob below lives in — and their precedence — a | You want to… | `ak` way | The raw ruflo/aqe way it maps to | | ------------------------------------ | --------------------------------- | --------------------------------------------------- | -| Enable claude/codex hosts | `ak host pick` | `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` env + managed bridge/guidance | +| Enable claude/codex hosts | `ak host pick` | `ENABLE_CLAUDE_CODE` / `ENABLE_CODEX` env + shared Ruflo/AQE access and guidance | | Register a Ruflo LLM provider | `--provider ollama:qwen3.6:27b` | `ruflo providers configure -p ollama -m qwen3.6:27b -e http://127.0.0.1:11434` | | Select a direct Ruflo provider | per-agent/raw setting | agent `--provider` or `RUFLO_PROVIDER=ollama` / `openrouter` | | Set which LLM runs QE | `--aqe-provider gemini` | `AQE_LLM_PROVIDER=gemini` (env) | diff --git a/docs/SETUP.md b/docs/SETUP.md index a8d50af..02e58e5 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -74,8 +74,9 @@ versions. The table below describes the current contract. | Project `AGENTS.md` | Arbitrary project content is preserved. Agentic-kit only reconciles its own sentinel-delimited managed blocks, including removing stale blocks that no longer apply. | Created only when a selected integration needs managed guidance. | Project setup also reapplies enabled host/provider wiring and seeds the default -per-activity routing policy in dual-host mode. With Codex enabled, it repairs -both directions of the Claude/Codex–Ruflo bridge. +per-activity routing policy in dual-host mode. With Codex enabled, it registers +the workspace-aware Ruflo MCP in Codex and retires only agentic-kit-owned legacy +`codex mcp-server` project entries. ## Setup trust manifest @@ -117,11 +118,14 @@ is removed and setup fails instead of silently expanding project trust. ### Codex and OpenCode -Codex does not need an agentic-kit auto-approve list. The manifest instead names -the project Claude-to-Codex MCP bridge, the user-scope Codex-to-Ruflo MCP -registration, and the AQE Codex integration that project setup will create. +Codex does not need an agentic-kit auto-approve list. The manifest names the +user-scope Codex-to-Ruflo MCP registration and the AQE Codex integration that +project setup will create. Agentic-kit does not alter Codex's sandbox or approval policy. +OpenAI's Claude Code plugin for Codex is an optional App Server-based interactive +path. It is user-owned: setup and sync never install, enable, update, or remove it. + Codex also retains exclusive ownership of third-party plugins. Agentic-kit never installs or enables a Codex plugin (including `security-guidance`), and setup/sync never rewrites Codex's plugin tables or cache. `ak status` only reads enabled diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 2f70aab..0766dff 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -83,7 +83,7 @@ open in other terminals, here is what can actually reach them, worst first: What does **not** break, by design: running binaries keep executing their old code (replaced files don't affect a running process's open inodes). Agentic-kit's managed settings and guidance writers are atomic and fail closed when the one-time backup cannot -be created or validated. Settings env keys, `~/.codex/config.toml` edits (MCP bridge, +be created or validated. Settings env keys, `~/.codex/config.toml` edits (Ruflo/AQE MCP, `[tui]` status line), OpenCode wiring, `.agentic-qe/llm-config.json`, and the managed guidance blocks are all **read at session start** — a live session simply doesn't see them until its next launch. The kit's own self-update runs last and applies from the next @@ -158,6 +158,11 @@ an agentic-qe or `ak` upgrade. If `ak status` reports `writerIsNeverJuror`, rege config with agentic-qe 3.13.3+ or change `routing.defense.provider` from `cognitum-low` to `claude-code`. `ak` reports this state read-only; `ak sync` no longer changes QE-Court roles. +The local anti-collusion check is not a runtime-readiness proof. Current consumer projections can +reference source-only referee/oracle assets, and `primaryHost` does not reverse court seats. Use +`pnpm test:qe-court-live` in a source checkout for one bounded Claude-led and one bounded +Codex-led **participant-transport** trial; do not record it as a court verdict. + If you already have `ak` working, you almost never need `ak setup` again — it's the installer. Enabling a shipped-but-opt-in host feature is a `host pick` (or an `x mcp pick`, etc.), not a re-`setup`. Project setup calls `ruflo init --full --force`, so review the @@ -184,7 +189,8 @@ ak host status # 3. verify: hosts "enabled, wired" + routing tab Step 1 gets the newer code onto disk. Step 2 is what actually turns dual-host on — it records `codex` in `kit.json` and does the wiring: writes `ENABLE_CODEX` into `.claude/settings.local.json`, seeds the per-activity routing policy, registers the -Codex↔ruflo MCP bridge both ways, and generates the dual-host guidance. +workspace-aware Ruflo MCP in Codex, retires any agentic-kit-owned legacy `codex mcp-server` +project entry, and generates the dual-host guidance. Add `--primary-host codex` if you want Codex to lead (Claude becomes the alternate). > [!NOTE] @@ -208,12 +214,12 @@ Every `ak` command ends with a best-effort, never-blocking drift nudge. It has t - **Version drift** (npm-managed tools; TTL-cached network check): `↑ ruflo 4.1.0 available (installed 4.0.0) — run: ak sync` - **Local artifact drift** (spawn-light file compares, evaluated on every run): - `↻ drifted: 2 CLAUDE.md block(s) · codex MCP unregistered — run: ak sync` + `↻ drifted: 2 CLAUDE.md block(s) · deprecated codex MCP registered — run: ak sync` The second half covers the artifacts `ak` *renders*: managed guidance blocks in the machine-wide guidance files (`~/.claude/CLAUDE.md`, and `~/.codex/AGENTS.md` on codex -machines), the Claude↔Codex MCP bridge (both -directions), and the statusline footer. These can drift with **no version change at all** — +machines), Codex's independent Ruflo/AQE access, legacy MCP retirement, and the statusline +footer. These can drift with **no version change at all** — a kit update (or, on an npm-linked dev checkout, merely merging a PR that edits a `claude/*.md` template) revises the source of truth, and the rendered copies lag until the next `ak sync`. The nudge closes that window, using the exact drift definitions `ak status` uses (the two diff --git a/docs/adr/0001-one-routing-policy-many-projections.md b/docs/adr/0001-one-routing-policy-many-projections.md index cebb0ca..da2ab2b 100644 --- a/docs/adr/0001-one-routing-policy-many-projections.md +++ b/docs/adr/0001-one-routing-policy-many-projections.md @@ -2,13 +2,15 @@ - **Status:** Superseded in part by [ADR-0020](0020-ga-stable-surfaces.md) - **Date:** 2026-07-23 -- **Updated:** 2026-07-30 +- **Updated:** 2026-08-25 - **Update note:** Retained the one-policy decision while retiring the compatibility projection, command, and persisted names for GA. - **Deciders:** agentic-kit maintainers > **Historical scope:** the one-policy principle remains, but the compatibility command, > adapter projection, and persisted field names recorded below were removed for 4.0 GA. +> OpenAI deprecated the recorded `codex mcp-server` projection on 2026-08-24; +> [ADR-0033](0033-retire-codex-mcp-and-bound-qe-court-participants.md) retires it. ## Context diff --git a/docs/adr/0016-capability-driven-integration-adapters.md b/docs/adr/0016-capability-driven-integration-adapters.md index bb42ed8..9600a70 100644 --- a/docs/adr/0016-capability-driven-integration-adapters.md +++ b/docs/adr/0016-capability-driven-integration-adapters.md @@ -34,6 +34,10 @@ project-memory pin is now projected through host-specific launch context: Claude project env, Codex's workspace-aware MCP launcher, and OpenCode's project-aware MCP/lifecycle processes. + 2026-08-25: [ADR-0033](0033-retire-codex-mcp-and-bound-qe-court-participants.md) + retires the deprecated Claude→Codex MCP projection while retaining this ADR's + value-precise ownership boundary. OpenAI's Claude Code plugin remains external and + user-owned; Ruflo and Agentic-QE registrations are independent Codex integrations. - **Deciders:** agentic-kit maintainers - **Related:** [ADR-0001](0001-one-routing-policy-many-projections.md), [ADR-0003](0003-auto-seed-dual-host-provenance.md), diff --git a/docs/adr/0018-generalized-host-worker-execution.md b/docs/adr/0018-generalized-host-worker-execution.md index 115c607..f23c691 100644 --- a/docs/adr/0018-generalized-host-worker-execution.md +++ b/docs/adr/0018-generalized-host-worker-execution.md @@ -1,11 +1,13 @@ # ADR-0018 — Generalized host-worker execution; `ak run` canonical -- **Status:** Accepted; compatibility retention superseded by +- **Status:** Implemented; compatibility retention superseded by [ADR-0020](0020-ga-stable-surfaces.md) - **Date:** 2026-07-29 -- **Updated:** 2026-07-30 +- **Updated:** 2026-08-25 - **Update note:** Added private bounded dependency handoffs and one absolute lifecycle deadline per attempt; removed the temporary command, adapter, and persisted-schema compatibility clauses. + ADR-0033 extends cleanup to POSIX process groups so host-spawned MCP descendants share the + worker's bounded termination contract. - **Deciders:** agentic-kit maintainers > **GA amendment:** the host-neutral lifecycle, `ak run`, and OpenCode safety decisions remain. diff --git a/docs/adr/0026-about-component-directory.md b/docs/adr/0026-about-component-directory.md index 6a97bd6..6e441c9 100644 --- a/docs/adr/0026-about-component-directory.md +++ b/docs/adr/0026-about-component-directory.md @@ -59,7 +59,7 @@ About (no secondary rail — one scrolling page with category anchors): Knowledge RuvNet Brain — the offline KB that grounds answers about this stack. The kit agentic-kit itself — the caretaker that installs, heals, and explains the rest. Configured for you Non-package surfaces: MCP registrations, guidance blocks, - statuslines, dual-host routing & bridge, background daemon, permission + statuslines, dual-host routing & shared tools, background daemon, permission allowlists — each with "configured by setup/sync · yours to change". ``` @@ -159,7 +159,7 @@ The draft left three points open. All three are decided; this section is the rec usage scorecard and `ak system` already follow. It also answers the question in the place the question is usually asked — right after `ak setup` finishes printing. 3. **Six separate "Configured for you" cards, not one card with a list.** Each configured - surface — MCP registrations, guidance blocks, statuslines, dual-host routing and bridge, the + surface — MCP registrations, guidance blocks, statuslines, dual-host routing and shared tools, the background daemon, permission allowlists — has its own state chip and its own managing command. Collapsing them into one card would force a single chip to summarize six independent health facts, which is exactly the kind of averaging the honest-degradation contract forbids, diff --git a/docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md b/docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md new file mode 100644 index 0000000..3826d94 --- /dev/null +++ b/docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md @@ -0,0 +1,91 @@ +# ADR-0033 — Retire Codex MCP and bound QE-Court participant transport + +- **Status:** Implemented +- **Date:** 2026-08-25 +- **Updated:** 2026-08-25 +- **Update note:** Initial implementation retires only receipt-owned legacy MCP state, + diagnoses effective Codex MCP topology, extends POSIX cleanup to process groups, and adds + fail-closed QE-Court readiness plus a reciprocal live participant-transport regression. +- **Deciders:** agentic-kit maintainers +- **Related:** [ADR-0001](0001-one-routing-policy-many-projections.md), + [ADR-0006](0006-primary-host-and-ambidextrous-mirroring.md), + [ADR-0016](0016-capability-driven-integration-adapters.md), + [ADR-0018](0018-generalized-host-worker-execution.md), + [ADR-0020](0020-ga-stable-surfaces.md), + [issue #108](https://github.com/pacphi/agentic-kit/issues/108) + +## Context + +Agentic-kit previously registered `codex mcp-server` as a project-scoped Claude Code MCP +server and described it together with Codex's Ruflo MCP registration as a bidirectional bridge. +That model was inaccurate: one leg exposed Codex as a nested host while the other exposed Ruflo +tools to Codex. It also put another long-lived transport inside a multi-participant workflow. + +OpenAI deprecated `codex mcp-server` on 2026-08-24. OpenAI recommends App Server for custom +integrations and its Claude Code plugin for interactive Claude→Codex work. The plugin is a +user-scoped external integration and does not provide the inverse Codex→Claude path. Agentic-kit's +existing `ak run` supervisor already provides host-neutral routing, one absolute deadline per +attempt, bounded cancellation, and normalized terminal evidence. + +The QE-Court investigation exposed a separate proof gap. The current Agentic-QE consumer skill +can pass local role/vendor checks while its generated package references schema, referee, and +oracle assets that are absent from the consumer. Agentic-QE does not currently expose a supported +host-neutral court runner, and `routing.primaryHost` mirrors activity routes rather than court +seats. A successful Claude/Codex transport check therefore cannot be called a court verdict. + +## Decision + +1. `ak run` remains the sole agentic-kit-managed cross-host executor. Claude-primary and + Codex-primary policies select leadership without changing the transport contract. +2. Setup, sync, and host selection stop creating `codex mcp-server`. They remove a legacy project + registration only when `integrations.ownership.codex.mcp === "ak"`, confirm its absence, and + clear the receipt only after confirmation. User-owned entries are preserved with a deprecation + warning and an exact manual remedy. +3. Codex keeps one independent, workspace-aware Ruflo MCP registration. Agentic-QE continues to + own its Codex platform/MCP integration. Agentic-kit detects recursive Codex self-registration, + missing concrete Agentic-QE registration, and duplicate Ruflo transports without rewriting + unowned Codex TOML. +4. OpenAI's Claude Code plugin is optional and user-owned. Agentic-kit may document and detect it, + but does not silently install, enable, update, or remove it. A future managed App Server/plugin + adapter requires a separate lifecycle, ownership, cancellation, and teardown decision. +5. POSIX host workers run as process-group leaders. Timeout, cancellation, and cleanup signal the + whole group (TERM then bounded KILL), matching Windows whole-tree cleanup and preventing a host's + MCP descendants from surviving the direct worker. +6. Status distinguishes three levels of QE-Court evidence: + + - local anti-collusion routing validation; + - self-contained consumer artifacts and concrete provider-seat readiness; + - a completed upstream court verdict. + + A lower level never claims a higher one. +7. The opt-in reciprocal live harness is named a **QE-Court participant-transport regression**. + It runs bounded Claude-led and Codex-led direct-host DAGs, requires private Ruflo-memory proofs + in validated handoffs, rejects repository mutation and orphaned state, and never substitutes for + Agentic-QE's court protocol. + +## Consequences + +- Existing agentic-kit-owned legacy MCP state converges safely on the next setup, sync, or host + selection. Foreign state remains visible and untouched. +- Claude→Codex interactive delegation is no longer implied by enabling both hosts. Users who want + it can install OpenAI's plugin explicitly; production automation should use `ak run`. +- Codex startup no longer needs to load a recursive `codex → codex mcp-server` topology. +- A transport regression can prove bounded participant lifecycle in both leadership directions, + but full QE-Court parity remains blocked on an upstream Agentic-QE execution surface and complete + Codex projection. +- ADR-0001's MCP projection is historical. ADR-0006's leadership decision, ADR-0016's ownership + boundary, ADR-0018's worker lifecycle, and ADR-0020's stable execution surface remain in force. + +## Verification + +- Unit tests cover receipt-owned retirement, preservation of user-owned state, concrete Codex MCP + topology, fail-closed court artifact readiness, POSIX descendant cleanup, and orphan reporting. +- `pnpm test:qe-court-live` runs one Claude-led and one Codex-led participant-transport trial. + `AK_QE_COURT_TRIALS=5` raises this to a reciprocal soak test on POSIX shells. +- `ak status` fails recursive self-MCP and reports missing Agentic-QE or duplicate Ruflo MCP state. + +## References + +- [OpenAI Codex changelog](https://developers.openai.com/codex/changelog) +- [OpenAI Codex App Server](https://developers.openai.com/codex/app-server) +- [OpenAI Codex Claude Code plugin](https://github.com/openai/codex-plugin-cc) diff --git a/docs/adr/README.md b/docs/adr/README.md index 86d5c5c..33f66e4 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -16,7 +16,7 @@ Consequences**, and cites the grounded source it rests on where relevant. | [0005](0005-dashboard-in-page-routing-reveal.md) | Dashboard surfaces routing via in-page reveal | Implemented | | [0006](0006-primary-host-and-ambidextrous-mirroring.md) | Primary host & ambidextrous mirroring (which host leads) | Amended by 0020 | | [0007](0007-maintainer-admin-local-telemetry.md) | Maintainer admin: a loopback telemetry page with deliberate egress | Accepted | -| [0008](0008-guidance-target-scope-split.md) | Machine-scoped guidance blocks land in machine files, not a repo's AGENTS.md | Accepted | +| [0008](0008-guidance-target-scope-split.md) | Machine-scoped guidance blocks land in machine files, not a repo's AGENTS.md | Implemented | | [0009](0009-usage-scorecard-local-transcript-analytics.md) | Usage scorecard: local transcript analytics with graded evidence | Implemented | | [0010](0010-provider-mediated-quota-reads.md) | Provider-mediated quota reads (the only honest denominators) | Accepted | | [0011](0011-local-model-provenance-zero-cost-and-transcript-fidelity.md) | Local models: provenance out-of-band, $0 per model, stated transcript fidelity | Proposed | @@ -26,7 +26,7 @@ Consequences**, and cites the grounded source it rests on where relevant. | [0015](0015-managed-codex-native-statusline.md) | Manage Codex's native user-wide status line without claiming rich-renderer parity | Accepted | | [0016](0016-capability-driven-integration-adapters.md) | Capability-driven host, provider, binding, projection, and observability adapters | Accepted; compatibility amended; closed-registry clause superseded by 0029 | | [0017](0017-opencode-host.md) | OpenCode as a managed, observable host through native surfaces | Accepted; compatibility amended | -| [0018](0018-generalized-host-worker-execution.md) | Generalized host-worker execution; `ak run` canonical | Accepted; compatibility amended | +| [0018](0018-generalized-host-worker-execution.md) | Generalized host-worker execution; `ak run` canonical | Implemented; compatibility amended | | [0019](0019-escalation-in-ak-run.md) | Bounded per-worker escalation in `ak run` | Accepted; historical context closed | | [0020](0020-ga-stable-surfaces.md) | One stable GA surface per capability | Implemented | | [0021](0021-inference-provider-provenance.md) | Inference-provider provenance for live sessions | Accepted | @@ -40,6 +40,7 @@ Consequences**, and cites the grounded source it rests on where relevant. | [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) | | [0032](0032-model-lifecycle-intelligence.md) | Model lifecycle intelligence from provenance-aware local evidence | Implemented | +| [0033](0033-retire-codex-mcp-and-bound-qe-court-participants.md) | Retire Codex MCP; bound reciprocal QE-Court participant transport | Implemented | 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 diff --git a/docs/ddd/component-directory.md b/docs/ddd/component-directory.md index 200f565..70f06d1 100644 --- a/docs/ddd/component-directory.md +++ b/docs/ddd/component-directory.md @@ -118,7 +118,7 @@ under the dashboard's CSP. ### Configured surfaces Non-package things ak sets up — MCP registrations, managed guidance blocks -(CLAUDE.md/AGENTS.md), statuslines, the dual-host routing policy and Claude↔Codex bridge, the +(CLAUDE.md/AGENTS.md), statuslines, the dual-host routing policy and shared Ruflo/AQE access, the background daemon, permission allowlists — are directory entries too, in the `configured` category. Their chip reads `configured` (joined from the relevant status subsystem row), their paragraph explains what was configured and why it helps, and each names the command that diff --git a/docs/ddd/routing-and-orchestration.md b/docs/ddd/routing-and-orchestration.md index 1e178db..5632faf 100644 --- a/docs/ddd/routing-and-orchestration.md +++ b/docs/ddd/routing-and-orchestration.md @@ -20,7 +20,7 @@ RoutingPolicy provenance escalation rungs[] -RoutingPolicy -> eligible configuration projections -> AQE / MCP surfaces +RoutingPolicy -> eligible configuration projections -> AQE / host guidance RoutingPolicy + template + task -> materialized workers -> ak run ``` @@ -84,12 +84,15 @@ change the persisted base route. ladder. Pure projectors materialize that policy into: - AQE `agentOverrides` for AQE-eligible Claude/Codex routes; -- `ak run`'s host-neutral worker plan; -- Codex MCP availability/configuration. +- `ak run`'s host-neutral worker plan. These are configuration projections, not independent routing policies. Native surfaces must not be edited back into domain truth without an explicit import or reconciliation design. +Codex's Ruflo/AQE MCP registrations are independent host integrations, not routing projections. +OpenAI deprecated the former Claude→Codex `codex mcp-server` projection; bounded cross-host work +uses `ak run`. + ## Canonical execution `ak run` is the canonical executor for materialized activity plans. It can select every host whose diff --git a/docs/ddd/ubiquitous-language.md b/docs/ddd/ubiquitous-language.md index e01b236..72993c8 100644 --- a/docs/ddd/ubiquitous-language.md +++ b/docs/ddd/ubiquitous-language.md @@ -160,7 +160,7 @@ transcript and never substitute for each other. See [Machine footprint](machine- | Monogram tile | The honest icon for a component with no official mark: initials on a category-hued tile | | Register contract | The editorial writing rules (one ~50-word paragraph, plain language, active voice, no runtime claims, no superlatives) | | Parity gate | The test asserting managed-tools registry ↔ directory completeness in both directions | -| Configured surface | A non-package thing ak sets up — MCP registrations, guidance blocks, statuslines, routing and bridge, the daemon, permission allowlists — carrying a managing command instead of package links | +| Configured surface | A non-package thing ak sets up — MCP registrations, guidance blocks, statuslines, routing and shared tool access, the daemon, permission allowlists — carrying a managing command instead of package links | Editorial content states purpose and reads true on a machine where the component is absent; runtime state is a chip word, never a prose word. See diff --git a/package.json b/package.json index b15ee7a..061edf2 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,15 @@ "docs/CODEX-STATUSLINE.md", "docs/adr/0015-managed-codex-native-statusline.md", "docs/adr/0032-model-lifecycle-intelligence.md", + "docs/adr/0033-retire-codex-mcp-and-bound-qe-court-participants.md", + "tests/live/qe-court-participant-transport.test.mjs", "docs/ddd/model-lifecycle-intelligence.md" ], "scripts": { "test": "node --test --experimental-test-coverage --test-coverage-lines=70 --test-coverage-branches=70 --test-coverage-functions=70 \"tests/kit/*.test.mjs\" && node tests/statusline-segments.test.cjs && node tests/statusline-brain.test.cjs && node tests/agentdb.test.cjs && node tests/health-history.test.cjs && node tests/harvest.test.cjs && node tests/dashboard.test.cjs && node tests/admin-model.test.cjs && node tests/admin.test.cjs", "test:ui": "node tests/ui/dashboard-ui.mjs", "test:surface": "node --test tests/kit/dispatch-surface.test.mjs", + "test:qe-court-live": "node --test tests/live/qe-court-participant-transport.test.mjs", "typecheck": "tsc -p tsconfig.json", "lint": "eslint .", "lint:fix": "eslint . --fix", diff --git a/src/commands/setup.mjs b/src/commands/setup.mjs index 8409bb5..7b06704 100644 --- a/src/commands/setup.mjs +++ b/src/commands/setup.mjs @@ -17,7 +17,7 @@ import { runLifecycle } from '../lib/adapters/lifecycle.mjs'; import { hostsWithLifecycle, lifecycleAdapterFor, lifecycleExecutionEnabled, detectionBinFor } from '../lib/adapters/lifecycle-registry.mjs'; import { renderApplyReport } from '../lib/adapters/lifecycle-render.mjs'; import { loadKitConfig, saveKitConfig } from '../lib/config.mjs'; -import { HOSTS, applyHosts, applyProviders, hostInstallState, installHost, applyAqeRouter, seedActivityRoutesIfMultiHost, printActivityRoutingTable, aqeSupportsAgentOverrides, ensureCodexMcp, ensureRufloMcpInCodex, applySetupHostFlags, bothHostsEnabled } from '../lib/providers.mjs'; +import { HOSTS, applyHosts, applyProviders, hostInstallState, installHost, applyAqeRouter, seedActivityRoutesIfMultiHost, printActivityRoutingTable, aqeSupportsAgentOverrides, retireCodexMcp, ensureRufloMcpInCodex, applySetupHostFlags, bothHostsEnabled } from '../lib/providers.mjs'; import { installedVersion } from '../lib/versions.mjs'; import * as rb from '../lib/ruvnet-brain.mjs'; import * as adb from '../lib/agentdb.mjs'; @@ -74,8 +74,8 @@ Options: --no-security skip the security-surface verification --codex enable + install the OpenAI Codex host during setup (dual-mode; default is claude-only, codex opt-in). Installs @openai/codex - if absent (prompted; external installs untouched) and wires - the Claude↔Codex bridges + per-activity routing. + if absent (prompted; external installs untouched), wires + shared Ruflo/AQE access, and seeds per-activity routing. --opencode enable the opencode host during setup: wires opencode.json (claude-flow + ruvnet-brain MCP, skills paths, permissions), deploys the lifecycle plugin + platform skill, and converts @@ -351,7 +351,7 @@ export async function run_project({ flags, cfg, trustDisclosed = false }) { } // 7. Write-verification (store → actual on-disk row, then clean up). Native - // bridges may select agentdb-memory.db beside the pinned compatibility DB. + // Native memory integrations may select agentdb-memory.db beside the pinned compatibility DB. const probeKey = `_setup/verify-${process.pid}-${Date.now()}`; const stored = (await runCmd('ruflo', ['memory', 'store', '-k', probeKey, '--value', 'setup-verify', '-n', '_setup'], { cwd: root, env })).code === 0; const landed = stored ? findMemoryEntry(root, '_setup', probeKey) : null; @@ -407,11 +407,10 @@ export async function run_project({ flags, cfg, trustDisclosed = false }) { if (rt.changed) (rt.ok ? ok : warn)(`aqe router: ${rt.detail}`); if (Object.keys(cfg.routing?.routes ?? {}).length) printActivityRoutingTable(cfg); if (cfg.integrations?.hosts?.codex) { - const mcp = await ensureCodexMcp(cfg, root); - if (mcp.changed) saveKitConfig(cfg); // persist the codexMcp ownership marker - if (mcp.changed || !mcp.ok) (mcp.ok ? ok : warn)(`codex MCP: ${mcp.detail}`); - // reverse bridge: register ruflo MCP into codex (codex→ruflo) so the bridge is - // two-way — parity with `ak sync` / `ak host pick`. + const mcp = await retireCodexMcp(cfg, root); + if (mcp.changed) saveKitConfig(cfg); + if (mcp.changed || !mcp.ok) (mcp.ok ? ok : warn)(`legacy codex MCP: ${mcp.detail}`); + // Independently register Ruflo in Codex for shared routing/swarm/memory tools. const rmcp = await ensureRufloMcpInCodex(cfg, root); if (rmcp.changed) saveKitConfig(cfg); // persist reverse MCP ownership if (rmcp.changed || !rmcp.ok) (rmcp.ok ? ok : warn)(`ruflo→codex MCP: ${rmcp.detail}`); @@ -504,7 +503,7 @@ export async function run({ flags, pkgRoot, confirm = ask }) { info('not inside a project (no .git here) — run `ak setup` from a repo to set one up'); } // Final reconcile pass — deliberately AFTER the hosts branch (which installs - // the claude/codex/opencode CLIs) and the project phase (whose codex bridge + // the claude/codex/opencode CLIs) and the project phase (whose Codex integration // creates ~/.codex): the user-scope MCP registration needs the claude CLI on // disk, and several guidance blocks gate on freshly-installed hosts // (command:codex, flag:dualMode). Shares blocks.mjs reconcileGuidance with diff --git a/src/commands/status.mjs b/src/commands/status.mjs index 7458907..ac32255 100644 --- a/src/commands/status.mjs +++ b/src/commands/status.mjs @@ -8,7 +8,7 @@ import { loadRing, detectRegression } from '../lib/health-history.mjs'; import * as paths from '../lib/paths.mjs'; import { nativesStatus, rufloRuntimeNatives, dbPathPinStatus, aidefencePresent, securityPresent } from '../lib/natives.mjs'; import { scanNpxStale } from '../lib/npx.mjs'; -import { registrationStatus, codexMcpStatus, rufloCodexMcpStatus, ruvectorRegistered } from '../lib/mcp.mjs'; +import { registrationStatus, codexMcpStatus, codexMcpTopology, rufloCodexMcpStatus, ruvectorRegistered } from '../lib/mcp.mjs'; import { opencodeMcpStatus, catalogSource, createOpencodeLifecycleAdapter, opencodeArtifactReceiptState, @@ -27,7 +27,7 @@ import { HOSTS, settingsTarget, isDefault, managedEnv, MANAGED_ENV_KEYS, hostIns import { hostsWithLifecycle, isBuiltinHost, lifecycleExecutionEnabled } from '../lib/adapters/lifecycle-registry.mjs'; import { PROVIDER_REGISTRY } from '../lib/adapters/index.mjs'; import { configuredPolicyToAgentOverrides, agentOverridesDrift, routingSummary, divergedRoutes } from '../lib/routing.mjs'; -import { qeCourtShipped, readQeCourtConfig, validateCourtConfig } from '../lib/qeCourt.mjs'; +import { qeCourtShipped, readQeCourtConfig, validateCourtConfig, qeCourtReadiness } from '../lib/qeCourt.mjs'; import { drift as ruvectorDrift } from '../lib/ruvector.mjs'; import { statuslineDrift } from '../lib/codex-statusline.mjs'; import { inspectCodexPlugins } from '../lib/codex-plugins.mjs'; @@ -544,28 +544,24 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) { rows.push(row('mcp', 'warn', "legacy 'ruflo'-keyed MCP registration present", 'sync migrates it to claude-flow')); } - // codex MCP backend (mcp__codex__codex) — the dual-host swarm's inline Claude→Codex - // path (ADR-0001 projection #3). Only surfaces when the codex host is enabled; - // setup/sync register it project-scoped via ensureCodexMcp. Spawn-free: reads the - // project .mcp.json (== `claude mcp get codex`) plus the kit.json ownership marker. + // Retired Claude→Codex `codex mcp-server` projection (ADR-0033). Its absence + // is healthy; setup/sync remove only the prior agentic-kit-owned entry. + // User-owned entries are preserved and receive an explicit manual remedy. if (cfg.integrations?.hosts?.codex) { try { const { registered, owned } = codexMcpStatus(cfg, cwd); if (registered) { - rows.push(row('codex-mcp', 'ok', - `codex MCP registered (mcp__codex__codex)${owned ? '' : ' — pre-existing (not ak-managed)'}`)); - } else if (await have('codex')) { - rows.push(row('codex-mcp', 'warn', 'codex enabled but codex MCP not registered', - 'sync registers the codex MCP server')); + rows.push(row('codex-mcp', 'warn', + `deprecated codex mcp-server registered${owned ? ' — agentic-kit-owned' : ' — user-owned; preserved'}`, + owned ? 'sync retires the legacy MCP entry' : 'remove manually: claude mcp remove codex -s project')); } else { - rows.push(row('codex-mcp', 'warn', 'codex enabled but codex CLI not installed', - 'sync installs codex, then registers the codex MCP')); + rows.push(row('codex-mcp', 'ok', 'legacy codex mcp-server absent; supervised cross-host execution uses ak run')); } } catch (e) { rows.push(row('codex-mcp', 'warn', `codex MCP check unavailable: ${e.message}`)); } - // reverse bridge: ruflo MCP → codex (a codex-driven session reaches ruflo's - // tools). The mirror of the claude→codex row above; makes the bridge two-way. + // Independent Ruflo MCP integration lets a Codex-driven session reach the + // same routing, swarm, and memory tools as Claude. try { const { registered, owned, command, args } = rufloCodexMcpStatus(cfg); const workspacePinned = command === 'ak' @@ -584,6 +580,32 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) { } catch (e) { rows.push(row('codex-mcp', 'warn', `ruflo→codex MCP check unavailable: ${e.message}`)); } + + // Effective project+user topology. These checks are independent of the + // agentic-kit ownership receipt because recursive/duplicate transports can + // stall a Codex-driven worker even when another tool created them. + try { + const topology = codexMcpTopology({ cwd }); + if (topology.selfRegistrations.length) { + const scopes = topology.selfRegistrations.map((entry) => entry.scope).join(', '); + rows.push(row('codex-mcp', 'fail', + `recursive codex → codex mcp-server registration detected (${scopes})`, + 'remove the [mcp_servers.codex] table from the reported Codex config before live multi-host runs')); + } + if (!topology.agenticQeRegistrations.length) { + rows.push(row('codex-mcp', 'warn', 'agentic-qe MCP is not concretely registered in Codex', + 'run: aqe platform setup codex --overwrite --with-ruflo')); + } else { + rows.push(row('codex-mcp', 'ok', 'agentic-qe MCP concretely registered in Codex')); + } + if (topology.duplicateRuflo) { + rows.push(row('codex-mcp', 'warn', + `duplicate Ruflo MCP registrations in Codex: ${topology.rufloRegistrations.map((entry) => entry.name).join(', ')}`, + 'keep the workspace-aware [mcp_servers.ruflo] entry and remove legacy duplicates after reviewing ownership')); + } + } catch (e) { + rows.push(row('codex-mcp', 'warn', `Codex MCP topology check unavailable: ${e.message}`)); + } } // Codex owns plugin installation, enablement, and refresh. Inspect every @@ -923,7 +945,13 @@ export async function collect({ pkgRoot, cwd = process.cwd() }) { rows.push(row('qe-court', 'warn', `qe-court panel invalid: ${violations.join(', ')} — regenerate with agentic-qe >=3.13.3 or choose different defense/jury vendors`)); } else { - rows.push(row('qe-court', 'ok', 'qe-court panel valid (vendor-diverse, jury independent of writer)')); + const readiness = qeCourtReadiness(qcRoot); + if (readiness.ready) { + rows.push(row('qe-court', 'ok', 'qe-court routing and consumer artifacts are ready; provider-seat readiness still requires a live proof')); + } else { + rows.push(row('qe-court', 'warn', + `qe-court routing config passes the local anti-collusion check, but executability is not proven (${readiness.artifactIssues.join('; ')})`)); + } } } } diff --git a/src/commands/sync.mjs b/src/commands/sync.mjs index 03fbea3..a2da7ef 100644 --- a/src/commands/sync.mjs +++ b/src/commands/sync.mjs @@ -13,7 +13,7 @@ import { hostsWithLifecycle, lifecycleAdapterFor, lifecycleExecutionEnabled, det import { renderApplyReport } from '../lib/adapters/lifecycle-render.mjs'; import { listDaemons, staleDaemons, reap } from '../lib/daemons.mjs'; import { loadKitConfig, saveKitConfig } from '../lib/config.mjs'; -import { commandHosts, applyHosts, applyProviders, hostInstallState, installHost, applyAqeRouter, seedActivityRoutesIfMultiHost, migrateRetiredRoutesInConfig, ensureCodexMcp, ensureRufloMcpInCodex, bothHostsEnabled } from '../lib/providers.mjs'; +import { commandHosts, applyHosts, applyProviders, hostInstallState, installHost, applyAqeRouter, seedActivityRoutesIfMultiHost, migrateRetiredRoutesInConfig, retireCodexMcp, ensureRufloMcpInCodex, bothHostsEnabled } from '../lib/providers.mjs'; import { driftReport, selfDrift } from '../lib/versions.mjs'; import { RUVECTOR_PKG, managed as ruvectorManaged } from '../lib/ruvector.mjs'; import { pruneNpxStale } from '../lib/npx.mjs'; @@ -267,10 +267,10 @@ export async function run({ flags, pkgRoot, fetchLatest }) { } const router = applyAqeRouter(cfg, cwd); if (router.changed || !router.ok) report('aqe router', router); - const mcp = await ensureCodexMcp(cfg, cwd); + const mcp = await retireCodexMcp(cfg, cwd); if (mcp.changed) saveKitConfig(cfg); - if (mcp.changed || !mcp.ok) report('codex MCP', mcp); - // reverse bridge: register ruflo MCP into codex (codex→ruflo half) + if (mcp.changed || !mcp.ok) report('legacy codex MCP', mcp); + // Independent Ruflo integration for Codex-driven sessions. const rmcp = await ensureRufloMcpInCodex(cfg, cwd); if (rmcp.changed) saveKitConfig(cfg); if (rmcp.changed || !rmcp.ok) report('ruflo→codex MCP', rmcp); diff --git a/src/commands/x/host.mjs b/src/commands/x/host.mjs index 962af7b..53f961e 100644 --- a/src/commands/x/host.mjs +++ b/src/commands/x/host.mjs @@ -10,7 +10,7 @@ import { settingsTarget, isDefault, applyHosts, applyProviders, undoProviders, hostInstallState, hostAuthState, installHost, applyAqeRouter, undoAqeRouter, bothHostsEnabled, DUAL_ROLE_TIP, JUDGE_BIAS_TIP, QE_COURT_TIP, suggestedFallbackFor, - seedActivityRoutesIfMultiHost, printActivityRoutingTable, ensureCodexMcp, undoCodexMcp, + seedActivityRoutesIfMultiHost, printActivityRoutingTable, retireCodexMcp, undoCodexMcp, ensureRufloMcpInCodex, undoRufloMcpInCodex, detectAqeProviders, aqeProviderCredential, credentialGaps, fallbackSource, collectIntegrationFacts, } from '../../lib/providers.mjs'; @@ -296,7 +296,7 @@ function printQeCourtStatus(cwd) { console.log(` ${role.padEnd(28)} ${provider ?? dim('(unset)')}`); } if (violations.length) warn(`qe-court panel invalid: ${violations.join(', ')}`); - else ok('qe-court panel valid (vendor-diverse, jury independent of writer)'); + else ok('qe-court routing config passes the local anti-collusion check (runtime court readiness not proven)'); } /** Opt-in, per-activity re-seed of routes whose seeded pin diverges from the @@ -370,12 +370,6 @@ async function off({ cwd, pkgRoot }) { cfg.integrations.hosts = defaultHostMap(); cfg.routing.primaryHost = DEFAULT_PRIMARY_HOST; cfg.routing.routes = {}; - cfg.integrations.ownership ??= {}; - cfg.integrations.ownership.codex = { - ...(cfg.integrations.ownership.codex ?? {}), - mcp: null, - reverseMcp: null, - }; if (ret.ok && cfg.integrations.ownership.opencode) { delete cfg.integrations.ownership.opencode.catalogDir; } @@ -386,10 +380,18 @@ async function off({ cwd, pkgRoot }) { const router = undoAqeRouter(cwd); const mcp = await undoCodexMcp(cwd, { managed: codexMcpManaged }); const rmcp = await undoRufloMcpInCodex(cwd, { managed: rufloCodexManaged }); + cfg.integrations.ownership ??= {}; + cfg.integrations.ownership.codex = { + ...(cfg.integrations.ownership.codex ?? {}), + ...(mcp.ok ? { mcp: null } : {}), + ...(rmcp.ok ? { reverseMcp: null } : {}), + }; + saveKitConfig(cfg); const ocLine = ret.ok ? `opencode: ${ret.undo.detail}; ${ret.artifacts.detail}` : `opencode teardown incomplete — ${ret.undo.detail}`; - (ret.ok ? ok : warn)(`reset to claude-only default — ${env.detail}; ${router.detail}; ${mcp.detail}; ${rmcp.detail}; ${ocLine}`); - return ret.ok ? 0 : 1; + const complete = ret.ok && mcp.ok && rmcp.ok; + (complete ? ok : warn)(`reset to claude-only default${complete ? '' : ' with teardown receipts retained'} — ${env.detail}; ${router.detail}; ${mcp.detail}; ${rmcp.detail}; ${ocLine}`); + return complete ? 0 : 1; } export const parseModels = (csv) => csv.split(',').map((s) => s.trim()).filter(Boolean).map((tok) => { @@ -618,8 +620,9 @@ async function pick({ flags, cwd, pkgRoot }) { } } - // Codex owns two directional MCP bridges. Disable only the marker-owned - // bridges, matching OpenCode's receipt-based teardown semantics. + // Disable only marker-owned integrations, matching OpenCode's receipt-based + // teardown semantics. The legacy Claude→Codex MCP receipt may still exist on + // machines upgrading across ADR-0033. let codexRetired = null; if (prevCodex && !cfg.integrations.hosts.codex) { const mcp = await undoCodexMcp(cwd, { managed: codexMcpManaged }); @@ -627,8 +630,8 @@ async function pick({ flags, cwd, pkgRoot }) { cfg.integrations.ownership ??= {}; cfg.integrations.ownership.codex = { ...(cfg.integrations.ownership.codex ?? {}), - mcp: null, - reverseMcp: null, + ...(mcp.ok ? { mcp: null } : {}), + ...(rmcp.ok ? { reverseMcp: null } : {}), }; codexRetired = { mcp, rmcp }; } @@ -695,7 +698,10 @@ async function pick({ flags, cwd, pkgRoot }) { const h = applyHosts(cfg, cwd); (h.ok ? ok : fail)(`hosts: ${h.detail}`); - if (codexRetired) ok(`codex disabled: ${codexRetired.mcp.detail}; ${codexRetired.rmcp.detail}`); + if (codexRetired) { + const complete = codexRetired.mcp.ok && codexRetired.rmcp.ok; + (complete ? ok : warn)(`codex disabled${complete ? '' : ' with teardown receipts retained'}: ${codexRetired.mcp.detail}; ${codexRetired.rmcp.detail}`); + } if (primaryHost !== DEFAULT_PRIMARY_HOST) { const alt = routing.filter((e) => e !== primaryHost).join(', ') || 'none'; ok(`primary host: ${primaryHost} (alternate: ${alt})`); @@ -703,11 +709,11 @@ async function pick({ flags, cwd, pkgRoot }) { if (aqeProvider) ok(`aqe provider: AQE_LLM_PROVIDER=${aqeProvider}`); const router = applyAqeRouter(cfg, cwd); if (router.changed || !router.ok) (router.ok ? ok : warn)(`aqe router: ${router.detail}`); - const mcp = await ensureCodexMcp(cfg, cwd); - if (mcp.changed) saveKitConfig(cfg); // persist forward MCP ownership - if (mcp.changed || !mcp.ok) (mcp.ok ? ok : warn)(`codex MCP: ${mcp.detail}`); - // reverse bridge — register ruflo MCP into codex (codex→ruflo) so the bridge is - // two-way. aqe's codex MCP is handled by `aqe init --with-codex` (setup runs it). + const mcp = await retireCodexMcp(cfg, cwd); + if (mcp.changed) saveKitConfig(cfg); + if (mcp.changed || !mcp.ok) (mcp.ok ? ok : warn)(`legacy codex MCP: ${mcp.detail}`); + // Register Ruflo independently in Codex. Agentic-QE's Codex integration is + // handled by `aqe init --with-codex` during setup. const rmcp = await ensureRufloMcpInCodex(cfg, cwd); if (rmcp.changed) saveKitConfig(cfg); // persist reverse MCP ownership if (rmcp.changed || !rmcp.ok) (rmcp.ok ? ok : warn)(`ruflo→codex MCP: ${rmcp.detail}`); diff --git a/src/lib/adapters/registries.mjs b/src/lib/adapters/registries.mjs index ee010f2..9de7d19 100644 --- a/src/lib/adapters/registries.mjs +++ b/src/lib/adapters/registries.mjs @@ -231,7 +231,6 @@ const hostEntries = [ trust: { approvalPolicy: 'unchanged', changes: [ - { id: 'claude-to-codex-mcp', kind: 'mcp-registration', scope: 'project', owner: 'agentic-kit', value: 'codex mcp-server', effect: 'expose Codex to Claude Code as mcp__codex__codex in this project', operations: ['setup', 'host-pick', 'sync'], features: ['project'] }, { id: 'codex-to-ruflo-mcp', kind: 'mcp-registration', scope: 'user', owner: 'agentic-kit', value: 'ak x ruflo-mcp', effect: 'register the Ruflo MCP server in Codex with workspace-pinned project memory', operations: ['setup', 'host-pick', 'sync'], features: ['project'] }, { id: 'aqe-codex-integration', kind: 'host-integration', scope: 'project', owner: 'agentic-qe', value: 'aqe init --with-codex', effect: 'project Agentic-QE Codex skills and configuration', operations: ['setup'], features: ['project', 'aqe'] }, ], diff --git a/src/lib/blocks.mjs b/src/lib/blocks.mjs index af6fa92..713c581 100644 --- a/src/lib/blocks.mjs +++ b/src/lib/blocks.mjs @@ -97,7 +97,7 @@ export const BUILTIN_BLOCKS = [ // so it lands in the two MACHINE-scoped guidance files — ~/.claude/CLAUDE.md // (claude) and ~/.codex/AGENTS.md (agents-user) — never a repo's checked-in // AGENTS.md, which would leak machine truths into shared git history (ADR-0008). - // Documents `ak run`, the Claude↔Codex bridge, and per-activity routing. + // Documents `ak run`, shared Ruflo/AQE access, and per-activity routing. slug: 'ruflo-dual-mode-reference', template: 'dual-mode-reference.md', position: 'append', diff --git a/src/lib/dashboard/about-directory.mjs b/src/lib/dashboard/about-directory.mjs index fd7c321..3e01e72 100644 --- a/src/lib/dashboard/about-directory.mjs +++ b/src/lib/dashboard/about-directory.mjs @@ -278,12 +278,12 @@ const ENTRIES = Object.freeze([ Object.freeze({ id: 'dual-host-routing', category: 'configured', - name: 'Dual-host routing & bridge', + name: 'Dual-host routing & shared tools', tagline: 'Each kind of work goes to the better host.', paragraph: 'With Claude Code and Codex both enabled, ak records which host handles which kind ' - + 'of work — coding, testing, review, security — and wires each one to reach the ' - + 'other as a tool, so a job can be handed across mid-flight. You pick the table and ' + + 'of work — coding, testing, review, security — and gives both hosts the same ' + + 'Ruflo/AQE tools. Bounded `ak run` workers hand work across hosts. You pick the table and ' + 'which host leads: `ak host pick`.', links: Object.freeze([]), icon: MONOGRAM('R', '--info'), diff --git a/src/lib/execution/process-tree.mjs b/src/lib/execution/process-tree.mjs index dafac0b..1c24150 100644 --- a/src/lib/execution/process-tree.mjs +++ b/src/lib/execution/process-tree.mjs @@ -7,13 +7,25 @@ import { isWindows } from '../paths.mjs'; /** * @param {any} child * @param {'SIGTERM'|'SIGKILL'} signal - * @param {{windows?:boolean,runFn?:typeof run}} [options] + * @param {{windows?:boolean,runFn?:typeof run,processKill?:typeof process.kill}} [options] */ export async function signalProcessTree(child, signal, { windows = isWindows, runFn = run, + processKill = process.kill, } = {}) { - if (!windows) return !!child?.kill?.(signal); + if (!windows) { + // Subprocess workers are spawned detached on POSIX, making the direct + // child a process-group leader. A negative pid reaches that whole group, + // including MCP servers or other descendants started by the host CLI. + if (Number.isInteger(child?.pid) && child.pid > 0) { + try { + processKill(-child.pid, signal); + return true; + } catch { /* non-detached/custom child — fall through to direct signal */ } + } + return !!child?.kill?.(signal); + } if (!Number.isInteger(child?.pid) || child.pid < 1) return false; // Windows has no portable TERM for arbitrary console trees. /F is used for // both rungs; /T is the required descendant-ownership guarantee. diff --git a/src/lib/execution/subprocess.mjs b/src/lib/execution/subprocess.mjs index 890f1c8..825ea5e 100644 --- a/src/lib/execution/subprocess.mjs +++ b/src/lib/execution/subprocess.mjs @@ -261,7 +261,14 @@ export function createSubprocessExecutionAdapter({ if (invocation.resolved === false) { throw new Error(`${host} command has no safe Windows invocation`); } - const child = spawnFn(invocation.command, invocation.args, { cwd: state.cwd, env: process.env, stdio: ['ignore', 'pipe', 'pipe'] }); + const child = spawnFn(invocation.command, invocation.args, { + cwd: state.cwd, + env: process.env, + stdio: ['ignore', 'pipe', 'pipe'], + // POSIX process groups let timeout/cancel reach host-spawned MCP + // descendants. Windows uses taskkill /T in signalProcessTree. + detached: process.platform !== 'win32', + }); if (!child?.once) throw new Error(`${host} process did not expose child lifecycle events`); // Register the acquired child on runner-owned state before any future // await so a launch deadline can still cancel and clean it up. diff --git a/src/lib/hosts.mjs b/src/lib/hosts.mjs index 8d6bc5c..06736c9 100644 --- a/src/lib/hosts.mjs +++ b/src/lib/hosts.mjs @@ -1,8 +1,8 @@ // Host-adapter core — the host-neutral spine of ak's ambidextrous experience. // // why: ak models two frontier hosts (claude, codex) but the *experience* used to -// be claude-shaped (guidance → CLAUDE.md, statusline = claude's, MCP bridge one -// way). This module makes "which host is driving" a first-class, detected axis and +// be claude-shaped (guidance → CLAUDE.md, statusline = claude's). This module +// makes "which host is driving" a first-class, detected axis and // puts every host-specific artifact behind a per-host descriptor, so the commands // become host-loops instead of claude-hardcoded paths. // @@ -12,10 +12,9 @@ // versions) lives in providers.mjs, which imports THIS (one direction only). // // Grounded: -// - codex is exposed as an MCP server via `codex mcp-server` (stdio JSON-RPC, -// tools codex/codex-reply) and consumes servers via `[mcp_servers.*]` in -// ~/.codex/config.toml (TOML). Claude Code uses settings.json (JSON) + -// `claude mcp add`. +// - codex consumes servers via `[mcp_servers.*]` in ~/.codex/config.toml +// (TOML). OpenAI deprecated its former `codex mcp-server` mode; managed +// cross-host execution uses `ak run` (ADR-0033). // - codex statusline is a fixed built-in enum (`tui.status_line`); a // command-backed footer like Claude Code's is an unimplemented upstream // request (openai/codex #16921/#17827/#20140/#20244). @@ -142,10 +141,9 @@ export function hostAsymmetryNote(hostIdOrEntry, { registry = effectiveHostRegis if (!host?.capabilities?.canDriveSession) return ''; const notes = []; - // F-25: this host is registered as callable FROM another host via MCP — a - // real bridge capability, read off the trust manifest rather than an id - // check ('expose