Context
The operator reframed the Fleet Manager cockpit (2026-07-02, relayed via @neo-opus-grace): "the team can control it and it enables the team — session-sunset → restart your harness." So the cockpit (#13448, Grace's Lane B) is a control surface, not observe-only. This leaf delivers the per-agent repo control verb — setRepo — plus the registry partial-update primitive it needs.
Scope refined during implementation (2026-07-03): this ticket originally paired setRepo with setWakeEnabled. Deep V-B-A showed the two split by AUTHORITY: setRepo is fleet authority (own-registry mutation, like defineAgent) and ships here; setWakeEnabled is control-plane authorized (toggling another agent's wake is a privilege boundary via WakeSubscriptionService's context-owner scoping) and moved to #14537 (blocked_by #14477). Ticket re-scoped to setRepo.
The Problem
FLEET_WIRE_METHODS (src/ai/fleet/fleetWireMethods.mjs) exposed defineAgent · listAgents · getAgent · startAgent · stopAgent · restartAgent · removeAgent · fleetStatus — no way for the cockpit to set an agent's working repo. And the registry had defineAgent (full create-or-replace upsert) but no narrow partial-update, so one facet couldn't be mutated without re-supplying the whole definition.
The Architectural Reality
ai/services/fleet/FleetManager.mjs is the Brain-side thin facade; each verb composes a collaborator.
startAgentProvisioned.mjs:54 already honors agent.metadata.repo = {cloneUrl, repoSlug} — it clone-or-reuses that repo and pins the harness cwd to the checkout. So setting metadata.repo is functional end-to-end; no new provisioning consumer is needed.
- The app↔fleet wire (
dispatchFleetRequest.mjs:30) forwards a single params argument, so a pane-reachable verb must take ONE payload object — mirroring defineAgent.
FleetControlBridge is the pane-reachable capability allowlist; createFleetRegistryBridge generates the client bridge from FLEET_WIRE_METHODS, and dispatchFleetRequest is the server-side choke-point enforcing the same list.
The Fix
FleetRegistryService.updateAgent(id, patch) — partial-merge (merges metadata, preserves other fields + createdAt + credential; null on unknown id). Non-destructive.
FleetManager.setRepo({id, cloneUrl, repoSlug}) — single-payload, wire-compatible; delegates to updateAgent setting metadata.repo = {cloneUrl, repoSlug}. Non-destructive (mirrors removeAgent).
FleetControlBridge.setRepo(payload) + FLEET_WIRE_METHODS += setRepo — pane-reachable, both wire ends + the dispatch choke-point updated.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
FLEET_WIRE_METHODS += setRepo |
operator control-plane reframe; the app↔fleet wire SSOT |
pane-reachable setRepo on both wire ends (createFleetRegistryBridge gen + dispatchFleetRequest enforce) |
none — off-list rejected |
fleetWireMethods JSDoc |
dispatchFleetRequest.spec allowlist + routing tests |
FleetControlBridge.setRepo(payload) |
FleetControlBridge capability allowlist |
single-params payload → FleetManager.setRepo |
none — fail-closed |
JSDoc |
FleetControlBridge.spec |
FleetManager.setRepo({id, cloneUrl, repoSlug}) |
FleetManager facade; startAgentProvisioned metadata.repo contract |
sets metadata.repo = {cloneUrl, repoSlug} via updateAgent; provisioner honors it |
none — fail-closed; null on unknown id |
JSDoc |
FleetManager.spec |
FleetRegistryService.updateAgent(id, patch) |
new registry primitive |
partial-merge metadata + modelProvider, preserve rest; null on unknown |
none |
JSDoc |
FleetRegistryService.spec |
Decision Record impact
aligned-with ADR-0026 — a fleet-authority operator-UI control verb on the client bridge, the OTHER side of the control-plane/diagnostics R3 seam from the #14477/#14501 daemon-core actuator. No ADR authority change.
Acceptance Criteria
Out of Scope
setWakeEnabled — moved to #14537 (blocked_by #14477; control-plane authorized).
setHookEnabled — deferred (Grace's #14439/#14481 stop-hook revert per #14501 OQ2).
- The #14477/#14501 daemon-core restart actuator — separate
control-plane/ off-bridge surface.
- The cockpit UI — #13448 (Lane B) consumes
registryBridge.setRepo.
data-dir per-agent override — the checkout path is managedRoot-derived (not a per-agent metadata.repo field); out of scope for this verb.
Related
#13015 (parent — FM MVP / Lane C) · #13448 (Lane B cockpit — blocked_by this) · #14537 (setWakeEnabled split-out) · #14477 / #14501 (control-plane authority) · PR #14536 · ai/services/fleet/FleetManager.mjs · startAgentProvisioned.mjs
Origin Session ID: 29018efb-2edf-4172-902d-0fe42d4b0c11
Retrieval Hint: "Fleet setRepo single payload metadata.repo updateAgent FLEET_WIRE_METHODS wire-compatible"
Context
The operator reframed the Fleet Manager cockpit (2026-07-02, relayed via @neo-opus-grace): "the team can control it and it enables the team — session-sunset → restart your harness." So the cockpit (#13448, Grace's Lane B) is a control surface, not observe-only. This leaf delivers the per-agent repo control verb —
setRepo— plus the registry partial-update primitive it needs.The Problem
FLEET_WIRE_METHODS(src/ai/fleet/fleetWireMethods.mjs) exposeddefineAgent · listAgents · getAgent · startAgent · stopAgent · restartAgent · removeAgent · fleetStatus— no way for the cockpit to set an agent's working repo. And the registry haddefineAgent(full create-or-replace upsert) but no narrow partial-update, so one facet couldn't be mutated without re-supplying the whole definition.The Architectural Reality
ai/services/fleet/FleetManager.mjsis the Brain-side thin facade; each verb composes a collaborator.startAgentProvisioned.mjs:54already honorsagent.metadata.repo = {cloneUrl, repoSlug}— it clone-or-reuses that repo and pins the harnesscwdto the checkout. So settingmetadata.repois functional end-to-end; no new provisioning consumer is needed.dispatchFleetRequest.mjs:30) forwards a singleparamsargument, so a pane-reachable verb must take ONE payload object — mirroringdefineAgent.FleetControlBridgeis the pane-reachable capability allowlist;createFleetRegistryBridgegenerates the client bridge fromFLEET_WIRE_METHODS, anddispatchFleetRequestis the server-side choke-point enforcing the same list.The Fix
FleetRegistryService.updateAgent(id, patch)— partial-merge (mergesmetadata, preserves other fields +createdAt+ credential;nullon unknown id). Non-destructive.FleetManager.setRepo({id, cloneUrl, repoSlug})— single-payload, wire-compatible; delegates toupdateAgentsettingmetadata.repo = {cloneUrl, repoSlug}. Non-destructive (mirrorsremoveAgent).FleetControlBridge.setRepo(payload)+FLEET_WIRE_METHODS += setRepo— pane-reachable, both wire ends + the dispatch choke-point updated.Contract Ledger Matrix
FLEET_WIRE_METHODS+=setReposetRepoon both wire ends (createFleetRegistryBridgegen +dispatchFleetRequestenforce)fleetWireMethodsJSDocdispatchFleetRequest.specallowlist + routing testsFleetControlBridge.setRepo(payload)FleetControlBridgecapability allowlistparamspayload →FleetManager.setRepoFleetControlBridge.specFleetManager.setRepo({id, cloneUrl, repoSlug})FleetManagerfacade;startAgentProvisionedmetadata.repocontractmetadata.repo = {cloneUrl, repoSlug}viaupdateAgent; provisioner honors itnullon unknown idFleetManager.specFleetRegistryService.updateAgent(id, patch)metadata+modelProvider, preserve rest;nullon unknownFleetRegistryService.specDecision Record impact
aligned-with ADR-0026— a fleet-authority operator-UI control verb on the client bridge, the OTHER side of the control-plane/diagnostics R3 seam from the #14477/#14501 daemon-core actuator. No ADR authority change.Acceptance Criteria
FleetRegistryService.updateAgent(id, patch)partial-mergesmetadata, preserves other fields, returnsnullon unknown id — unit spec.FleetManager.setRepo({id, cloneUrl, repoSlug})is single-payload (wire-compatible), setsmetadata.repo = {cloneUrl, repoSlug}, omits unset coords — unit spec.setRepoadded toFLEET_WIRE_METHODS;dispatchFleetRequestallowlist + routing tests confirm the choke-point routes it to the injected bridge without opening resolver seams.FleetControlBridge.setRepo(payload)exposed on the capability allowlist; the secret-omission boundary still holds.managedRoot/ re-wiring of singletons in the new methods (facade discipline).Out of Scope
setWakeEnabled— moved to #14537 (blocked_by#14477; control-plane authorized).setHookEnabled— deferred (Grace's #14439/#14481 stop-hook revert per #14501 OQ2).control-plane/off-bridge surface.registryBridge.setRepo.data-dirper-agent override — the checkout path ismanagedRoot-derived (not a per-agentmetadata.repofield); out of scope for this verb.Related
#13015 (parent — FM MVP / Lane C) · #13448 (Lane B cockpit —
blocked_bythis) · #14537 (setWakeEnabledsplit-out) · #14477 / #14501 (control-plane authority) · PR #14536 ·ai/services/fleet/FleetManager.mjs·startAgentProvisioned.mjsOrigin Session ID: 29018efb-2edf-4172-902d-0fe42d4b0c11 Retrieval Hint: "Fleet setRepo single payload metadata.repo updateAgent FLEET_WIRE_METHODS wire-compatible"