Context
Sub-leaf of #13015 (Fleet Manager MVP, pillar 1 of #13012). The FleetManager control-plane facade (ai/services/fleet/FleetManager.mjs, shipped via #13192) is the surface-independent service layer that resolves managedRoot once and exposes operator operations turnkey, so any surface (an MCP control-plane, the settings pane) sits thinly on top. Today it exposes only startAgent + fleetRepoStatus — the mutating lifecycle verbs stop / restart are not turnkey on the facade, so a consuming surface would have to reach into FleetLifecycleService directly, defeating the facade's single-turnkey-surface purpose. #13015's MVP success criterion names "whole-fleet start/stop/restart/health".
The Problem
The lifecycle primitive already implements stop + restart + status — FleetLifecycleService.stop(id) (:217), async restart(id) (:256), status(id) (:266) — but the facade only surfaces startAgent. The gap is pure exposure: the operator-operations surface is incomplete (start-only), so the downstream MCP-control-plane / settings-pane leaves cannot stop or restart an agent through the one turnkey layer.
The Architectural Reality
ai/services/fleet/FleetManager.mjs:122 — startAgent delegates to startAgentProvisioned (provision-then-start); fleetRepoStatus delegates to inspectFleetRepos. The facade idiom: resolve managedRoot + the lifecycle service, delegate to a merged primitive.
ai/services/fleet/FleetLifecycleService.mjs:256 — restart calls start(id) with NO cwd → a Fleet-Manager-provisioned agent (started via startAgentProvisioned with cwd = repoPath) would re-spawn in the FM's own directory, not its repo. That re-introduces the path-keyed-memory correctness bug fixed for the start path in #13177. So a turnkey restartAgent MUST route through the provisioned start, not lifecycleService.restart.
ai/services/fleet/removeAgentRepo.mjs:28-32 — explicitly states the repo-removal primitive is "deliberately never auto-wired into removeAgent"; removal + auto-memory reconciliation is the blocked #13190 (needs a Memory-Core delete/archive/tombstone policy). So removeAgent is correctly OUT of this leaf.
The Fix
In ai/services/fleet/FleetManager.mjs, add two turnkey methods mirroring the existing delegation idiom:
stopAgent(id) → this.getLifecycleService().stop(id) — thin delegation; returns {success, id, state}.
restartAgent(id) → await this.stopAgent(id); return this.startAgent(id) — provisioned restart: stop, then the facade's own provision-then-start (re-ensures the repo via ensureAgentRepo + preserves cwd). Deliberately NOT a delegation to lifecycleService.restart (which drops cwd).
Unit tests added to test/playwright/unit/ai/FleetManager.spec.mjs, mirroring the existing inject-lifecycle-stub + assert-delegation pattern.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
FleetManager.stopAgent(id) (new) |
this leaf |
delegate → lifecycleService.stop(id) ⇒ {success,id,state} |
n/a |
JSDoc @summary |
unit: delegation + id passthrough |
FleetManager.restartAgent(id) (new) |
this leaf |
await this.stopAgent(id) then this.startAgent(id) (provisioned, cwd-preserving) |
n/a |
JSDoc @summary |
unit: stop-then-provisioned-start |
FleetLifecycleService.stop(id) |
existing #13049 (:217) |
consumed as-is |
— |
existing |
read FleetLifecycleService.mjs:217 |
FleetLifecycleService.restart(id) |
existing #13049 (:256) |
NOT consumed (drops cwd) |
provisioned restart instead |
existing |
read FleetLifecycleService.mjs:256 |
Decision Record impact
none — facade method additions consistent with the established startAgent / fleetRepoStatus delegation pattern; no ADR authority interaction.
Acceptance Criteria
Out of Scope
removeAgent / repo removal / auto-memory reconciliation — the blocked #13190 (needs a Memory-Core policy + honors removeAgentRepo's never-auto-wire contract).
- Per-agent process
status passthrough on the facade — additive, can follow if a consuming surface needs it.
- The operator-facing surface itself (MCP control-plane / settings-pane wiring) — a separate leaf that consumes this facade (per
FleetManager.mjs JSDoc).
- Fixing
FleetLifecycleService.restart's cwd-drop at the primitive layer — restartAgent routes around it; a primitive-level fix (or JSDoc caveat) can be a separate follow-up.
Avoided Traps
- Delegating
restartAgent → lifecycleService.restart — rejected: drops cwd, re-spawning a provisioned agent in the FM dir not its repo (the #13177-class path-keyed-memory bug). The provisioned restart is the correct turnkey shape.
- Auto-wiring
removeAgent → removeAgentRepo — rejected: the primitive's contract forbids it; removal is the blocked #13190 policy.
Related
Release classification: post-release (product line; nothing v13.x-blocking — inherits #13015).
Live latest-open sweep: checked latest 25 open issues + 30 recent A2A claims immediately before filing; no equivalent FM-facade-lifecycle ticket or in-flight claim.
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-ada.
Origin Session ID: 73156d71-9a96-4bf1-bbc8-d6487ca7dddd
Retrieval Hint: "Fleet Manager facade turnkey stopAgent restartAgent provisioned restart cwd lifecycle"
Context
Sub-leaf of #13015 (Fleet Manager MVP, pillar 1 of #13012). The
FleetManagercontrol-plane facade (ai/services/fleet/FleetManager.mjs, shipped via #13192) is the surface-independent service layer that resolvesmanagedRootonce and exposes operator operations turnkey, so any surface (an MCP control-plane, the settings pane) sits thinly on top. Today it exposes onlystartAgent+fleetRepoStatus— the mutating lifecycle verbsstop/restartare not turnkey on the facade, so a consuming surface would have to reach intoFleetLifecycleServicedirectly, defeating the facade's single-turnkey-surface purpose. #13015's MVP success criterion names "whole-fleet start/stop/restart/health".The Problem
The lifecycle primitive already implements stop + restart + status —
FleetLifecycleService.stop(id)(:217),async restart(id)(:256),status(id)(:266) — but the facade only surfacesstartAgent. The gap is pure exposure: the operator-operations surface is incomplete (start-only), so the downstream MCP-control-plane / settings-pane leaves cannot stop or restart an agent through the one turnkey layer.The Architectural Reality
ai/services/fleet/FleetManager.mjs:122—startAgentdelegates tostartAgentProvisioned(provision-then-start);fleetRepoStatusdelegates toinspectFleetRepos. The facade idiom: resolvemanagedRoot+ the lifecycle service, delegate to a merged primitive.ai/services/fleet/FleetLifecycleService.mjs:256—restartcallsstart(id)with NOcwd→ a Fleet-Manager-provisioned agent (started viastartAgentProvisionedwithcwd = repoPath) would re-spawn in the FM's own directory, not its repo. That re-introduces the path-keyed-memory correctness bug fixed for the start path in #13177. So a turnkeyrestartAgentMUST route through the provisioned start, notlifecycleService.restart.ai/services/fleet/removeAgentRepo.mjs:28-32— explicitly states the repo-removal primitive is "deliberately never auto-wired intoremoveAgent"; removal + auto-memory reconciliation is the blocked #13190 (needs a Memory-Core delete/archive/tombstone policy). SoremoveAgentis correctly OUT of this leaf.The Fix
In
ai/services/fleet/FleetManager.mjs, add two turnkey methods mirroring the existing delegation idiom:stopAgent(id)→this.getLifecycleService().stop(id)— thin delegation; returns{success, id, state}.restartAgent(id)→await this.stopAgent(id); return this.startAgent(id)— provisioned restart: stop, then the facade's own provision-then-start (re-ensures the repo viaensureAgentRepo+ preservescwd). Deliberately NOT a delegation tolifecycleService.restart(which dropscwd).Unit tests added to
test/playwright/unit/ai/FleetManager.spec.mjs, mirroring the existing inject-lifecycle-stub + assert-delegation pattern.Contract Ledger Matrix
FleetManager.stopAgent(id)(new)lifecycleService.stop(id)⇒{success,id,state}@summaryFleetManager.restartAgent(id)(new)await this.stopAgent(id)thenthis.startAgent(id)(provisioned,cwd-preserving)@summaryFleetLifecycleService.stop(id):217)FleetLifecycleService.mjs:217FleetLifecycleService.restart(id):256)cwd)FleetLifecycleService.mjs:256Decision Record impact
none— facade method additions consistent with the establishedstartAgent/fleetRepoStatusdelegation pattern; no ADR authority interaction.Acceptance Criteria
FleetManager.stopAgent(id)delegates to the (injected) lifecycle service'sstopwith the agent id and returns its result.FleetManager.restartAgent(id)stops then re-starts via the provisioned path (startAgent), so a provisioned agent restarts in ITS repocwd(not the FM dir) — verified by asserting the provision-then-start composer is invoked on restart.FleetManager.spec.mjs(serial, injected-field reset, delegation assertions); full unit suite green.@summaryon both methods (Anchor & Echo).Out of Scope
removeAgent/ repo removal / auto-memory reconciliation — the blocked #13190 (needs a Memory-Core policy + honorsremoveAgentRepo's never-auto-wire contract).statuspassthrough on the facade — additive, can follow if a consuming surface needs it.FleetManager.mjsJSDoc).FleetLifecycleService.restart'scwd-drop at the primitive layer —restartAgentroutes around it; a primitive-level fix (or JSDoc caveat) can be a separate follow-up.Avoided Traps
restartAgent→lifecycleService.restart— rejected: dropscwd, re-spawning a provisioned agent in the FM dir not its repo (the #13177-class path-keyed-memory bug). The provisioned restart is the correct turnkey shape.removeAgent→removeAgentRepo— rejected: the primitive's contract forbids it; removal is the blocked #13190 policy.Related
cwd-on-start precedent: #13177. Blocked sibling: #13190.Release classification: post-release (product line; nothing v13.x-blocking — inherits #13015).
Live latest-open sweep: checked latest 25 open issues + 30 recent A2A claims immediately before filing; no equivalent FM-facade-lifecycle ticket or in-flight claim.
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-ada. Origin Session ID: 73156d71-9a96-4bf1-bbc8-d6487ca7dddd Retrieval Hint: "Fleet Manager facade turnkey stopAgent restartAgent provisioned restart cwd lifecycle"