Context
Sub-leaf of #13015 (Fleet Manager MVP). The FleetManager facade now exposes start / stop / restart (#13336) + fleetRepoStatus — but the operator cannot remove an agent from the fleet. That is the missing CRUD-delete in the MVP's "full agent CRUD". FleetRegistryService.removeAgent(id) (definition + PAT cleanup) already exists (:174); the gap is the turnkey facade verb that stops a running agent first (so removal never orphans a live process) then deregisters.
The Problem
Removing an agent must (a) not leave an orphaned running process — a deregistered-but-running harness is unmanageable — and (b) not silently orphan the agent's checkout-path-keyed auto-memory. (a) is solved by stop-then-deregister. (b) is why this leaf is deliberately non-destructive to the on-disk checkout: deleting the checkout orphans the path-keyed auto-memory, and that reconciliation is the blocked #13190 (a Memory-Core delete/archive/tombstone policy). Deletion and reconciliation cannot be safely decoupled, so the destructive repo cleanup stays coupled in #13190; this leaf ships the safe registry-removal half.
The Architectural Reality
ai/services/fleet/FleetManager.mjs — startAgent / stopAgent / restartAgent / fleetRepoStatus compose the lifecycle service + registry; removeAgent follows the same idiom. The registry is reached via lifecycleService.getRegistry().
ai/services/fleet/FleetRegistryService.mjs:174 — removeAgent(id) deletes the agent definition + its stored credential, returns {success, id}.
ai/services/fleet/FleetLifecycleService.mjs:217 — stop(id) gracefully stops (SIGTERM→SIGKILL); safe on a non-running agent ({success:false}, no throw).
ai/services/fleet/removeAgentRepo.mjs:28 — destructive repo removal is "never auto-wired"; it + the memory reconciliation (#13190) are the coupled destructive path, OUT of this leaf.
The Fix
Add removeAgent(id) to the facade:
await this.stopAgent(id) — stop the process first (safe no-op if not running); never orphan a live process.
return this.getLifecycleService().getRegistry().removeAgent(id) — deregister the definition + PAT; returns {success, id}.
- The on-disk checkout + its path-keyed auto-memory are deliberately NOT removed (the coupled destructive cleanup is #13190).
Unit tests in test/playwright/unit/ai/FleetManager.spec.mjs (inject a lifecycle stub exposing stop + getRegistry().removeAgent; assert stop-before-deregister order + return passthrough).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
FleetManager.removeAgent(id) (new) |
this leaf |
await this.stopAgent(id) then registry.removeAgent(id) ⇒ {success,id} |
n/a |
JSDoc @summary |
unit: stop-then-deregister order |
FleetRegistryService.removeAgent(id) |
existing (:174) |
consumed as-is |
— |
existing |
read FleetRegistryService.mjs:174 |
FleetLifecycleService.stop(id) |
existing #13049 (:217) |
consumed via stopAgent |
— |
existing |
read FleetLifecycleService.mjs:217 |
Decision Record impact
none — facade method addition consistent with the established delegation pattern.
Acceptance Criteria
Out of Scope
- Destructive checkout removal (
removeAgentRepo) + the auto-memory reconciliation — the coupled, blocked #13190 (Memory-Core policy). This leaf is deliberately non-destructive to disk.
- A
removeRepo opt-in flag — deferred: it would open the orphaning window #13190 closes; the destructive path is better added coupled with the reconciliation in #13190.
Avoided Traps
- Deregister without stopping — rejected: orphans a live process (unmanageable). Stop-first.
- Deleting the checkout here (or a
removeRepo flag now) — rejected: orphans path-keyed auto-memory (the #13190 problem); deletion can't be safely decoupled from reconciliation.
Related
Parent: #13015. Builds on the facade (#13192, #13336). Registry removal: existing FleetRegistryService.removeAgent. Coupled destructive follow-up: #13190.
Release classification: post-release (inherits #13015).
Live latest-open sweep: checked latest 20 open issues + 15 recent A2A claims immediately before filing; no equivalent FM-removeAgent 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 removeAgent stop deregister non-destructive registry CRUD delete"
Context
Sub-leaf of #13015 (Fleet Manager MVP). The
FleetManagerfacade now exposes start / stop / restart (#13336) +fleetRepoStatus— but the operator cannot remove an agent from the fleet. That is the missing CRUD-delete in the MVP's "full agent CRUD".FleetRegistryService.removeAgent(id)(definition + PAT cleanup) already exists (:174); the gap is the turnkey facade verb that stops a running agent first (so removal never orphans a live process) then deregisters.The Problem
Removing an agent must (a) not leave an orphaned running process — a deregistered-but-running harness is unmanageable — and (b) not silently orphan the agent's checkout-path-keyed auto-memory. (a) is solved by stop-then-deregister. (b) is why this leaf is deliberately non-destructive to the on-disk checkout: deleting the checkout orphans the path-keyed auto-memory, and that reconciliation is the blocked #13190 (a Memory-Core delete/archive/tombstone policy). Deletion and reconciliation cannot be safely decoupled, so the destructive repo cleanup stays coupled in #13190; this leaf ships the safe registry-removal half.
The Architectural Reality
ai/services/fleet/FleetManager.mjs—startAgent/stopAgent/restartAgent/fleetRepoStatuscompose the lifecycle service + registry;removeAgentfollows the same idiom. The registry is reached vialifecycleService.getRegistry().ai/services/fleet/FleetRegistryService.mjs:174—removeAgent(id)deletes the agent definition + its stored credential, returns{success, id}.ai/services/fleet/FleetLifecycleService.mjs:217—stop(id)gracefully stops (SIGTERM→SIGKILL); safe on a non-running agent ({success:false}, no throw).ai/services/fleet/removeAgentRepo.mjs:28— destructive repo removal is "never auto-wired"; it + the memory reconciliation (#13190) are the coupled destructive path, OUT of this leaf.The Fix
Add
removeAgent(id)to the facade:await this.stopAgent(id)— stop the process first (safe no-op if not running); never orphan a live process.return this.getLifecycleService().getRegistry().removeAgent(id)— deregister the definition + PAT; returns{success, id}.Unit tests in
test/playwright/unit/ai/FleetManager.spec.mjs(inject a lifecycle stub exposingstop+getRegistry().removeAgent; assert stop-before-deregister order + return passthrough).Contract Ledger Matrix
FleetManager.removeAgent(id)(new)await this.stopAgent(id)thenregistry.removeAgent(id)⇒{success,id}@summaryFleetRegistryService.removeAgent(id):174)FleetRegistryService.mjs:174FleetLifecycleService.stop(id):217)stopAgentFleetLifecycleService.mjs:217Decision Record impact
none— facade method addition consistent with the established delegation pattern.Acceptance Criteria
FleetManager.removeAgent(id)stops the process (viastopAgent) THEN deregisters via the registry'sremoveAgent, returning{success, id}.FleetManager.spec.mjs; full unit suite green. JSDoc@summaryon the method.Out of Scope
removeAgentRepo) + the auto-memory reconciliation — the coupled, blocked #13190 (Memory-Core policy). This leaf is deliberately non-destructive to disk.removeRepoopt-in flag — deferred: it would open the orphaning window #13190 closes; the destructive path is better added coupled with the reconciliation in #13190.Avoided Traps
removeRepoflag now) — rejected: orphans path-keyed auto-memory (the #13190 problem); deletion can't be safely decoupled from reconciliation.Related
Parent: #13015. Builds on the facade (#13192, #13336). Registry removal: existing
FleetRegistryService.removeAgent. Coupled destructive follow-up: #13190.Release classification: post-release (inherits #13015). Live latest-open sweep: checked latest 20 open issues + 15 recent A2A claims immediately before filing; no equivalent FM-removeAgent 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 removeAgent stop deregister non-destructive registry CRUD delete"