LearnNewsExamplesServices
Frontmatter
id16715
titleFleet workspace preparation: split pure plan from host-owned apply effects
stateClosed
labels
enhancementairefactoringarchitecture
assigneesneo-gpt-emmy
createdAtAug 8, 2026, 6:12 PM
updatedAtAug 8, 2026, 10:11 PM
githubUrlhttps://github.com/neomjs/neo/issues/16715
authorneo-gpt
commentsCount2
parentIssue16168
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 16735 Fleet control service joins the composition — optional fleet-server (Fleet-owned root + registered projections)
closedAtAug 8, 2026, 10:11 PM
milestonev13.2

Fleet workspace preparation: split pure plan from host-owned apply effects

Closed Backlog/active-chunk-14 enhancementairefactoringarchitecture
neo-gpt
neo-gpt commented on Aug 8, 2026, 6:12 PM

Context

This is the first executable leaf of #16168 under the v13.2 Fleet Manager containerization lane. The graduated source discussion (#16176) names the prerequisite directly: prepareManagedAgentWorkspace() currently mixes plan derivation with host filesystem effects, so the plan/apply boundary must exist before a container-owned envelope can be honest.

The current implementation is 1,989 lines and has exactly one production caller: startAgentProvisioned.mjs. Its present call chain is:

FleetManager.startAgent()startAgentProvisioned()ensureAgentRepo()prepareManagedAgentWorkspace()FleetLifecycleService.start()

Problem

prepareManagedAgentWorkspace() currently combines two contracts:

  1. deterministic workspace and MCP intent derivation; and
  2. host-owned capability checks, path binding, hydration, filesystem convergence, permissions, and transport receipts.

That makes a future container boundary false by construction: the logical planner cannot be container-owned while it also reads and mutates host paths, and the host apply edge must not become an alternative policy owner for agent identity, targets, credentials, or authorization.

The file already contains the beginnings of the right seam—private createMcpPlan(), render functions, seat-config generators, deriveHarnessLaunchSpec(), deriveAgentInstanceHome(), and resolveMcpMatrix()—but they are still composed inside the effectful monolith.

Architectural Reality

The current effect surface includes:

  • stat / lstat / access capability and executable checks;
  • symlink, containment, and real-directory validation;
  • repo hydration;
  • bounded file reads;
  • create-only and atomic writes;
  • mkdir, rename, chmod 0600, and unlink;
  • artifact convergence and transport-receipt replacement/removal.

The current MCP plan carries no secret values—it carries public URLs and environment-slot names—but it also contains host-derived absolute paths such as commands, entrypoints, and working directories. Those bindings belong at the host edge, not in the container-owned logical plan.

Intended Solution

Split workspace preparation into two explicit contracts:

  1. Pure logical planner — an exported, deterministic function that accepts immutable agent/transport intent and returns closed-schema harness, artifact, and MCP intent. It may contain opaque seat/target identifiers, public URLs, environment-slot names, and curated artifact intent. It performs no filesystem, environment, process, or global-config reads and contains neither secret values nor absolute host paths.
  2. Host-owned apply edge — binds the opaque target to resolved repo/home/main-checkout/Node paths and performs the existing capability checks, hydration, reads, writes, convergence, permission enforcement, and receipts. It does not decide registry, credential, identity, or authorization policy.

Keep prepareManagedAgentWorkspace() as the compatibility composer—plan, then apply—during this migration. Preserve its public return shape, adapter behavior, local↔remote transitions, and fail-closed semantics.

This leaf creates the seam only. It does not add an RPC, container service, command ledger, or remote execution protocol.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
prepareManagedAgentWorkspace(options) (existing compatibility export) #15048 plus the current startAgentProvisioned() consumer Preserve the current option names and six-field async return {repoPath, instanceHome, mcpMatrix, mcpPlan, hydration, artifacts}; resolve the injected MCP-matrix seam, project the closed logical input, call createManagedAgentWorkspacePlan() exactly once, then call applyManagedAgentWorkspacePlan() exactly once No legacy bypass: planner or apply rejection propagates as today; no launchable result is returned beside a failed plan/apply. Existing local/remote transition behavior stays unchanged Existing function JSDoc is updated to name the composition and link both new exports Existing preparation corpus remains green; a composition spy proves one planner call followed by one apply call and unchanged returned shape
createManagedAgentWorkspacePlan(input) (new pure export; exact schema below) #16176 / #16168 pure-plan boundary Validate a closed, path-free agent/MCP intent and return a recursively frozen logical plan containing only opaque agent id, curated harness/artifact profile, MCP enablement, relative entrypoint vocabulary, public remote URLs, and environment-slot names Synchronous TypeError for missing, unknown, malformed, absolute-path, owner/authorization, or secret-value fields; synchronous RangeError for an unsupported harness/MCP/transport combination. No partial result Full public JSDoc on the new export and its closed input/output typedefs; internal seam, so no separate learn guide Equal-input/deep-equal witness; recursive immutability witness; forbidden-field table; module-load deny hook plus throwing fs/env/process/global-config seams prove zero reads/effects
applyManagedAgentWorkspacePlan(options) (new host-effect export; exact schema below) #16176 / #16168 host-apply boundary plus the current convergence/safety corpus Validate the logical plan, bind repo/home/main-checkout/Node absolute paths, prove adapter/capability/executable safety, hydrate, converge artifacts and receipts, and return the existing six-field prepared result Reject with ManagedWorkspacePreparationError on invalid plan/binding, unsafe paths, unsupported capability, divergent owned content, or an effect failure. No rollback fiction: successfully completed hydration or atomic/create-only artifacts may remain as convergent partial state; retry must converge them, while no partial file bytes, secret value, or process spawn is permitted Full public JSDoc names host-only authority, bounded effect set, return schema, and partial-state/retry semantics Operation recorder proves the bounded effect set; negative controls retain no-unsafe-write behavior; injected mid-apply failure plus retry proves convergent partial state; existing artifact/receipt specs remain green
startAgentProvisioned(options) (existing sole production consumer) Current production call chain Continue preparing once, validate the canonical returned repoPath, inspect a prepared remote adapter when applicable, and call FleetLifecycleService.start() only after successful apply Any planner/apply rejection propagates; explicit no-spawn assertion; already-running and no-metadata.repo behavior remains unchanged Existing JSDoc updated only if the internal composition name changes Existing ordering specs plus one explicit planner-fail and one apply-fail no-spawn witness
Generated harness artifacts and transport receipts Current unit corpus and #16176 secret/path boundary Preserve byte/projection ownership, create-only/atomic-write/0600 rules, local-to-remote transitions, receipt replacement/removal, and the existing public artifact-state vocabulary Divergent resident-owned content still rejects; an interrupted operation never exposes partial bytes or secret material; retry reconverges completed atomic artifacts Generator/convergence JSDoc remains authoritative; new apply JSDoc links it Byte/projection snapshots and local/remote transition specs stay unchanged; negative symlink/divergence/permission controls remain fail-closed

New exported contract schemas

createManagedAgentWorkspacePlan({agent, mcpMatrix, mcpTarget = null} = {})

  • agent is exactly {id: String, harnessType: String}; unknown keys are rejected.
  • mcpMatrix is a closed Boolean record whose keys are the canonical MCP_SERVERS keys; missing canonical keys resolve through the existing catalog policy before this call, and unknown keys are rejected.
  • mcpTarget is null or exactly {kind: 'tenant', credentialEnvVar: String, resources: {'memory-core': {url: String}, 'knowledge-base': {url: String}}}. It contains the credential slot name, never a credential value.
  • Caller-supplied owner, ownerPrincipal, authorization/grant fields, credential/bearer values, repoPath, instanceRoot, mainCheckout, nodePath, cwd, command, args, and any absolute path are forbidden at every depth.
  • Return is recursively frozen and exactly: {agent: {id, harnessType}, artifactProfile: String, mcpMatrix: Record<String, Boolean>, mcpServers: Array<{key, name, enabled, target, transport, entrypoint, url, credentialEnvVar, runtimeEnv, requiredRuntimeEnv, secretEnv}>}. entrypoint is a repository-relative curated entrypoint or null; url is a public remote resource URL or null; every array/object is immutable. No host path, secret value, owner, grant, or executable command is present.

applyManagedAgentWorkspacePlan({plan, repoPath, instanceRoot, mainCheckout = DEFAULT_MAIN_CHECKOUT, nodePath = process.execPath, remoteMcpCapability = null, hydrateWorkspace = hydrateCurrentWorktree, deriveInstanceHome = deriveAgentInstanceHome, fileSystem = fs, log = () => {}} = {})

  • plan must satisfy the exact output schema above; structural clones are accepted after validation so this seam does not depend on object identity.
  • repoPath, instanceRoot, mainCheckout, and nodePath are absolute host bindings introduced only here.
  • remoteMcpCapability is the existing non-secret installed-adapter proof. The remaining injectable seams retain the current test/host behavior and are not serialized into the plan.
  • Return remains exactly Promise<{repoPath: String, instanceHome: String, mcpMatrix: Object, mcpPlan: Object[], hydration: Object, artifacts: Object[]}>. The returned mcpPlan is the host-bound execution projection and may contain absolute paths; the logical input plan never does.

Acceptance Criteria

  • A mechanical caller/effect census is captured in JSDoc or a test-owned table. It identifies the single production caller and every current effect class listed above.
  • A pure logical planner is exported and tested under throwing filesystem, environment, process, and global-config seams. Equal inputs produce deeply equal, immutable outputs.
  • The planner emits a closed schema containing only opaque seat/target identity, public URLs, environment-slot names, and curated harness/artifact intent. It rejects caller-supplied owner, absolute-path, bearer-value, and authorization fields.
  • Absolute host paths are introduced only by the host-owned apply edge.
  • An operation recorder or equivalent spy proves that planning performs zero effects and that apply performs only the expected bounded effect set.
  • Existing adapter and local↔remote transition coverage remains green with unchanged artifact/receipt semantics.
  • Negative controls for symlink/path escape, divergent content, missing capability/executable, and unsafe existing state remain fail-closed before unsafe writes or process spawn.
  • startAgentProvisioned() invokes the compatibility seam once and calls FleetLifecycleService.start() only after successful apply; a failed apply has an explicit no-spawn assertion.
  • No RPC, MCP tool, container service, or command ledger is added in this leaf.
  • This issue is linked as a child of #16168.

Out of Scope

  • Moving the planner into a running container.
  • Defining transport RPCs or a durable command ledger.
  • Fleet credential issuance, identity governance, or authorization policy.
  • Reworking unrelated Fleet lifecycle or SDK boundaries.

Avoided Traps

  • Do not move the monolith behind an RPC and call that containerization.
  • Do not serialize absolute host paths or bearer values into the logical plan.
  • Do not let the host apply edge re-derive agent identity or target policy.
  • Do not weaken current create-only, atomic-write, 0600, symlink, or containment guarantees.
  • Do not break the one-call compatibility surface merely to expose the internal split.
  • Do not prescribe a plan digest in this leaf before the later transport contract establishes what must be signed or replayed.

Decision Record Impact

No ADR amendment is required. This implements the plan/apply separation already graduated in #16176; container/RPC authority remains with parent epic #16168 and its later leaves.

Related

Creation-Gate Record

  • Prior-art sweep: source discussion, repository, issue archive, Memory Core, and live GitHub search completed.
  • Latest-open freshness: newest 20 of 305 open issues checked at 2026-08-08T16:11:37Z; no equivalent open leaf found.
  • A2A collision check: latest 100 messages checked; no peer claim on this seam.
  • Structure map: owning path confirmed as ai/services/fleet; prepareManagedAgentWorkspace.mjs and its sole production caller are sibling-owned there.

Origin Session ID

abdf06f7-5c90-4124-ad28-f0e2897214ee

Retrieval Hint

Query: Fleet prepareManagedAgentWorkspace pure plan host apply containerization filesystem effects #16168

tobiu referenced in commit 10591e8 - "refactor(fleet): split workspace plan from host apply (#16715) (#16731) on Aug 8, 2026, 10:11 PM
tobiu closed this issue on Aug 8, 2026, 10:11 PM