feat(acp): select an ACP interface per crew - #6390
Conversation
|
👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated. Missing sections:
Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle. |
|
👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated. Missing sections:
Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle. |
98432a8 to
9ab5c3d
Compare
|
Thanks — both template checks were correct, and one of them caught a second problem.
No code change was needed for the template finding itself — it was a description defect, not a diff defect. |
9ab5c3d to
9d8dd8d
Compare
9d8dd8d to
80256b7
Compare
|
CI round on
Apologies for the mangled earlier comment — a shell quoting error on my end, superseded by this one. |
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of I have enough to assess. Confirmed: the external harness spawn goes through Design assessment: real problem, right shape (interface-vs-backend split), additive/reversible, opt-in with kiro-cli unchanged, and unusually thorough security reasoning already in the diff (INTERNAL_SANDBOX exclusion, API-key strip, realpath, pool-mismatch bypass). One design-level interaction isn't reconciled anywhere in the PR: this places an exec'd command into agent-writable Design-Verdict: CONCERNS Sound, well-reasoned per-crew harness feature; one unaddressed boundary — an exec'd command now lives in agent-writable Watch
[DESIGN-REVIEWED] c08364a |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of All verification checks against the base tree are done. Here is the review. First-Principles-Verdict: CONCERNS The feature earns its place, but three shipped surfaces document a global What this change shipsIntent: let an operator run some crews on a different ACP harness while the rest stay on kiro-cli — an ADDITION.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] c08364a |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed BLOCKING -- src/kiro_crew/acp/client.py:2813 -- external harnesses can bypass mandatory tool governance BLOCKING -- src/kiro_crew/config/loader.py:4790 -- interface routing silently selects the wrong harness BLOCKING -- src/kiro_crew/config/loader.py:8971 -- adapter selection changes the Kiro construction path BLOCKING -- src/kiro_crew/config/loader.py:3531 -- interface is omitted from dispatch-binding identity (origin: validation) BLOCKING -- src/kiro_crew/config/loader.py:4838 -- executable canonicalization blocks the event loop (origin: validation) FINDING -- src/kiro_crew/config/loader.py:3537 -- |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo blocking findings — two advisory items on the effort-control paths. FINDING — src/kiro_crew/providers/acp.py:1147 — FINDING — src/kiro_crew/config/loader.py:840 — [OPUS-REVIEWED] c08364a |
5512c28 to
9b6f69d
Compare
Dispositions — GPT 5.6 ReviewAll fixes are in
Correct, and the sharpest finding in the set.
Correct: I declared the field and never parsed it, so a configured value was ignored and silently overwritten on the next
Correct on the warm-pool half, and it is the worst failure mode in the diff because nothing fails: On the subagent half: the backend id and its argv already travel together through
The mechanism is real but it is not introduced by this branch, and the proposed fix ("revert the external-backend execution branch") deletes the feature. Tool gating lives on the What is enforced regardless, and is the reason this is a bounded grant rather than an open door:
Cannot be done: it is a genuine import cycle, and the file already documents it. |
Dispositions — First Principles ReviewThe BLOCK was correct and it caught the most consequential defect in the change. Fixes are in
Correct, and worth stating plainly: I checked for This is the second instance of the same class in this PR — earlier, a hand-written verification script had diverged from the committed tests, so "all checks pass" was verifying code CI never ran. Both had the same shape: evidence that looked like test evidence but was not connected to the collector. I now run the committed suite under
Right on both counts, including the motive. Removed from the dataclass and the parse.
The docstring citing the predicate it duplicates is the tell. Deleted; the external spawn branch now calls
Agreed, and it converged with a GPT blocker from the other direction: the tier was also never parsed, so a configured value was discarded and overwritten on save. Removing it fixes the bug and the generalization in one move; resolution is now three tiers.
Items 4 and 9 are the two subtractions above, both taken. On item 10 (
Confirmed, and it is a live hole rather than a tidiness issue: |
Dispositions — Design ReviewCONCERNS is advisory and this check passed, so nothing in the pipeline would have forced these to be answered — both are legitimate and one is still open.
Correct, and independently found by the First Principles lane. Moved to
Legitimate, and I verified the mandate rather than taking it on trust: AGENTS.md:30 and :205 both require the spec to change in the same commit as what it documents, and Not yet done, so I am not claiming it as fixed. The three files and what each needs:
Sound caution and it is how the baseline was produced: |
|
Follow-up for the deferred item in the First Principles disposition: the 12 uncollected files in root |
Every crew shares one globally-configured ACP backend today: the provider factory reads `agent.acp_backend` once, so a gateway cannot run one crew on kiro-cli and another on a different harness. Adding a harness also meant editing a closed enum, which is the wrong shape for something an operator supplies. Separate the two ideas. An *interface* is what an operator selects — a name, a command, an env — and a *backend* stays what the code speaks, a closed set of dialects. Interfaces are declared in a new `acp_interfaces` section and bound per crew with `agents.<crew>.acp_interface`; `kiro-cli` and `kas` remain built in and need no configuration, so an install that touches none of this is unchanged. - add ACP_BACKEND_EXTERNAL for an operator-declared harness, plus the interface vocabulary and the built-in name map - add AcpInterfaceConfig + resolve_acp_interface (crew, then global acp_interface, then the pre-existing acp_backend, then kiro-cli) - resolve the interface per factory call rather than at factory-build time, so two crews in one gateway can differ - thread the launch argv through AcpProvider into AcpClient._spawn, and inherit it alongside the backend id for subagents - accept acp_interface on the crew create/update API Security posture is opt-out by construction. ACP_BACKENDS_INTERNAL_SANDBOX is the membership that makes wrap_argv SKIP Crew's seatbelt in deference to a harness's own; an external harness has demonstrated none, so it is excluded and keeps Crew's confinement. Kiro's API key is already stripped for non-kiro backends. Two overlay writers and the live effort push were gated on `not is_claude_backend`, which would have handed an external harness kiro-cli's cli.json and a `/effort` slash command — re-stated as positive kiro-family membership per harness-parity H5, behaviour-identical for existing backends. A missing command is refused at load, and at provider construction, rather than surfacing as a dead session on a crew's first message. Naming a built-in in `acp_interfaces` is refused: a redefined kiro-cli would silently move every unconfigured crew onto an operator's command. An unknown interface name degrades to kiro-cli with a warning, so a typo in one crew's binding cannot take the install down. Readiness still comes from kiro-cli's own `--version` / `whoami` probes and is not yet per-interface; documented as a known limitation. Verified end to end against a local ACP adapter over LM Studio: the real AcpClient completed the handshake with backend='external', returned a model reply, and did so with Crew's sandbox engaged.
9b6f69d to
c08364a
Compare
|
ACP coordination note from the actual full diffs of #5349, #6307, and this PR: This PR's per-crew Please rebase/integrate the external interface as a descriptor/admission implementation under #6307's registry and tool gate, rather than retaining a parallel closed backend-selection model. Preserve per-crew selection, absolute-command validation, env handling, and session inheritance through one shared spawn/validation path. |
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been stale with failing CI. I reviewed the blockers but they require your input:
When you've addressed these, the pipeline will re-assess on its next cycle. |
Pull request was closed
|
Other pull requests are a better fit for the capability this was trying to add. |
Problem / Motivation
Every crew in a gateway is locked to the same ACP backend.
create_provider_factoryreads
agent.acp_backendonce and closes over it, so there is no way to run onecrew on kiro-cli and another on a different harness — a local model behind an ACP
adapter, or a second vendor's agent binary. Adding a harness at all meant editing
ACP_BACKENDS_SELECTABLE, a closed enum in the product's own source, which is thewrong shape for something an operator supplies rather than something Kiro Crew ships.
Why it matters
An operator who wants part of their fleet on a local model today has exactly one
option: point
KIROCREW_KIRO_BINat a substitute binary, which replaces thebackend for every session in the gateway. That is all-or-nothing — it takes
the operator's own interactive chat down onto the substitute along with the
background work they actually wanted to move.
The mixed case is the useful one and is currently unreachable: an interactive
crew on kiro-cli during the day, and unattended crews on a cheaper or local
harness, in one gateway at the same time. It also means anyone evaluating a new
ACP harness has to patch the product to try it.
What changed (motivation → approach → change)
Goal: let a crew choose its harness, without making kiro-cli users configure
anything and without turning the backend list into operator-editable code.
Approach: separate the two ideas
acp_backendwas carrying at once.Open-ended, because the set of harnesses someone might run is not knowable.
the product must actually understand.
The alternative considered was extending
ACP_BACKENDS_SELECTABLEwith one idper harness. Rejected: every new harness would then need a product change, and
the id would have to carry launch details the enum has nowhere to put. A single
ACP_BACKEND_EXTERNALdialect plus named interfaces in config keeps the dialectset closed while leaving the harness list open.
What was built:
ACP_BACKEND_EXTERNALfor an operator-declared harness, plus the interfacevocabulary and the built-in name map (
acp/types.py).AcpInterfaceConfigand a newacp_interfacesconfig section, andresolve_acp_interface()implementing the resolution order: the crew'sacp_interface→ the globalagent.acp_interface→ the pre-existingagent.acp_backend→kiro-cli.factory-build time. That is the actual fix: a value closed over once binds
every crew together, which is the bug.
AcpProviderintoAcpClient._spawn, and isinherited alongside the backend id for subagents.
acp_interfaceaccepted on the crew create/update API.{ "acp_interfaces": { "lmstudio": { "command": "/Users/you/bin/acp-lmstudio", "args": ["acp", "--agent", "{agent}"], "env": { "LMSTUDIO_MODEL": "qwen3-coder" } } }, "agents": { "day": { "kiro_agent": "kirocrew" }, "night": { "kiro_agent": "kirocrew", "acp_interface": "lmstudio" } } }kiro-cliandkasare built in, need no configuration, and cannot beredefined, so an install that touches none of this behaves exactly as before.
Security posture is opt-out by construction. The capability sets in
acp/types.pyare opt-in, and an external harness has demonstrated nothing:ACP_BACKENDS_INTERNAL_SANDBOXexcludes it deliberately. That membershipis what makes
wrap_argvskip Kiro Crew's seatbelt in deference to aharness's own internal sandbox. An operator-supplied command has no such
sandbox, so it is precisely the case that must keep Crew's. The comment says
so explicitly, because the tempting "fix" for a harness that dislikes the
sandbox is to add it to that set.
_resolve_spawn_env(kiro_api_key=self._is_kiro).One latent bug found and fixed on the way. Three sites gated on
not is_claude_backend, so any backend that is not claude inherited kiro-cli'sown machinery by default:
_apply_effort_overlayand_apply_tool_search_overlay(both write kiro-cli'scli.json) andchange_effort(sends the/effortslash command). An external harness wouldhave been handed a settings file it never reads and a slash command it would be
right to reject. Re-stated as positive kiro-family membership per the repo's own
harness-parity H5 convention — behaviour-identical for kiro, kas and claude.
Failure modes are deliberately early, because each alternative surfaces as a
dead session on a crew's first message instead:
commandkiro-cliValueErrorat provider constructionAcpErrorat spawn — checked, never executed, so operator-supplied code does not run before the sandbox wraps itTests
tests/test_acp_interfaces.py, 24 tests:commandis dropped; a built-in namecannot be shadowed; non-string
args/envcollapse rather than crashing theload (config.json is hand-editable and agent-writable).
agent.acp_backend = "kas"still resolves to KAS so an existing install is notsilently re-pointed; two crews in one config resolving to different backends;
{agent}/{model}substitution; an unknown placeholder passing throughinstead of failing the launch; an unknown interface name degrading to kiro-cli;
config round-trip preserving the binding.
ACP_BACKEND_EXTERNALis in none of them.INTERNAL_SANDBOXis the one thatmatters: this test is what fails if someone later adds external to it and
unconfines every external harness.
launchability check rejecting a non-executable, a missing path, an empty file
and a directory.
subagent of an external-backed parent does not inherit a backend with nothing
to launch.
Manual verification
Verified end to end against a local ACP adapter over LM Studio, driving the
real
AcpClientrather than a stub — config declares the interface, theresolver produces the argv, the product's own client speaks ACP to it:
Repeated with
sandbox_mode="auto": the session still works and therunning without OS-level confinementwarning is absent, confirming Crew'ssandbox wraps the external harness rather than being skipped.
Caveat on the automated suite: I could not run
pytestin this checkout —.venvis unprovisioned andmake backendwas out of scope for this change — sothe same assertions were executed directly against the source tree (21 checks,
all passing, re-run after the rebase onto
f99b11e04to confirm upstream's neweffort-drop warning survived the merge resolution). Reviewers with a provisioned
venv should run
pytest tests/test_acp_interfaces.py; CI will be the first toexecute the committed module.
Known limitation
Readiness is still computed from kiro-cli's own
--version/whoamiprobes andis not per-interface, so an install with no working kiro-cli reports not-ready
even if every crew runs elsewhere.
KiroPrerequisiteServiceis a process-levelsingleton that probes one binary and gates all session readiness; making it
interface-aware is a larger change, so it is documented in the guide and called
out here rather than half-done.
Documentation
docs/guides/acp-interfaces.md— the two nouns, the config schema, theresolution order, the ACP surface an external harness must implement, and a
security section including the thing operators are most likely to get wrong:
tool gating runs on the
session/request_permissionpath, so a harness thatexecutes tools without asking bypasses the deny engine — not because the floor
failed, but because it was never consulted.
Why no screenshot: backend and config change only; the new
acp_interfacefield renders through the existing reflection-derived settings form with no
layout or component change.
Related Issues
no linked issue: this implements a capability that had no filed request.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)