Skip to content

feat(acp): add a dormant Codex ACP backend seam - #6910

Closed
vamgan wants to merge 1 commit into
kirodotdev:mainfrom
vamgan:feat/codex-backend-identifier
Closed

feat(acp): add a dormant Codex ACP backend seam#6910
vamgan wants to merge 1 commit into
kirodotdev:mainfrom
vamgan:feat/codex-backend-identifier

Conversation

@vamgan

@vamgan vamgan commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Kiro Crew drives one first-class harness (kiro-cli) and adapts others through the
ACP seam. The Codex ACP adapter is a plausible next adapted harness, but its
identifier does not exist yet: register_selectable_backend() refuses any id
outside ACP_BACKENDS_KNOWN, and AcpProvider.__init__ refuses it a second time,
so there is currently no way for an edition to register a Codex provider at all —
not even to evaluate one.

Why it matters

This lands the identifier and the per-harness branches it needs, and stops
there
: ACP_BACKEND_CODEX is deliberately absent from
BASELINE_SELECTABLE_BACKENDS, so no build offers it, a persisted "codex"
degrades to kiro through the existing single gate (H3), and nothing spawns the
adapter until something calls register_selectable_backend.

The reason for that absence is narrow and worth stating precisely, because it is
not the reason claude used to be excluded. Claude is baseline-selectable
today, and by the same argument (acp/client.py owns the spawn path, the adapter
is a public npm package) codex qualifies too. What codex still lacks is the other
half claude has: agent_sdk/backend_install.py probes claude's two binaries and
names the command that installs them, and there is no such probe for codex-acp.
Without it the install row can only read unknown, so an operator whose session
failed to start would get a switch with nothing to act on.

What changed (motivation → approach → change)

Vocabulary and identifier

  • acp_backends.pyACP_BACKEND_CODEX in ACP_BACKENDS_KNOWN and in
    POLICY_ID_BY_BACKEND, so a governance rule can name the id before an
    edition registers it (main added a completeness gate over that mapping; an id
    missing from it would be ungovernable while looking governed).
  • test_agent_backend_editable.py — main now pins
    BASELINE_SELECTABLE_BACKENDS == ACP_BACKENDS_KNOWN. Rather than relax that to
    an inequality, the exception is named in NOT_SHIPPED_SELECTABLE with its
    reason, so a plain baseline != known still fails and adding a second
    exception is a deliberate edit.
  • acp/types.pyPROVIDER_LABEL_CODEX, plus an explicit non-membership
    decision with its reason for every Group B capability set (H6/H8): one
    process per session, no _session/steer, a Node harness so Crew's sandbox is
    the only OS confinement it gets, and its own credentials store so a kiro
    logout says nothing about it.

Spawn path

  • acp/client.py_is_codex; PROTOCOL_VERSION_CODEX as its own literal
    (H10, so a future divergence is a one-line edit rather than a silent downgrade
    of whichever harness moved first); _resolve_codex_acp_bin mirroring the
    claude resolver's order and node-resolution rules; a _spawn branch; the
    _codex_session_mcp_servers() default-[] override seam; per-harness log
    labels; and a session/load arm — the adapter keeps its own session records,
    so gating file_ok on a kiro transcript would make an activated codex session
    start fresh every time instead of resuming.
  • acp/client.py — each per-harness mcpServers hook is now spliced only
    for its own backend. Both defaults return [], so the previous shared splice
    was inert, but an edition overriding both hooks would hand a claude session
    codex's entries and vice versa — and an entry whose transport the adapter does
    not advertise fails the whole session/new, not just that server.
  • acp/client.py_vendored_claude_acp_roots_vendored_acp_roots. The
    roots are plain node_modules directories and each resolver joins its own
    package path onto them, so both adapters legitimately share it; the claude name
    would have suggested codex had a copy of its own.

Tuning channels stated as membership (H6)

Six sites in providers/acp.py read if is_claude_backend: <adapter> else: <kiro>. That reads correctly with two harnesses and is wrong with three — codex
took the kiro arm at every one. Two new sets replace the negation:
ACP_BACKENDS_EFFORT_VIA_CONFIG_OPTION and ACP_BACKENDS_KIRO_SLASH_COMMANDS.

Kept separate from ACP_BACKENDS_MODEL_VIA_CONFIG_OPTION despite identical
membership today, because AcpClient.supports_config_option exists precisely
because adapter builds ship one config option without the other — collapsing them
would let a harness that gained model-switching inherit an effort channel it never
advertised.

What was actually broken:

  • stream_command and change_effort routed codex into
    _kiro.dev/commands/execute, a kiro extension the adapter does not implement,
    so every slash command and every effort push would have returned -32601
    and reset the session.
  • _apply_effort_overlay and _apply_tool_search_overlay wrote the kiro
    workspace cli.json overlay for codex, which never reads it. The companion
    clear (_clear_cli_overlay_effort) was already membership-gated, so those
    writes left a stale effort level in the user's workspace that no later clear
    could reach.
  • clear_effort took the kiro arm: overlay write, then a /effort push.
  • The effort telemetry label was literally "claude" or "kiro", so a codex
    session logged backend=kiro. It now resolves through POLICY_ID_BY_BACKEND,
    which already owns that translation (and handles ACP_BACKEND_KIRO being the
    empty string).
  • change_effort now reports unsupported for a harness in neither set rather
    than defaulting to a channel — an if/else would still have meant "everything
    that isn't an adapter is kiro". _set_claude_effort
    _set_effort_config_option: the method is the channel, not the harness.

Docs

  • docs/system-specs/modules/harness-parity.md — the intro records codex as
    the one named baseline exception and why; the H6 row names the new sets and the
    two new pins.

Dashboard

The Agent Backend switch could not have offered a registered agent. visible was
built from a hard-coded [KIRO, CLAUDE, KAS] and then filtered by the live schema
enum — narrowing works, widening is impossible — so an id an edition registered
through register_selectable_backend was selectable on the wire, valid to PATCH,
present in the probe payload, and absent from the only control that sets
agent.acp_backend. That literal was the last copy of the selectable list, which is
the thing the registry exists to retire, and the panel's own module note already
promised the opposite ("a build that ships another agent lights it up here with no
frontend change").

  • website/src/pages/developer/AgentBackendTab.tsxcandidates is now the
    union of the schema enum, the probe payload, current, and the ids this frontend
    has names for, sorted KIRO-first then by policy_id (the order the probe endpoint
    already uses, so the control does not reshuffle as queries resolve).
    unavailable() still narrows it, so an agent this deployment may not select is
    hidden exactly as before.
  • The named trio is now a floor, not a ceiling. As a ceiling it capped the panel
    at three ids permanently; as a floor it only guarantees the core agents keep rows
    while the schema and probe queries are in flight — dropping it outright regressed
    the "every option visible while the schema is still loading" case to a
    single-option control on a slow load.
  • nameOf / iconOf fall back to the server's policy_id and a generic icon for
    an id with no translated entry. Untranslated deliberately: policy_id is the name
    a governance rule spells, so it is already a word rather than a token, and a chip
    labelled undefined is the alternative. A core agent that ships selectable earns
    a real i18n entry instead of the fallback.
  • Corrected a stale claim in the same module note — it said this build does not
    include Claude Code, which stopped being true when claude joined the baseline.

Nothing renders differently today: codex is not selectable, so it is filtered out
exactly as before. The change is what makes the seam this PR ships reachable at all.

Why no screenshot: On a public build the panel renders the identical three agents
in the identical order — the probe payload now carries a codex row (it lists every
id ACP_BACKENDS_KNOWN holds), unavailable() drops it exactly as the old
hard-coded list did, and the policy_id label/icon fallback is unreachable until
something calls register_selectable_backend, so there is no rendered state to
capture that differs from main.

Notes for review

test/test_acp_backend_kas.py: the is_acp_runtime_backend == not is_claude_backend equivalence is scoped to the three backends that conversion
covered, rather than ACP_BACKENDS_KNOWN. That identity holds only while claude is
the sole non-runtime harness; codex is the first additional one, so widening it
would require every future harness to be claude-shaped — the negation reasoning
H5/H6 exist to retire. The durable form (codex's absence from
ACP_BACKENDS_ACP_RUNTIME) is pinned by its own assertion in the same file, so an
edit routing codex onto AcpRuntime cannot land green.

Deliberately not changed: a failed adapter resolution stays cached for the
process lifetime. That is pre-existing, documented behaviour for the claude
resolver, and codex now matches it. Fixing it for codex alone would reintroduce the
asymmetry the rest of this change removes — it should be both or neither, in its own
PR.

Follow-up, not in scope: _probe_codex in agent_sdk/backend_install.py is
the single remaining thing between this seam and codex being baseline-selectable.
When it lands, the NOT_SHIPPED_SELECTABLE entry and the unknown-row assertion
both come out.

Tests

  • test/test_harness_parity.py — codex is known but not shipped-selectable;
    codex carries its own provider label; the model-switch channel is opt-in; the
    effort channel is opt-in and the two channels are disjoint; the overlay is
    written only for its readers; each mcpServers seam is spliced only for its own
    harness; codex keeps its own spawn branch and protocol literal.
  • test/test_acp_backend_kas.pyis_codex_backend added to the
    mutual-exclusion pin, plus the new ACP_BACKENDS_ACP_RUNTIME assertion.
  • test/test_agent_sdk_backend_install.py — the codex install row reads
    unknown and names nothing to install, which is the gap that keeps it out of
    the baseline.
  • website/src/test/AgentBackendTab.test.tsx — two cases the old candidate list
    could not express: an agent this frontend has no name for renders under its
    policy_id and saves the id the wire accepts; and a known-but-unselectable
    agent stays hidden even though the probe payload lists it (the probe returns a
    row per id the CORE knows, which is wider than what a deployment may select —
    codex is exactly that case, so widening candidates to the payload must not
    smuggle in an option PATCH answers 400 for).
  • Full backend suite: 75,628 passed, 16 failed — the same 16 fail on an
    unmodified origin/main worktree on this host (they need user-namespace
    sandbox sealing, which this machine does not permit).
  • mypy clean on all four changed source files.
  • Gates run locally against origin/main: harness-parity (self-test + diff-scoped),
    black, isort, flake8, docs-lint, scrub-lint, loop-bound-locks.
  • Not run locally: vitest, tsc -b and eslint. Frontend dependencies could
    not be installed in my environment, so the two frontend lanes are unverified here
    and CI gives them their first real signal. What I could check without them:
    declaration order in the component, that Boxes is already imported from
    lucide-react elsewhere in the tree, and a hand-trace of all 15 pre-existing
    AgentBackendTab cases against the new candidates (the default fixture yields
    ['', 'claude', 'kas'] in the same order as before, so none should change
    behaviour).

@vamgan
vamgan requested a review from a team as a code owner August 30, 2026 04:08
@vamgan
vamgan requested a review from krishdhasmana August 30, 2026 04:08
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 30, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 89b6853 to 33d402f Compare September 2, 2026 00:48
@github-actions github-actions Bot removed the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 33d402f to 48d4c41 Compare September 2, 2026 00:59
@vamgan
vamgan requested a review from a team September 2, 2026 00:59
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 48d4c41 to 57666db Compare September 2, 2026 01:07
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 62d86c1 to 6293b61 Compare September 2, 2026 01:22
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 6293b61 to 800062e Compare September 2, 2026 01:30
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 800062e to cf4033b Compare September 2, 2026 01:56
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from cf4033b to 2b74ae0 Compare September 2, 2026 02:01
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 2b74ae0 to 4eb6f16 Compare September 2, 2026 02:54
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — ✅ PASS

UX-level review of c593891393c3c06534a3cbff204c630ce3591f02 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: PASS

No rendered change on any shipped build; the edition-only fallback path keeps loading floors, stable ordering, honest status lines, and readable policy_id labels.

[UX-REVIEWED] c593891

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — 🟡 CONCERNS

Design-level review of c593891393c3c06534a3cbff204c630ce3591f02 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The problem claim checks out: register_selectable_backend rejects ids outside ACP_BACKENDS_KNOWN, so a Codex provider genuinely cannot be registered today, and the PR lands the id while keeping it out of BASELINE_SELECTABLE_BACKENDS. The six negation→membership conversions and the dashboard candidates widening are the right shape under the repo's harness-parity rules (H5/H6), and each non-membership decision is stated with its reason. The design is sound; my remaining findings are fidelity and duplication.

Design-Verdict: CONCERNS

Sound dormant seam done by the book; two unrelated test hunks ride along undocumented, and the codex resolver is a near-verbatim copy of the claude one.

Watch

  • Diff→description fidelity: the test_design_tweak_relay_paths.py flake rewrite (socketpair SHUT_WR replacing a sleep-ordered feeder thread) and the new test_session_control.py autouse rate-limit-reset fixture appear nowhere in the description and have nothing to do with the Codex seam. Both are good fixes, but in a fork PR an unexplained hunk in an unrelated file is exactly the signal reviewers must clear — move them to their own PR or document them.
  • The codex spawn/handshake/resume path ships with no build that can exercise it end-to-end (structure-pinning tests only), so "the spawn path is complete" is first falsified the day an edition registers it. Acceptable for a staged landing, but whoever lands _probe_codex should plan a real-session smoke test in the same change.

Suggestions

  • _resolve_codex_acp_bin duplicates _resolve_claude_acp_bin's entire override→vendored→mise→PATH→node ladder; parameterize that machinery over a per-adapter spec (bin, pkg entry, env var) — H9/H10 protect kiro's spawn branch and per-harness handshake literals, not generic path-search plumbing shared by two adapted harnesses, and the PR already shares _vendored_acp_roots on the same reasoning.

[DESIGN-REVIEWED] c593891

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — 🔴 changes requested (blocking)

Reviewed c593891393c3c06534a3cbff204c630ce3591f02 via the fork AI-review pipeline; updated in place on each push.

BLOCKING -- src/kiro_crew/acp/client.py:3701 -- H13 changes the Kiro session path
*(self._claude_session_mcp_servers() if self._is_claude else []),
edition override -> Kiro session/new or session/load -> injected MCP entries disappear.
Anchor: harness-parity H13
Fix: Preserve the existing hook for Kiro; exclude only Codex at lines 3701 and 3834.

FINDING -- src/kiro_crew/acp_backends.py:362 -- "ACP_BACKENDS_CONFIG_OPTION_TUNING" merges model and effort channels despite the stated purpose promising separate sets -> Fix: Split model-via-config and effort-via-config memberships and update their call sites.

[BLOCK-MERGE] c593891
[GPT-REVIEWED] c593891

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — 🟡 CONCERNS

Premise-level review of c593891393c3c06534a3cbff204c630ce3591f02 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence gathered — the contract's counts check out: register_selectable_backend has zero in-repo callers (edition-only seam), providers/acp.py has the seven is_claude_backend branch sites the diff converts, backend_install.py derives its rows from ACP_BACKENDS_KNOWN via _PROBES.get() so the codex "unknown" row claim holds, and create_rate_limit.reset_for_tests exists for the new fixture. Final review:

First-Principles-Verdict: CONCERNS

The codex seam's only support is "a plausible next adapted harness" — no registrant is named anywhere — and shipping it probe-less creates a baseline-exception concept that ordering the work the other way never would.

What this change ships

Intent: let a downstream edition register and run a Codex ACP agent, while the public build offers nothing new. ADDITION.

  1. An edition can register the codex agent id (was rejected outright) — inherited: "plausible next" is the only support
  2. Registered codex sessions spawn the adapter with own resolver, CODEX_ACP_BIN override, handshake — justified given 1 (H8/H9/H10)
  3. Model/effort pushes routed by membership sets, not "not claude" — justified (documented H5/H6)
  4. Effort telemetry logs the real backend name, not "claude"/"kiro" — justified
  5. Workspace cli.json overlay written only for harnesses that read it — justified (stale-file harm named)
  6. Backend panel offers any server-registered agent, not a fixed three — justified; deletes the last hard-coded selectable list
  7. Unknown agents render under their policy_id with a generic icon — justified consequence of 6
  8. Codex named as the one baseline exception (NOT_SHIPPED_SELECTABLE, doc paragraph, dedicated test) — exists only because the install probe doesn't
  9. Flaky WS relay test rewritten without threads/sleeps — rides along (unrelated subsystem)
  10. Session-create rate-limit reset fixture in test_session_control.py — rides along; plausibly triggered by this PR's added tests

Watch

  • Provenance of the whole codex surface: the description offers only "a plausible next adapted harness." Grepped register_selectable_backend under src/: 0 callers (definition and comments only), so activation is out-of-repo by design — not dead surface, but no edition, ticket, or defect names a consumer, and PROVIDER_LABEL_CODEX/POLICY_ID_BY_BACKEND["codex"] become persisted, policy-nameable vocabulary that cannot be quietly withdrawn once any edition registers it.
  • Items 9 and 10 are non-codex work; the description is truncated at 8000 bytes so I cannot confirm they are declared, but neither belongs to this change's job.

Subtractions

  • Defer ACP_BACKEND_CODEX until the backend_install.py probe lands, then ship both halves at once: codex then enters BASELINE_SELECTABLE_BACKENDS on the same two facts claude did, and NOT_SHIPPED_SELECTABLE (test/test_agent_backend_editable.py), the harness-parity.md exception paragraph, and test_codex_is_known_but_not_shipped_selectable are never written.
  • Defer the test_design_tweak_relay_paths.py rewrite to its own PR — it shares no cause with this change.

[FIRST-PRINCIPLES-REVIEWED] c593891

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed c593891393c3c06534a3cbff204c630ce3591f02 via the fork AI-review pipeline; updated in place on each push.

Review details

I've independently verified the load-bearing claims:

  • The working tree is at the PR base (change_effort on disk still reads if self.is_claude_backend), so the patch is the change under review.
  • The capability sets resolve to: ACP_BACKENDS_CONFIG_OPTION_TUNING = {claude, codex} and ACP_BACKENDS_KIRO_SLASH_COMMANDS = {kiro, kas}. For the three shipped backends {kiro, claude, kas}, backend in CONFIG_OPTION_TUNING is exactly is_claude, and backend not in KIRO_SLASH_COMMANDS is exactly is_claude — so every refactored branch (set_model, _apply_startup_model, change_effort, clear_effort, _apply_effort_overlay, _apply_tool_search_overlay, stream_command) is behavior-preserving on the shipped paths.
  • resolve_selected_backend degrades any persisted "codex" to ACP_BACKEND_KIRO (it isn't in selectable_backends()), and DefaultProviderRegistry.register_acp_backends registers nothing, so the entire codex spawn/handshake/MCP path is unreachable in the public build — no concrete input reaches it.
  • Frontend: unavailable(value) still requires schema-enum membership (selectable.includes(value)) for any non-current row, so widening candidates to the probe union cannot surface a row PATCH would 400; current gets the same treatment it had in the base.

The candidate file recorded no candidates, and I found nothing reachable to add under the Step 2 bar.

No findings.

[OPUS-REVIEWED] c593891

@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 4eb6f16 to 1adae96 Compare September 2, 2026 03:51
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Sep 2, 2026
Adds the Codex adapter as an adapted harness: known and driveable, but absent
from BASELINE_SELECTABLE_BACKENDS, so no build offers it until an edition
registers it with register_selectable_backend.

Vocabulary and identifier
- acp_backends.py: ACP_BACKEND_CODEX in ACP_BACKENDS_KNOWN and in
  POLICY_ID_BY_BACKEND, so a governance rule can name the id before an edition
  registers it. ACP_BACKENDS_CONFIG_OPTION_TUNING is ONE set covering both the
  model and the effort config options: whether a given adapter build advertises a
  specific option is a runtime question that AcpClient.supports_config_option
  already answers, so a second frozenset with identical membership would restate
  that check statically and less accurately. Split it when a harness genuinely
  takes one option and not the other.
- platform/defaults.py: DefaultProviderRegistry.register_acp_backends said "the
  baseline covers every id in ACP_BACKENDS_KNOWN, so there is no id it accepts
  that is not already selectable". This change makes that false -- codex is the
  first known-but-unshipped id -- so the comment now records that the seam is
  live and that calling it is how an edition activates such an id. Absent from BASELINE_SELECTABLE_BACKENDS: the spawn path is
  complete, but backend_install.py has no probe for the adapter, so the switch
  would render with nothing to say about a session that failed to start.
  test_agent_backend_editable.py names the exception in NOT_SHIPPED_SELECTABLE
  rather than relaxing the baseline == known assertion.
- acp/types.py: PROVIDER_LABEL_CODEX, plus an explicit non-membership decision
  with its reason for every Group B capability set (H6/H8): one process per
  session, no _session/steer, a Node harness so Crew's sandbox is the only OS
  confinement it gets, and its own credentials store so a kiro logout says
  nothing about it.

Spawn path
- acp/client.py: _is_codex; PROTOCOL_VERSION_CODEX as its own literal (H10, so a
  future divergence is a one-line edit rather than a silent downgrade of
  whichever harness moved first); _resolve_codex_acp_bin mirroring the claude
  resolver's order and node-resolution rules; a _spawn branch; the
  _codex_session_mcp_servers override seam; per-harness log labels; and a
  session/load arm -- the adapter keeps its own session records, so gating
  file_ok on a kiro transcript would make an activated codex session start fresh
  every time instead of resuming.
- acp/client.py: each per-harness mcpServers hook is now spliced only for its
  own backend. Both defaults return [], so the previous shared splice was inert,
  but an edition overriding both hooks would hand a claude session codex's
  entries and vice versa -- and an entry whose transport the adapter does not
  advertise fails the whole session/new, not just that server.
- acp/client.py: _vendored_claude_acp_roots -> _vendored_acp_roots. The roots
  are plain node_modules directories and each resolver joins its own package
  path onto them, so both adapters share it; the claude name would have made the
  next reader think codex had its own copy.

Tuning channels stated as membership (H6)
Six sites in providers/acp.py read `if is_claude_backend: <adapter> else:
<kiro>`. That is correct with two harnesses and wrong with three: codex took the
kiro arm at every one of them. ACP_BACKENDS_EFFORT_VIA_CONFIG_OPTION and
ACP_BACKENDS_KIRO_SLASH_COMMANDS replace the negation.

- stream_command and change_effort routed codex into
  _kiro.dev/commands/execute, a kiro extension the adapter does not implement,
  so every slash command and every effort push would have returned -32601 and
  reset the session.
- _apply_effort_overlay and _apply_tool_search_overlay wrote the kiro workspace
  cli.json overlay for codex, which never reads it. The companion clear
  (_clear_cli_overlay_effort) was ALREADY membership-gated, so those writes left
  a stale effort level in the user's workspace that no later clear could reach.
- clear_effort took the kiro arm: overlay write, then a /effort push.
- The effort telemetry label was literally "claude" or "kiro", so a codex
  session logged backend=kiro. It now resolves through POLICY_ID_BY_BACKEND.
- change_effort now reports unsupported for a harness in NEITHER set instead of
  defaulting to a channel. _set_claude_effort -> _set_effort_config_option: the
  method is the channel, not the harness, and codex now arrives there too.

Dashboard switch renders from the registry
The Agent Backend panel could never have offered a registered agent. `visible` was
built from a hard-coded [KIRO, CLAUDE, KAS] and then FILTERED by the live schema
enum -- narrowing works, widening is impossible -- so an id an edition registered
through register_selectable_backend was selectable on the wire, valid to PATCH,
present in the probe payload, and absent from the only control that sets
agent.acp_backend. That literal was the last surviving copy of the selectable list,
which is what the registry exists to retire, and the panel's own module note already
promised the opposite ("a build that ships another agent lights it up here with no
frontend change").

- AgentBackendTab.tsx: `candidates` is the union of the schema enum, the probe
  payload, `current`, and the ids this frontend has names for, sorted KIRO-first then
  by policy_id -- the order the probe endpoint already uses, so the control does not
  reshuffle as queries resolve. `unavailable()` still narrows it, so an agent this
  deployment may not select stays hidden exactly as before.
- The named trio is now a FLOOR, not a ceiling. As a ceiling it capped the panel at
  three ids permanently; as a floor it only guarantees the core agents keep rows
  while the schema and probe queries are in flight. Dropping it outright regressed
  the "every option visible while the schema is still loading" case to a
  single-option control on a slow load.
- nameOf/iconOf fall back to the server's policy_id and a generic icon for an id
  with no translated entry. Untranslated on purpose: policy_id is the name a
  governance rule spells, so it is already a word rather than a token, and the
  alternative is a chip labelled undefined. A core agent that ships selectable earns
  a real i18n entry instead of the fallback.
- Corrected a stale claim in the same module note: it said this build does not
  include Claude Code, which stopped being true when claude joined the baseline.

Nothing renders differently today -- codex is not selectable, so it is filtered out
exactly as before. The change is what makes the seam this commit ships reachable.

Two test-isolation fixes this change's own sharding exposes
Adding tests here moves later tests between CI shards, and two files depended on
which co-tenants they happened to share a worker with. No .test_durations is
committed, so pytest-split falls back to an even split by test COUNT -- the 9 tests
added above shift every later test's index and reshuffle that placement. Neither
test fails on mainline; both fail on this branch. The defects are pre-existing, the
trigger is this change, so they are fixed here rather than left for a follow-up.

- test_session_control.py: add the _fresh_create_budget autouse fixture.
  create_rate_limit._buckets is a module global keyed (verb, caller_key) over a
  five-minute window, and caller_key is the caller's SLOT NAME -- "chat-1" for these
  tests, one of the most common names in the suite. Any co-tenant creating a session
  from a chat-1 slot spends the same 20-per-window budget, after which the create
  path refuses before reaching the behaviour under test and the failure reads "too
  many sessions created recently" from an assertion that never ran. The file already
  had the identical fixture for stop_retry's process-wide state (_fresh_stop_windows);
  this is the sibling that was missed. Verified by pre-spending the chat-1 budget in
  the same process: 2 failed without the fixture, 3 passed with it.
- test_design_tweak_relay_paths.py: make the 101-handshake relay test deterministic.
  It fed the upstream socket from a daemon thread and used time.sleep(0.05) to order
  the close after the read, which held on an idle machine and lost on a loaded runner
  -- the pump saw EOF first and the assertion read an empty relay ("assert '101
  Switching Protocols' in ''"). Now the handshake is written and the write half shut
  down BEFORE the relay runs: a socket delivers buffered bytes ahead of the EOF that
  follows them, so there is nothing to race. Also drops the dead scaffolding the
  sleep was hiding -- an unused socketpair, a feeder thread writing to a socket the
  relay never reads, and an empty `with patch.object(...): pass` block -- left behind
  by an abandoned first attempt in the same test.

Tests
- test_harness_parity.py: codex is known but not shipped-selectable; codex
  carries its own provider label; the model-switch channel is opt-in; the effort
  channel is opt-in and the two channels are disjoint; the overlay is written
  only for its readers; each mcpServers seam is spliced only for its own
  harness; codex keeps its own spawn branch and protocol literal.
- test_acp_backend_kas.py: is_codex_backend in the mutual-exclusion pin, plus an
  ACP_BACKENDS_ACP_RUNTIME assertion. The is_acp_runtime_backend == not
  is_claude_backend equivalence is scoped to the three backends conversion
  covered rather than ACP_BACKENDS_KNOWN: that identity holds only while claude
  is the SOLE non-runtime harness, and codex is the first additional one, so
  widening it would require every future harness to be claude-shaped -- the
  negation reasoning H5/H6 exist to retire.
- test_agent_sdk_backend_install.py: the codex install row reads unknown and
  names nothing to install, which is the gap that keeps it out of the baseline.
- AgentBackendTab.test.tsx: an agent this frontend has no name for renders under
  its policy_id AND saves the id the wire accepts; and a known-but-unselectable
  agent stays hidden even though the probe lists it. The probe returns a row per id
  the CORE knows, a wider set than a deployment may select -- codex is exactly that
  case -- so widening candidates to the payload had to be paired with a test that it
  cannot smuggle in an option PATCH answers 400 for.

Not verified locally: vitest, tsc -b and eslint. Frontend dependencies could not be
installed in this environment, so the two frontend lanes are unproven here and CI
gives them their first signal. Checked without them: declaration order in the
component, that Boxes is already imported from lucide-react elsewhere in the tree,
and a hand-trace of all 15 pre-existing AgentBackendTab cases against the new
candidates (the default fixture yields ['', 'claude', 'kas'] in the same order as
before, so none should change behaviour).

Deliberately not changed: a failed adapter resolution stays cached for the
process lifetime. That is pre-existing, documented behaviour for the claude
resolver, and codex now matches it. Fixing it for codex alone would reintroduce
the asymmetry the rest of this change removes -- it should be both or neither,
in its own PR.

No .github/** file is touched. An earlier revision reformatted one call in
test_acp_backend_kas.py, which graduated the file off .github/black-baseline.txt
and forced a prune there -- and any .github change from a fork trips the
fork-workflow-change guard, which then needs a maintainer label. The reformat was
incidental to this change, so it is reverted instead: the file stays as the
baseline already describes it, and the guard has nothing to flag.
@vamgan
vamgan force-pushed the feat/codex-backend-identifier branch from 1adae96 to c593891 Compare September 2, 2026 04:09
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 2, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

Closing — a duplicate of #7813, which has since merged

Verified relationship: duplicate

This is one piece of work submitted twice, and I verified the identity rather than inferring it: #7813's first commit f3dbcdb and #6910's sole commit c593891 carry the same author (Vamil Gandhi 13998000+vamgan@users.noreply.github.com), the same author date (Sun Aug 30 03:28:41 2026 +0000) and the same subject, so f3dbcdb is a rebase of c593891. #7813 has since merged: f51e659 is an ancestor of origin/main 1a765b8 (the issue/PR reference check still says 'open' and is stale). I then checked every line #6910 adds against main's file contents, file by file. The whole seam is landed: ACP_BACKEND_CODEX in ACP_BACKENDS_KNOWN (acp_backends.py:59), out of BASELINE_SELECTABLE_BACKENDS (:86), POLICY_ID_BY_BACKEND row (:107), the complete _resolve_codex_acp_bin ladder, PROTOCOL_VERSION_CODEX, the _is_codex spawn branch, the session/load arm, and both per-harness mcpServers splices (main client.py:3722-3723 is byte-identical to pr/6910:3701-3702). The frontend AgentBackendTab.tsx fix is landed too - the only diff between main and #6910 there is comment prose plus a cosmetic comparator rewrite, zero behavioural delta - and test_session_control.py's _fresh_create_budget fixture is already on main at line 58 with identical semantics. What is left of a 738-add PR is about 32 lines, and one of #6910's remaining pieces is not even harvestable: its single combined ACP_BACKENDS_CONFIG_OPTION_TUNING (pr/6910:acp_backends.py:362) is the design GPT 5.6 blocked on #6910's own head and that main resolved the other way, shipping ACP_BACKENDS_MODEL_VIA_CONFIG_OPTION (:355) and ACP_BACKENDS_EFFORT_VIA_CONFIG_OPTION (:364). Merging #6910 now would revert a landed decision, not add a capability. The contributor's work is not being deleted by this closure - his commit is on main; the closing note should say so.

Why this one and not the other

#7813 is your own commit, rebased and landed.

Carry this over first

This closure is about redundancy, and these items are the exception: they are not on main and not in the surviving PR, so they need a home before the topic is finished. Please don't let them go with the branch.

Two hunks, both from pr/6910 (c593891), neither Codex-related and neither carried by #7813's merge; best landed as one or two small follow-up PRs. (1) test/test_design_tweak_relay_paths.py :: TestRelayWsHandshakeSanitization.test_set_cookie_stripped_from_101 (+17/-30) - replaces the sleep-ordered daemon feeder thread with a pre-written socketpair plus up_local.sendall(handshake_response) / up_local.shutdown(socket.SHUT_WR), and deletes the dead with patch(...): pass block and the two orphan socketpairs (up_a/up_b, up_c/up_d). Makes the 'a 101 upgrade strips Set-Cookie so a dev server cannot overwrite kirocrew_session' assertion deterministic; all four assertions preserved. Still unfixed on main (only commit touching the file is e6dfd22). (2) src/kiro_crew/platform/defaults.py :: DefaultProviderRegistry (class docstring + register_acp_backends comment, +15/-9) - corrects text that main still asserts and that #7813's merge made false: the baseline no longer equals ACP_BACKENDS_KNOWN (ACP_BACKEND_CODEX is known at acp_backends.py:59 and absent from BASELINE_SELECTABLE_BACKENDS at :86), so register_selectable_backend now does accept an id that is not already selectable, and calling it is how an edition activates one. Do NOT harvest #6910's combined ACP_BACKENDS_CONFIG_OPTION_TUNING (acp_backends.py:362) or the test_harness_parity.py test_tuning_channels_are_opt_in / harness-parity.md H6 wording built on it - main deliberately ships the split pair, so those would revert a landed decision. Nothing to harvest from test_session_control.py (already on main, line 58), AgentBackendTab.tsx or AgentBackendTab.test.tsx (landed; prose-only delta).

Current state

#7813 merged as f51e659 (verified ancestor of origin/main 1a765b8) and PARTIALLY covers #6910 - substantively it covers all of it, since #7813's first commit is a rebase of #6910's only commit, so the entire Codex seam, the six providers/acp.py negation-to-membership conversions, the AgentBackendTab.tsx candidates fix and all seven new harness-parity tests are already on main. The remainder is two hunks #7813's rebase dropped: the test_design_tweak_relay_paths.py::test_set_cookie_stripped_from_101 deflake and the platform/defaults.py DefaultProviderRegistry comment correction (which #7813's own merge is what made necessary). #6910's combined ACP_BACKENDS_CONFIG_OPTION_TUNING is not a remainder but a superseded design: main carries the split ACP_BACKENDS_MODEL_VIA_CONFIG_OPTION + ACP_BACKENDS_EFFORT_VIA_CONFIG_OPTION, so #6910 cannot land that half at all.


From a repository-wide duplicate/overlap audit of every pull request open against main (2026-09-02, 330 PRs, one reviewer per PR). Each PR was read as its full merge-base diff plus its description and every comment and review, then compared against each candidate PR's own diff and against origin/main at 1a765b88ceb7. Findings that implied a closure were re-adjudicated independently, including an adversarial pass whose only job was to refute them; the reasoning above is what survived. If it is wrong, reopening costs nothing — please say so, and treat the reasoning rather than the outcome as the thing to correct.

@bolichen97 bolichen97 closed this Sep 2, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Sep 2, 2026
@vamgan
vamgan deleted the feat/codex-backend-identifier branch September 2, 2026 22:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) merge conflict Branch has merge conflicts with its base — author must resolve before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants