feat: skip the session reset when kiro-cli hot-reloads MCP config - #8319
Conversation
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Sound gate, but its premise — "the reset only made kiro-cli re-read a file" — is false for MCP-gateway sessions, whose servers are session/new-injected and outrank that file. Watch
Suggestions
[DESIGN-REVIEWED] 2156c7c |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of First-Principles-Verdict: PASS Every item traces to a probed kiro-cli behavior or a documented invariant; the one riding-along change (Slack parser) is itself a deletion. What this change shipsIntent: stop killing every running conversation when the user installs or toggles an MCP server from the dashboard — a FIX (removal of an over-broad reset).
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 2156c7c |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe candidate claims the re-enable path is asymmetric with the disable path: disable marks both Verifying the three-part bar:
No grounded defect in the changed lines survives, and I found nothing else meeting the bar. No findings. [OPUS-REVIEWED] 2156c7c Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
0ed2d4d to
b18ae03
Compare
b18ae03 to
1fadeaf
Compare
kiro-cli 2.10.0+ watches ~/.kiro/agents and mcp.json and reconciles a running session against an edit: only the changed servers restart, the conversation is kept, and the change applies at the next turn boundary. POST /api/mcp/sync still reset every session after each MCP write, a reset whose only purpose was to make kiro-cli re-read a file it already watches. Gate the reset on the processes actually running: every registered session and every warm-pool process must be on a backend in the new opt-in set ACP_BACKENDS_MCP_CONFIG_HOT_RELOAD (kiro-cli only) and must have reported an agentInfo.version at its own initialize handshake at or above the 2.10.0 floor. The handshake version is what the process runs; after an in-place kiro-cli upgrade the binary on disk is newer than every process spawned before it, so it is never consulted. The gate fails closed: one unhandshaked, older, or foreign-harness process resets everything as before, and with nothing running there is nothing to reset. POST /api/sessions/restart stays the manual path. Write disabled: true onto the agent-file entry when a server is disabled from the dashboard, and lift it on enable. Under the live reconcile a @server ref removed from tools leaves a still-running server's tools mounted, so dropping the ref was never a disable; the entry marker is what stops the process, and includeMcpJson is pinned false so the kiro-global disabled flag never reaches kiro-cli. Keep a disabled row in list_servers for a server disabled that way, built from the agent entry's full spec, so the user has something to re-enable from. Both transports retain agentInfo.version (AcpClient, AcpRuntime, through AcpSessionHandle/AcpSessionProvider to AcpProvider.agent_version), and the slack gateway's kiro-cli floor check reuses the one version parser.
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
1fadeaf to
2156c7c
Compare
bolichen97
left a comment
There was a problem hiding this comment.
Approved after deep review (feat: escalation — MCP hot-reload gate).
Security checklist: no auth/authz, trust-boundary, sandbox, or secret-handling changes. The one new input surface is agentInfo.version off the local ACP initialize response, type-guarded in agent_version_from_init and parsed by an anchored regex in parse_kiro_cli_version.
The new gate is a skip, so what matters is that it fails closed — and it does on every path: non-member backend, missing handshake version, below the 2.21.0 floor, any exception in _mcp_hot_reload_active, and a non-literal True all fall back to the reset that was always correct.
SessionPool.warm_providers() is synchronous drain-and-requeue mirroring _pool_pids, so it is atomic against the event loop: no lost provider, FIFO preserved. The disabled: true marker on the agent entry is the load-bearing half — without it the skip would silently turn a disable into a no-op for running sessions.
Non-blocking follow-ups: (1) re-enable lifts disabled only from the alias entry while disable marks alias + legacy slash key; (2) skip is inferred not confirmed — _kiro.dev/mcp/server_initialized hardening already named in the PR docs.
All 5 AI reviewers and 29 CI checks green on 2156c7c.
Verdict: SAFE_TO_MERGE.
Summary
Installing, enabling or disabling an MCP server from the dashboard no longer kills every running session when those sessions reconcile their config live.
kiro-cli 2.10.0+ ships "Config Hot-Reload": a file watcher on
~/.kiro/agentsandmcp.jsonrestarts only the changed MCP servers in a running session, keeps the conversation, and applies the edit at the next turn boundary. Kiro Crew'sPOST /api/mcp/syncstill unconditionally called_reset_all_sessionsafter every MCP config write — a reset whose only purpose was to make kiro-cli re-read a file it already watches.What changes
kiro_crew/mcp_hot_reload.py(new) — the gate, keyed to the processes actually running. Every live provider — registered sessions plus the warm pool the reset would drain — must declareLLMProvider.mcp_config_hot_reloadas a literalTrue(new ABC property, defaultFalse, harness-parity H14).AcpProvideranswers it from the opt-in setACP_BACKENDS_MCP_CONFIG_HOT_RELOAD(kiro-cli only) and theagentInfo.version≥ 2.21.0 its own process reported atinitialize— the floor is the release the reconcile semantics were observed on, not 2.10.0 where the watcher first shipped; a release in between keeps the always-correct reset until verified, and lowering the floor later is one line. The handshake version is what the process runs; after an in-place kiro-cli upgrade the binary on disk is newer than every process spawned before it, so the file is never consulted. Fails closed: one unhandshaked, older, foreign-harness, or non-declaring process resets everything as before; with nothing running there is nothing to reset.agentInfo.version(AcpClient.agent_version,AcpRuntime.agent_version, surfaced throughAcpSessionHandle→AcpSessionProvider→AcpProvider.agent_version; shared parseragent_version_from_initinacp/_dispatch.py).SessionManager.warm_providers()snapshots the pool without consuming it.POST /api/mcp/syncconsults the gate; when it holds, no session is touched andsessions_reset: 0is the observable outcome.POST /api/sessions/restartstays the unconditional manual path.disabled: trueonto the agent-file entry (_sync_mcp_to_agent, single and batch); enable lifts it. Under the live reconcile a@serverref removed fromtoolsleaves a still-running server's tools mounted, so dropping the ref was never going to be a disable —disabled: trueon the entry is what stops the process. Thedisabledin the kiro-global file cannot stand in:includeMcpJsonis pinned false, so kiro-cli never reads it. Side benefit on the cold path: a disabled server is no longer spawned only to sit unmounted.list_serverskeeps a row (marked disabled, built from the agent entry's full spec, sourceagent) for a server disabled that way — otherwise the row vanished the moment the user disabled it, leaving nothing to re-enable from.acp_backends.py(re-exported fromacp/types.py), registered intest_harness_parity.py(subset check +test_mcp_config_hot_reload_is_opt_in) and the H6 row.slack/gateway.pykiro-cli floor check reusesparse_kiro_cli_versioninstead of its own split.docs/architecture/mcp.mdgains a "Live reconcile" section and an updated troubleshooting entry;acp-client.md,harness-parity.md,overview.mdupdated.How the kiro-cli semantics were established
Probed against kiro-cli 2.21.0 over ACP with an isolated
HOME(agent file edited on disk while one session stayed live, no restart):mcpServersentry_kiro.dev/mcp/server_initializedin ~0.5 s, process spawneddisabled: trueon the entrydisabledToolson a running server@serverref totools(server already running)/toolsbefore a turn still shows the old set)@serverref only (server keeps running)disabled: true/mcp addslash commandinitializeon 2.21.0 returnsagentInfo: {name: "Kiro CLI Agent", version: "2.21.0"}— the field the gate reads.Not ACP-standard: the reconcile is a kiro-cli feature (the
_kiro.dev/*extension namespace), which is why it is gated by positive harness membership plus a per-process version floor rather than assumed.Verification
isort,flake8,mypy(CI-parity pin 1.14.1) on the changed modules, black gate, subprocess-encoding gate, harness-parity gate, brand gate, docs-lint — all exit 0. Tests are left to CI by host policy (no local test runs on this dev host).test/test_mcp_hot_reload.py(parser,agent_version_from_init, pure gate, per-provider gate, live-sessions gate incl. the version-skew case); new cases intest_mcp_sync_agent.py(gate both arms over a fake session registry, disable marker single/batch, enable lifts it, fail-closed) andtest_mcp_discovery.py(disabled row survives);test_harness_parity.pyextended.Review dispositions
Round 1 — Design (version skew) and First Principles (unused response field, parser consolidation, floor provenance): addressed in the second push. Round 2 — GPT (H14: capability probed off
AcpProviderinstead of declared onLLMProvider; function-local imports; stale docstring): addressed in the third push. Round 3 — Design (floor granted to unverified releases): floor raised to the probed 2.21.0 in the fourth push; Design (inferred vs observed reconcile): documented as a trade-off, follow-up named below. Per-lane disposition comments on the PR.Follow-ups (not in this PR)
_kiro.dev/mcp/server_initializednotification is the observable signal and is not yet consulted, so a watcher that fails at runtime reads as success (recovery today: the troubleshooting entry +POST /api/sessions/restart).PUT /api/agent/configand the computer-use toggle still reset sessions unconditionally; both could take the same gate.