Repository navigation
Description
Motivation
Iterating on a plugin that is installed and in active use in real hosts (Claude Code, Codex, Cursor) currently requires: agent-bundle build → copy/reinstall artifact per host → restart the host or start a new session so the stdio MCP server respawns. Every cycle kills open MCP connections and loses host-side session state. What we want is the webpack-HMR analog: the host's connection stays open while the plugin's code hot-swaps behind it.
What exists today (verified on agent-bundle dev, cargo-conductor project, 2026-09-01)
agent-bundle devserves the loopback workbench and (withAGENT_BUNDLE_AGENT_API_TOKEN+--agent-api) an authenticated/mcpendpoint — but that endpoint exposes the workbench's control tools (project_status,skills_list,skill_inspect, …), not the developed plugin's own MCP tools, so a host cannot attach to a live dev instance of the plugin.- The dev server does not watch source: editing a file under
src/does not advanceactiveEpoch/projectRevisioninproject_statusoutput (verified by polling before/after an edit). - Host installs consume a built artifact (stdio MCP + copied skills/mcp-apps), so there is no live link back to the project at all.
The epoch model (activeEpoch, epoch-scoped workbench tools) looks like it was built for exactly this — the missing pieces are the file-watch rebuild and a host-facing stable endpoint that swaps epochs without dropping connections.
Proposed design
- Watch mode:
agent-bundle dev --watchrebuilds a new epoch on source change (debounced), keeping the last-good epoch active when the build fails (diagnostics surface in the workbench, never a dead server). - Host dev proxy:
agent-bundle dev --install-host <claude|codex|cursor>installs a thin, stable stdio proxy as the plugin's MCP server in the host. The proxy connects to the dev server and forwards the plugin's MCP surface. On epoch swap the proxy keeps the host pipe open, re-resolves tools against the new epoch, and emitsnotifications/tools/list_changed(the server already advertisestools.listChanged: true) — the host never sees a disconnect. - Hooks: generated hook commands in the host point at the dev proxy too; since hooks are spawned per event they pick up the new epoch naturally on the next event.
- Skills / mcp-apps: re-sync copied assets on epoch swap for hosts that read them from disk (Cursor), or serve them through the proxy where the host supports it.
- Session semantics: in-flight tool calls run to completion against the epoch they started on; new calls bind to the active epoch (same rule webpack uses for module disposal).
Why the proxy indirection
Hosts spawn stdio MCP servers themselves and cache the connection per session; only a process that outlives rebuilds can keep that connection stable. A ~zero-dependency proxy binary whose behavior never changes is the smallest thing a host can hold onto while everything behind it stays fluid.
Environment
agent-bundle dev workbench serving /fast/projects/agent-plugins/cargo-conductor, Node 22.23.1, Linux.
Activity
ScriptedAlchemy commentedon Sep 2, 2026
Decision (2026-09-01): sequenced after #218 stages 2-3 land; not parked.
Merge pull request #339 from ScriptedAlchemy/feat/179-s1-host-proxy
ScriptedAlchemy commentedon Sep 2, 2026
Stage 1 landed — #339 (
27a0d592e8d234e6f16c770b5db92d365d7caba2)The keystone of this epic: a host now holds ONE stable stdio MCP process while
agent-bundle devhot-swaps the generated plugin server behind it.POST /mcp/host/:serverName?target=<t>— stateful host-facing Streamable HTTP MCP endpoint on the foreground server, serving the developed plugin's MCP surface (tools/resources/prompts,listChanged: true) from the active artifact epoch. This is the endpoint this issue found missing ("/mcp exposes the workbench's control tools, not the developed plugin's own MCP tools").notifications/tools/list_changedemitted on the same connection; in-flight calls complete against the epoch they started on (the webpack module-disposal rule from this issue's §5), then the old session drains and closes. Failed builds keep last-good serving.AB8024MCP error ({ code, epochId }), connection invalidated — the AB8018/fix(dev): fail MCP tool calls closed when the pinned epoch vanishes #134 discipline extended to the host surface.agent-bundle dev proxy --root <project> --server <name> [--target <t>] [--url <origin>]— the thin stable stdio proxy hosts spawn (issue §2). Discovers the dev server via the dev-lock sidecar (discoverDevServerUrl, PR fix(dev): include owning server URL in AB5000 #132's publication read back), forwards everything including notifications, fails closed withAB8025when no dev server is running.Evidence:
tests/host-mcp-proxy.test.ts(integration) — real proxy child process, real dev server, real rebuild: list_changed received mid-session, v1 in-flight completes while v2 serves, AB8024 on physical epoch removal, AB8025 on server loss and on cold start.Gates local on
fb25f47aa: build/typecheck/lint (0/0) · unit 2615/2616 (1 pre-existing mcp-probe timing flake, 9/9 isolated) · route-unit 24 · projection 59 · proxy integration 5/5. Pre-existing on clean main, not this diff:cli.test.tsinspect-JSON pin vs the #133 Codex validator diagnostics.Stages: 1 done · 2 (hooks/skills re-sync into installed hosts) in review · 3 (live-host acceptance via the #242/#311 machinery) and 4 (#218 stage-4 dev-epoch contract matrix through the session-injected entry points) queued.
Merge pull request #343 from ScriptedAlchemy/feat/179-s2-host-sync
ScriptedAlchemy commentedon Sep 3, 2026
Stage 2 landed — #343 (
05f53c82dc3dd988c365e64bdadfa4414da17470)Issue §3/§4 (hooks + skills/mcp-apps re-sync):
agent-bundle dev --install-host <claude|codex|cursor>(repeatable) installs the developed plugin as a marked dev variant and re-syncs it into the installed host locations on every successful epoch swap.claude/codexplugin CLIs, Cursor copy); the installed MCP document points at the stable stage-1 proxy —process.execPath+ the framework's own resolved CLI entry +dev proxy --root … --server … --target <host>— and is never rewritten on swap (byte-identical pin).artifact.available: immutable per-epoch generations, atomic top-level symlink/file pointer renames (no torn reads, including the first swap), rollback +AB7202on failure via a new epoch-scopeddev.host.syncevent, generations pruned to {current, previous}. Claude/Codex caches are updated directly — zero host-CLI calls per epoch (pinned by command-runner call count).AB8025).AB8093decoder.Evidence:
tests/dev-host-install.test.ts— realDevCoordinator+EpochStorerebuild propagating skill+hook edits into an isolated Cursor install; binary-gated real Claude and Codex lanes (install via host CLI, re-sync into the host-owned cache, marker advanced); spawn proofs for all three hosts that the exact emitted command runs without PATH assumptions and fails closedAB8025when no dev server runs.pnpm test:host-install16/16 with 0 skips locally.Stages: 1 done (#339) · 2 done · 3 (live-host acceptance) and 4 (#218 stage-4 dev-epoch matrix) dispatched.
ScriptedAlchemy commentedon Sep 3, 2026
Stage 3 landed — #374 (
9fe8a35d9882621e5961ddd3128edbe8980dde23)Live-host acceptance for the stage-1 proxy (#339) and stage-2 re-sync (#343), through the exact installed host layout rather than a hand-built client:
tests/dev-live-host.test.ts(now part ofpnpm test:host-install).HOME)startDevServer({ installHosts: ['cursor'] })installs the dev variant; the test reads~/.cursor/plugins/local/<plugin>/mcp.json, spawns that command over stdio, seesv1:cursor, edits MCP entry + Skill + hook sources, receives exactly onenotifications/tools/list_changedon the same connection, seesv2:cursor; marker advanced, installedSKILL.mdand hook module re-syncedclaude2.1.257, isolatedCLAUDE_CONFIG_DIR)plugincommand count), same v1→v2 cycle through the installed.mcp.jsoncommand; zero additional host-CLI calls across the rebuildcodex-cli0.147.0, isolatedCODEX_HOME)plugins/cache/...installAGENT_BUNDLE_HOST_INSTALL_CLAUDE_SESSION=1)--session-idthen--resume) against the installed tree returnv1:claude-sessionthenv2:claude-sessionwith no reinstall; normal Claude settings/plugin tree byte-unchangedit.skip— Codex exec exposes no inline plugin-loading surface for an isolated dev install; the reason is in the test titleGates on the merged tree: build · typecheck · lint (0/0) ·
pnpm test:host-install19/19 (2 documented skips) · PR CI green (Verify, Examples, Release gates after one unrelatednpm audit signaturesregistry retry).Stages: 1 done (#339) · 2 done (#343) · 3 done · 4 (#218 stage-4 dev-epoch contract gate + Workbench visibility) in review as #385.
feat(dev): gate host-facing epoch adoption on the dev-epoch contract …
ScriptedAlchemy commentedon Sep 3, 2026
Stage 4 landed — #385 (
5fac48048d2188e1478bb8a2a30ebc57d0948a5a) — epic completeThe last design item: a rebuild that would change the source→artifact→dev→installed-host contract is gated before any host-facing surface adopts it (#218 stage 4).
dev.contracts(opt-in) runs the shared contract matrix against each published epoch through an epoch-pinned generated stdio session; only a passing epoch reaches the stage-1 proxy connections and the stage-2 installs, a failing one stays inactive (AB7211, orAB7210for a bad declaration) while the last passing epoch keeps serving, and the Workbench Overview shows Host adoption (host-facing vs published build, failed checks per route) instead of silently applying it. Cold start with a failing initial build seeds the restored last-good epoch through the same gate. Full detail and evidence in the #218 close-out comment.Acceptance against this issue's proposed design
tests/host-mcp-proxy.test.ts"failed builds keep last-good serving"--install-host <claude|codex|cursor>installs a stable stdio proxy; epoch swap keeps the host pipe open, re-resolves against the new epoch, emitsnotifications/tools/list_changed(+ resources/prompts), host never sees a disconnect27a0d592):POST /mcp/host/:server,agent-bundle dev proxy,AB8024/AB8025; stage 2 #343 (05f53c82): installed MCP document pins the proxy command byte-identically across swaps; stage 3 #374 (9fe8a35d): exact installed command on Cursor, real Claude 2.1.257, real Codex 0.147.0 observesv1→v2on one connection with exactly onelist_changedAB7202); #374 pins the installed hook module advancing tov2SKILL.md→v2in all three installed layoutshost-mcp-proxy.test.ts: v1 in-flight completes while v2 serves; old session drains aftertests/dev-contract-adoption.test.ts,packages/workbench/tests/host-adoption.e2e.test.ts(1440×900),AB7210/AB7211indocs/diagnostics.mddocs/framework-mode.md"Live development into hosts",docs/architecture/rsc-runtime-workbench.mdhost-adoption axis,docs/diagnostics.md(AB7200–AB7202,AB7210–AB7211,AB8024–AB8025), package + root READMEReal-host evidence beyond the deterministic lanes: the opt-in
AGENT_BUNDLE_HOST_INSTALL_CLAUDE_SESSION=1lane drove two real Claude model turns (--session-id/--resume) against the installed dev plugin and gotv1:claude-sessionthenv2:claude-sessionwith no reinstall and the normal Claude settings/plugin tree byte-unchanged. Codex exposes no inline plugin-loading surface for an isolated dev install; that lane is an honest skip with the reason in its title.Stages: 1 #339 · 2 #343 · 3 #374 · 4 #385. Closing.