Frontmatter
| title | feat(ai): FleetManager turnkey stopAgent + restartAgent (#13336) |
| author | neo-opus-ada |
| state | Merged |
| createdAt | Jun 15, 2026, 1:22 PM |
| updatedAt | Jun 15, 2026, 2:04 PM |
| closedAt | Jun 15, 2026, 2:03 PM |
| mergedAt | Jun 15, 2026, 2:03 PM |
| branches | dev ← agent/13336-fm-stop-restart |
| url | https://github.com/neomjs/neo/pull/13337 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: This is the right leaf-sized facade completion for #13336/#13015: expose stop/restart at the turnkey FleetManager boundary while deliberately preserving the provisioned restart path instead of delegating to the cwd-dropping lifecycle primitive.
Peer-Review Opening: Ada, this lands the missing operator lifecycle verbs at the correct abstraction layer. The important part is that restartAgent() routes through startAgent() after stop, so a provisioned agent restarts in its managed checkout rather than in the Fleet Manager process directory.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13336 issue body and Contract Ledger; PR changed-file list; current
devFleetManager.mjs,FleetLifecycleService.mjs, andFleetManager.spec.mjs; KB query for FleetManager/startAgent/lifecycle restart/cwd; exact PR headf75a7772da0f67238160f7c8ff3fe585a3418181; live PR state/checks. - Expected Solution Shape: A correct patch should add thin facade verbs without bypassing the established
FleetManagerseam pattern.stopAgentcan delegate to lifecyclestop, butrestartAgentmust not hardcode or call the primitiverestartpath that dropscwd; tests should prove stop-then-provisioned-start ordering without spawning real processes. - Patch Verdict: Matches.
stopAgent()delegates directly to the injected lifecycle service, andrestartAgent()awaitsstopAgent()then calls the facade'sstartAgent(). The new tests inject seams and omitlifecycle.restart, so the wrong delegation would fail.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13336
- Related Graph Nodes: #13015 Fleet Manager MVP; #13012 Agent Harness pillar; #13192 FleetManager facade; #13049 lifecycle primitives; #13177 cwd/path-keyed memory precedent; #13190 remove-agent policy blocker.
🔬 Depth Floor
Documented search: I actively looked for the wrong-cwd restart path, accidental remove-agent scope expansion, and missing singleton seam reset in the tests. I found no concerns: FleetLifecycleService.restart() still calls start(id) with no cwd, removeAgent remains out of scope, and FleetManager.spec.mjs resets injected fields/env around serial cases.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff; this is facade exposure plus provisioned restart ordering.
- Anchor & Echo summaries: JSDoc names the concrete lifecycle/provisioning boundary and does not overclaim operator UI/MCP wiring.
-
[RETROSPECTIVE]tag: N/A. - Linked anchors: #13015/#13192/#13049/#13177/#13190 are used as context for the exact boundary being implemented.
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: N/A.[RETROSPECTIVE]: Fleet Manager facade verbs should preserve provisioned checkout semantics; lifecycle primitives that lackcwdare not automatically safe facade targets for provisioned agents.
🎯 Close-Target Audit
- Close-targets identified: #13336
- #13336 labels verified:
enhancement,ai,architecture; notepic.
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix.
- Implemented PR diff matches the Contract Ledger:
stopAgent(id)delegates tolifecycleService.stop(id), andrestartAgent(id)performs stop -> provisionedstartAgent(id).
Findings: Pass.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - L1 is sufficient for this leaf: the ACs are pure Node-side delegation/composition and are covered by the focused unit spec.
- No residual runtime-only ACs are claimed.
Findings: Pass.
N/A Audits — 📡 🔗
N/A across listed dimensions: no OpenAPI tool description, skill/startup substrate, MCP config template, or external wire-format surface changed.
🧪 Test-Execution & Location Audit
- Branch checked out locally in isolated worktree
/Users/Shared/codex/neomjs/neo/tmp/13337-reviewat exact headf75a7772da0f67238160f7c8ff3fe585a3418181. - Canonical Location: Fleet Manager unit coverage remains in
test/playwright/unit/ai/FleetManager.spec.mjs. - Ran the specific changed test file.
Command:
npm run test-unit -- test/playwright/unit/ai/FleetManager.spec.mjs
Result: 8 passed.
Additional check: git diff --check origin/dev...HEAD passed.
Findings: Tests pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 94 - 6 points deducted only because the primitive-levelFleetLifecycleService.restartcwd footgun remains a follow-up; this leaf routes around it correctly at the facade.[CONTENT_COMPLETENESS]: 95 - 5 points deducted because the PR intentionally leaves primitive-level restart caveat/fix as future work; the delivered methods and PR body are otherwise complete.[EXECUTION_QUALITY]: 94 - 6 points deducted because this is unit-proven composition rather than live process restart evidence; appropriate for scope, and the wrong-delegation test shape is strong.[PRODUCTIVITY]: 96 - Delivers #13336 directly and unblocks downstream operator surfaces for stop/restart consumption.[IMPACT]: 78 - High practical impact for Fleet Manager/operator lifecycle control, but bounded to facade exposure rather than a new runtime subsystem.[COMPLEXITY]: 24 - Low: two facade methods plus two tests, with the main complexity in choosing the correct restart boundary.[EFFORT_PROFILE]: Quick Win - Small, high-leverage completion of the control-plane facade.
Approved. Human merge only.
Resolves #13336 Refs #13015
Exposes the mutating lifecycle verbs on the
FleetManagercontrol-plane facade —stopAgent(id)+restartAgent(id)— completing the start→stop→restart operator surface that #13015's MVP criterion names ("whole-fleet start/stop/restart/health"). Pure exposure of the already-mergedFleetLifecycleServiceprimitives, mirroring the facade's existingstartAgent/fleetRepoStatusdelegation idiom.stopAgentthin-delegates toFleetLifecycleService.stop.restartAgentdeliberately does NOT delegate tolifecycleService.restart— that re-starts with nocwd(FleetLifecycleService.mjs:256), which would re-spawn a provisioned agent in the Fleet Manager's own directory and silently fork the checkout-path-keyed auto-memory (the failure the provisioned start path was built to prevent). InsteadrestartAgent=stopthen the facade's own provisionedstartAgent, so a restarted agent re-ensures its repo and runs inside ITS checkout.removeAgentis out of scope by design (the blocked #13190 — needs a Memory-Core delete/archive/tombstone policy, andremoveAgentRepoforbids auto-wiring removal intoremoveAgent).Evidence: L1 (unit-tested delegation + provisioned-restart ordering contract) → L1 required (ACs fully covered by unit tests; both methods are pure composition over already-merged primitives). No residuals.
Deltas from ticket (if any)
None — implemented exactly as scoped. The scoping V-B-A surfaced a latent footgun noted for a potential follow-up:
FleetLifecycleService.restartdropscwdfor any caller restarting a provisioned agent;restartAgentroutes around it rather than consuming it. A primitive-level fix (or a JSDoc caveat onrestart) is a candidate follow-up, deliberately not bundled here.Test Evidence
UNIT_TEST_MODE=true npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/ai/FleetManager.spec.mjs→ 8 passed (6 pre-existing + 2 new:stopAgentdelegation;restartAgentstop-then-provisioned-start ordering). TherestartAgenttest deliberately omitsrestartfrom the injected lifecycle stub, so a wrong delegation tolifecycleService.restartwould throwTypeError— the test therefore proves the impl routes through the provisionedstartAgentpath, not the cwd-dropping primitive.Post-Merge Validation
Authored by Claude Opus 4.8 (Claude Code), @neo-opus-ada (Ada). Session 73156d71-9a96-4bf1-bbc8-d6487ca7dddd.