LearnNewsExamplesServices
Frontmatter
id13162
titleFleet Manager: ensureAgentRepo orchestrator (derive → inspect → provision)
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-ada
createdAtJun 14, 2026, 2:50 AM
updatedAtJun 14, 2026, 3:14 AM
githubUrlhttps://github.com/neomjs/neo/issues/13162
authorneo-opus-ada
commentsCount0
parentIssue13015
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 14, 2026, 3:14 AM

Fleet Manager: ensureAgentRepo orchestrator (derive → inspect → provision)

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

Context

Sub-leaf of #13015 (FM MVP) — the capstone of the repo-provisioning trio. #13145 / PR #13146 (derive the path), #13148 / PR #13149 (inspect the state → action), and #13155 / PR #13156 (provision the action) are all merged on dev. This composes them into the single "ensure an agent's repo exists" operation the Fleet Manager lifecycle calls.

The Problem

The three primitives are individually useful, but a caller must wire them in the correct sequence (derive → inspect → provision) every time — error-prone + duplicated. The orchestrator is the single entry point: given an agent + repo + managed root + clone URL, ensure the managed checkout exists (clone if absent, reuse if present, conflict if a foreign occupant), returning the outcome.

The Architectural Reality

  • ai/services/fleet/ensureAgentRepo.mjs (Brain side, beside the three primitives + FleetLifecycleService). A plain async function that imports + composes deriveAgentRepoPath + inspectAgentRepo + provisionAgentRepo (all on dev).
  • Inherits the primitives' contracts: deriveAgentRepoPath validates + computes the stable path; inspectAgentRepo classifies the on-disk state; provisionAgentRepo executes (clone via the injectable seam / reuse / conflict-throw). The cloneRepo seam passes through for testability.
  • Single-writer (Scenario-C-zero per #13015): the inspect→provision sequence isn't TOCTOU-guarded — and doesn't need to be (git clone fails safe if the dir changed, and the FM is single-writer per the epic guardrail).

The Fix

export async function ensureAgentRepo({managedRoot, agentId, repoSlug, cloneUrl, cloneRepo}){repoPath, state, action, cloned}:

  • repoPath = deriveAgentRepoPath({managedRoot, agentId, repoSlug})
  • inspection = inspectAgentRepo({repoPath})
  • result = await provisionAgentRepo({repoPath, provisioningAction: inspection.provisioningAction, cloneUrl, cloneRepo})
  • return {repoPath, state: inspection.state, ...result}

Acceptance Criteria

  • ensureAgentRepo composes derive → inspect → provision; async; imports the three merged primitives.
  • absent path → clones (cloneRepo invoked once), returns cloned: true.
  • existing checkout → reuses (cloneRepo NOT invoked), returns cloned: false.
  • foreign occupant → throws (conflict), cloneRepo NOT invoked.
  • the cloneRepo seam passes through to provisionAgentRepo for testability; the default (un-injected) path uses the real git clone.
  • invalid managedRoot / agentId / repoSlug throw (inherited from deriveAgentRepoPath).
  • Unit spec test/playwright/unit/ai/ensureAgentRepo.spec.mjs: temp-dir fixtures (absent / checkout / occupied) + an injected clone stub; green.

Out of Scope

  • cloneUrl / credential resolution (the caller supplies the already-resolved URL).
  • Wiring ensureAgentRepo into FleetLifecycleService spawn (a later lifecycle integration).
  • Concurrency / locking (single-writer per the epic).

Decision Record impact

none — composes existing Brain-side helpers; no ADR interaction.

Related

Live latest-open sweep: checked open issues + an ensure / orchestrate / compose-repo scan at 2026-06-14T00:49Z; no equivalent found (the one "orchestrator" hit is #12065, the unrelated REM pipeline epic).

Release classification: post-release (Fleet Manager product line; nothing v13.x-blocking).

Origin Session ID: 4c598c8f-d8a7-4288-9420-e825a45d310e