fleetd #480 Unit C: fleet_handover MCP tool #485
Reference in New Issue
Block a user
Delete Branch "worker/480-c-handover-tool-8ed764-6"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #480 Unit C — wires the
fleet_handoverMCP tool onto the already-mergedLeadRolloverexecutor (PR #483).
What changed
fleet_handovertool (FleetMcp.java): actionsopen/confirm/cancel, thin adapter overLeadRollover.open/confirm/cancel.FleetMcptakes a nullableLeadRollover(new trailingconstructor parameter). When it is
null(noleadRollover:configured), every action returns aclean, structured refusal naming
NOT_CONFIGUREDinstead of throwing — same forLeadRollover'sown
IllegalStateExceptionfrom a config removed by a later hot reload. This keeps theregistered tool surface stable regardless of config, per the fleetd #474 charter tool-surface
gate and
McpContractDocTest.Authz.Action.HANDOVER, gated oncaller.isPrimary()— folded into thesame
case SPAWN, STOP, DRAIN, HANDOVER -> caller.isPrimary();line as the other lifecycleactions.
action,reason,token,operatorConfirmed— noterminal/sessionId/leadTerminal/anything like it. Thelead pane is always
callerTerminal(exchange), resolved by the MCP layer from the connection —never a request field, per
LeadRollover's class javadoc (fleetd #480 correction 2) and thisproject's charter invariant 3.
Fleetd.java: the existingleadRolloverlocal (previously unused after Unit A) is nowpassed into
FleetMcp's construction. TheLeadRollover leadRollover = leadRollover(cfg, router.leadAgents(), config);line itself is untouched —FleetdLeadRolloverWiringTeststillpasses unchanged.
FleetMcp.registeredTools()accessor (delegates toMcpSyncServer#listTools()) so a test can assert against the actually-registered wire schemarather than scraping source text.
Explicitly out of scope (left alone, not touched)
LeadRollover.javaitself.CLAUDE.md,wiki/,fleetd.yaml.open.Defect found in
LeadRollover.java, NOT fixed (out of scope for this unit)waitUntilInjectable(used byconfirm()'s deferred continuation to decide whether the callinglead's own turn has ended) gates on
AgentStatus.injectable(), which isIDLE || BLOCKED || DONE.BLOCKEDis a live, paused turn (e.g. a pane sitting at an open approval prompt) — not anended one. So if the lead's turn hits an approval prompt right after calling
confirm(), thecontinuation can read that as "settled" within one
turnSettleSecondswindow and send/clearinto a live, paused turn, destroying context — the exact failure this guard exists to prevent.
This is a real, verified finding (recorded in this session's own memory before this unit started,
dated 2026-09-11), not a hypothesis. Per the brief for this unit,
LeadRollover.javawas leftuntouched; flagging for the lead to open as a follow-up.
Build
mvn clean installinfleetd/, unpiped, full output read: BUILD SUCCESS (1 occurrence),BUILD FAILURE (0 occurrences),
Tests run: 1659, Failures: 0, Errors: 0, Skipped: 0.New tests added (
FleetMcpHandoverTest, 8 tests, all passing):registeredEitherWay— fleet_handover registered whetherleadRollover:is configured or not.nullLeadRolloverRefusesCleanlyForEveryAction— nullLeadRollover, all 3 actions: no throw,clean
NOT_CONFIGURED.unknownActionIsACleanError— missing/bogusactionis a clean tool error, not an exception.onlyThePrimaryIsPermitted—toolActionmaps toAuthz.Action.HANDOVER; worker and architectrefused, primary permitted, via
denyFor(the authorization gate itself).schemaCarriesNoCallerIdentityParameter— asserts againstregisteredTools()'s actual wireschema that no
terminal/sessionId/leadTerminal/callerTerminal/targetproperty exists.openThenConfirmRoundTripsAndOwnershipIsEnforced— open then confirm on the same terminalsucceeds; a different terminal gets
NOT_YOUR_ROLLOVER.cancelUnknownTokenIsCleanNotAFailure/cancelKnownTokenSucceeds.