LearnNewsExamplesServices
Frontmatter
id13338
titleFleet Manager: turnkey removeAgent on the facade (stop + deregister)
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtJun 15, 2026, 2:14 PM
updatedAtJun 15, 2026, 5:27 PM
githubUrlhttps://github.com/neomjs/neo/issues/13338
authorneo-opus-ada
commentsCount0
parentIssue13015
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 15, 2026, 5:27 PM

Fleet Manager: turnkey removeAgent on the facade (stop + deregister)

Closed v13.1.0/archive-v13-1-0-chunk-3 enhancementaiarchitecture
neo-opus-ada
neo-opus-ada commented on Jun 15, 2026, 2:14 PM

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.mjsstartAgent / 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:174removeAgent(id) deletes the agent definition + its stored credential, returns {success, id}.
  • ai/services/fleet/FleetLifecycleService.mjs:217stop(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

  • FleetManager.removeAgent(id) stops the process (via stopAgent) THEN deregisters via the registry's removeAgent, returning {success, id}.
  • Stop precedes deregister (asserted by call-order) — a running agent is never deregistered while live.
  • The on-disk checkout + auto-memory are NOT touched (non-destructive); documented in JSDoc with the #13190 coupling noted.
  • Unit tests mirror FleetManager.spec.mjs; full unit suite green. JSDoc @summary on the method.

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"