Skip to content

feat(acp): register opencode ACP backend — known, gated until enforceable routing exists - #9013

Closed
atomsbaza wants to merge 1 commit into
kirodotdev:mainfrom
atomsbaza:feat/opencode-acp-backend
Closed

feat(acp): register opencode ACP backend — known, gated until enforceable routing exists#9013
atomsbaza wants to merge 1 commit into
kirodotdev:mainfrom
atomsbaza:feat/opencode-acp-backend

Conversation

@atomsbaza

@atomsbaza atomsbaza commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Users who run opencode want to point agent sessions at it from Kiro Crew. opencode ships an ACP-compatible stdio agent (opencode acp), and the backend registry is data-driven, so the surface change is small — but it must not ship selectable until Crew can gate its tool calls.

Why it matters

Two user-facing values: the groundwork (registration, install probe, spawn resolution, docs) makes opencode a first-class known backend, and the security analysis in this PR documents exactly why it is not selectable yet — closing the gap a selectable-but-ungated backend would open.

What changed (motivation → approach → change)

Goal: add opencode following the codex template, gated by Crew's security posture rather than around it.

  • acp_backends.py: ACP_BACKEND_OPENCODE registered in ACP_BACKENDS_KNOWN and deliberately excluded from BASELINE_SELECTABLE_BACKENDS. A live probe of opencode acp v1.18.x session/new shows the adapter advertises exactly one config option (model, select) and no permission mode, so there is nothing to enforce through session/set_config_option; routing_for("opencode") answers UNVERIFIED honestly. Config naming opencode falls back to the default backend with the standard not-selectable warning (same path as an unknown id on a stock build) — verified from resolve_selected_backend through every consumer (config load, member backend, boot re-check, dashboard schema, _bg runtime). Joining any capability set later is an explicit, evidenced edit.
  • acp/client.py: spawn arm (OPENCODE_ACP_BIN env → mise → PATH), _is_opencode arms for prompt/resume, and capability fail-fast (steer/compact/set-model). No per-session MCP seam: opencode joins no SESSION_MCP_ARRAY, and the claude/codex splices are untouched.
  • agent_sdk/backend_install.py: _probe_opencode() reports the binary plus the opencode auth login prerequisite.
  • Docs: agent-host-contract and acp-client specs state the gated status, the probe evidence, and the fallback behavior.
  • The predecessor of this branch was briefly selectable chat-only; review (and a live prompt round) showed opencode's self-approved built-in tools would bypass Crew's PreToolUse gate entirely, which is what the exclusion now prevents.

Tests

New test/test_acp_opencode_backend.py mirrors the codex twins: registry snapshot (known-but-not-selectable), spawn-resolution order (Windows-safe fixtures), probe state, capability fail-fast, serialization (incl. a source-level lock that no opencode MCP splice exists), member-dispatch exclusion, and a regression test that a config naming opencode degrades to the default backend with the warning. Known-keyed parity gates (agent-host-contract buckets, frame replay) gained honest opencode rows/fixtures.

Local validation: targeted suites across the touched files all green (298 checks across 10 files); mypy + flake8 + black clean on touched files.

Manual verification

Live E2E against a real gateway booted from this branch (isolated KIROCREW_HOME, port 5588), during the development round: the Developer → Agent Backend page probed and listed opencode (Experimental), selecting it persisted, and a real chat prompt was answered through the opencode acp adapter. That round is what surfaced the security gap (opencode's built-in shell tools ran outside Crew's gate), which this PR's gating now closes — on this head, opencode no longer appears as selectable and the fallback path is test-locked.

Screenshots / video

Captured during the development round (before the gating decision), as evidence the adapter works end-to-end:

Agent Backend page showing opencode selected

Chat prompt answered over the opencode backend

Related Issues

N/A — no tracking issue yet.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@atomsbaza
atomsbaza requested a review from a team as a code owner September 6, 2026 11:46
@atomsbaza
atomsbaza requested a review from CrysisDeu September 6, 2026 11:46
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 6, 2026
@atomsbaza
atomsbaza force-pushed the feat/opencode-acp-backend branch from 82114e6 to 041d037 Compare September 6, 2026 12:27
@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 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

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

Review details

No findings.
[GPT-REVIEWED] 3b1e8e6

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — 🟡 CONCERNS

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

Design-Verdict: CONCERNS

Sound staged landing, but the doc's claimed session-time refusal doesn't exist — registry exclusion is the only control keeping opencode's ungated tools out.

Watch

  • The gating story overstates the mechanism. The new agent-host-contract text says opencode stays safe "because Crew refuses to start a session on a backend whose tool calls it cannot gate … see enforce_runtime_routing" — but enforce_runtime_routing returns unchanged for any routing outside ENFORCED_ROUTINGS, and opencode's UNVERIFIED is outside it, so no session-time refusal exists. Meanwhile adding opencode to ACP_BACKENDS_KNOWN makes register_selectable_backend("opencode") newly succeed (it previously rejected the id), and platform/defaults.py explicitly frames edition registration as available. Net: one seam call re-opens the exact PreToolUse bypass the dev round demonstrated, with no second control and a spec that claims one. Fix the doc sentence, and consider a real backstop (below).
  • temp-screenshots/opencode-e2e/*.png — dev-round binaries committed to the repo root, permanently in git history, referenced from the fork branch. Drop them; attach images to the PR description instead.
  • Collateral damage in agent-host-contract.md tables: the widened tables' delimiter rows have 5 cells for 6 columns, and two untouched 2-column tables ("Seam status today", the getattr-census) gained a stray --- | delimiter cell — GFM will stop rendering these as tables in the authoritative spec.

Suggestions

  • Add a fail-fast in AcpClient.__init__/spawn refusing ACP_BACKEND_OPENCODE while its routing is UNVERIFIED — same pattern as the steer/compact/set_model refusals — so an edition registration cannot silently spawn an ungated harness.

[DESIGN-REVIEWED] 3b1e8e6

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

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

Premise-level review of 3b1e8e615fdbcee851161ec036952896caa78fc7 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 is in. The verdict below is grounded in these counted facts: AcpClient has exactly 2 production construction sites, neither of which can pass "opencode" (one takes only resolve_selected_backend output, one passes no backend), so the entire client-side spawn machinery is unreachable; a config naming "opencode" already degraded identically before this PR; and the steer() guard changes behavior on a caller (continuation.py:493) that gates only on hasattr.

First-Principles-Verdict: CONCERNS

The stated job — run sessions on opencode — is deliberately undelivered; the code half stages an unreachable backend for a selectability decision this PR itself defers.

What this change ships

Intent: let operators who run opencode point Kiro Crew agent sessions at it — an ADDITION, and the diff deliberately does not deliver that job (declared in the title).

  1. Dashboard backend panel gains an opencode row with an install-and-login remedy — for a backend nobody can select
  2. opencode becomes a KNOWN id (label, mirror answer, policy id, probe) — justified once KNOWN, forced by KNOWN-keyed parity gates
  3. Spawn machinery in acp/client.py (resolver, OPENCODE_ACP_BIN, protocol literal, resume shape) — zero consumers
  4. Config naming "opencode" degrades to default with a warning — pre-existing behavior, now test-locked
  5. Exclusion from the selectable baseline and every capability set, pinned — justified: records the live gate-bypass finding
  6. steer() refuses in-method on any non-STEER backend — rides along; changes claude/codex behavior
  7. Model switch raises / startup pin withheld on a no-channel backend — zero consumers (only opencode reaches it)
  8. Agent-host-contract fifth column (mostly "unmeasured"/"moot") plus gated-status section — justified
  9. Separator rows malformed in eight rewritten tables, plus two tables edited that gained no column — undeclared
  10. 429-line codex-twin test file and synthesized frame fixtures — justified as the pin

Watch

  • The client.py machinery has 0 production consumers: AcpClient( is constructed at exactly 2 sites (grepped AcpClient\( in src/), providers/acp.py:337 (backend already passed through resolve_selected_backend, which degrades "opencode") and knowledge/llm_pool.py:309 (no backend arg). The spawn arm, PROTOCOL_VERSION_OPENCODE, resume shape, and set_model/startup branches exist only for the future edit the description defers: "Joining any capability set later is an explicit, evidenced edit." Inherited, not derived.
  • Item 4 is not new behavior: base resolve_selected_backend (acp_backends.py:276-295) already degrades any unselectable id with the same warning — the code half's zero option is observably nothing; only the docs and pins record anything.
  • The steer() guard is declared as an opencode fail-fast but is backend-generic: subagent_manager/continuation.py:493 gates only on hasattr(provider, "steer"), so claude/codex subagent steers change from a wire method-not-found to a clean False. A fix, but on selectable backends, riding in this feat.
  • Eight rewritten tables in agent-host-contract.md have 6-column headers over 5-cell separators (|---|---|---|---| --- |), which breaks GFM rendering; the correctness lane owns the fix, but the edits to "Seam status today" and the seam-count table (2 columns, third separator cell added, no new column) are undeclared riders.

Subtractions

  • Defer the whole acp/client.py opencode arm — _resolve_opencode_acp_bin, OPENCODE_ACP_BIN, PROTOCOL_VERSION_OPENCODE, the _is_opencode spawn/resume/model branches — to the PR that makes opencode selectable (0 production consumers, counted above).
  • With that gone, defer the KNOWN registration and probe row too: the fallback already behaves identically (resolve_selected_backend), and the remedy line (curl … | bash && opencode auth login) invites installing a backend the panel will not offer. The docs alone carry the recorded gating decision.
  • Revert the separator-row edits to the two tables that gained no column (agent-host-contract.md "Seam status today" and the Kind/Count table).

[FIRST-PRINCIPLES-REVIEWED] 3b1e8e6

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

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

Review details

I've verified the reachable changed paths:

  • steer(): the new if not self.supports_steer: return False guards on ACP_BACKEND_STEER = {KIRO, KAS}. Both remain steer-capable (guard passes); only never-capable AcpClient-path backends (claude/codex) short-circuit, which is the intended fast-fail. No regression.
  • set_model gate (not in MODEL_VIA_CONFIG_OPTION | {KIRO}): AcpClient.set_model is only reached by non-runtime backends (claude/codex, both in the config-option set). KIRO/KAS are ACP_BACKENDS_ACP_RUNTIME and their provider.client is swapped to an AcpSessionProvider (acp.py:994), whose set_model never touches AcpClient.set_model. No regression.
  • _apply_startup_model restructure: the new else withhold branch is reachable only by a non-runtime, non-config, non-kiro backend — i.e. only opencode. KIRO retains its set_model via elif self._is_kiro. No regression.

The sole candidate requires an out-of-repo edition to call register_selectable_backend("opencode") and flip selectability — a hypothetical "if a caller were to" that does not occur on a stock build (resolve_selected_backend degrades a persisted "opencode" to kiro, and the dashboard allowlist derives from selectable_backends(), which excludes it). Its (a) concrete-input premise fails; the discovery pass itself rated it low confidence. It dies under falsification.

No findings.

[OPUS-REVIEWED] 3b1e8e6

@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 merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 6, 2026
@atomsbaza
atomsbaza force-pushed the feat/opencode-acp-backend branch from 041d037 to b640e3d Compare September 6, 2026 12:55
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 6, 2026
Register `opencode` in the baseline selectable backend registry and add
its spawn arm (stdio JSON-RPC via `opencode acp`), install probe, session
lifecycle with capability gating, and content serialization, mirroring
the codex backend template.

v1 scope: chat-only (no per-session MCP array), no steer/compaction, no
model-via-config; unsupported capability calls fail fast with explicit
errors so existing backends are unaffected.

Adds twin tests (test_acp_opencode_backend.py, 29 tests) and updates the
agent-host-contract and acp-client docs.
@atomsbaza
atomsbaza force-pushed the feat/opencode-acp-backend branch from b640e3d to 3b1e8e6 Compare September 7, 2026 18:34
@atomsbaza atomsbaza changed the title feat(acp): add opencode as a selectable ACP backend feat(acp): register opencode ACP backend — known, gated until enforceable routing exists Sep 7, 2026
@github-actions github-actions Bot added merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 7, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

@atomsbaza Thanks for the detailed writeup. Flagging overlap with other open PRs before this lands.

#9055 (@Premshay) edits the same method body, AcpClient._apply_startup_model in src/kiro_crew/acp/client.py. It appends one attribution line (_resolved_model_id = _last_substitution_model or _model); this PR replaces that method's trailing else with elif self._is_kiro plus a new withhold branch. Different goals, no behavioural conflict, so both can land, but whichever lands second must re-apply by hand, and both also touch the Context-window backfill. paragraph in docs/system-specs/modules/acp-client.md. #9055 is 3 files; landing it first and rebasing this PR on top is the cheaper order.

The harder blocker is elsewhere. #6777 and #6307 also define ACP_BACKEND_OPENCODE in src/kiro_crew/acp_backends.py, so only one of the three can land. #6777 adds opencode TO BASELINE_SELECTABLE_BACKENDS along with the dashboard row, model catalog and MCP projection this PR omits, which is the opposite product decision from the NOT_SHIPPED_SELECTABLE pin in test/test_agent_backend_editable.py. #6307's src/kiro_crew/acp/opencode.py routes opencode tool calls back to Crew by seeding permission: "ask" in the session work_dir's opencode.json, which contradicts this PR's "no enforceable permission boundary" premise. Please reconcile the three designs before any of them merges.

Two mechanical items either way: the branch is 34 commits behind and needs a rebase onto #9281's set_model / _apply_startup_model rewrite, and it commits about 228 KB of dev-round PNGs under temp-screenshots/.

Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong.

@atomsbaza

Copy link
Copy Markdown
Contributor Author

Closing in favor of the competing designs — as flagged in the open-PR relationship audit, #6307 and #6777 both define ACP_BACKEND_OPENCODE in acp_backends.py and only one of the three can land. #6307's permission: "ask" routing back to Crew delivers the enforceable permission boundary this PR deliberately deferred, so it is the better path. Happy to help re-apply anything useful from here (e.g. the probe findings on opencode's session/new config surface) if useful. Thanks for the audit!

@atomsbaza atomsbaza closed this Sep 8, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Sep 8, 2026
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