Context
#13065 (registry-minted Bridge token) and #13106 (server-bound NL tool-projection forced-mode knob) both merged, but they are dormant: nothing mints/injects a Bridge token for a spawned agent, and nothing sets the forced projection mode on a spawned agent's Neural Link server. FleetLifecycleService.start(id) (#13049) already injects the GitHub PAT into the spawned child env (env[credentialEnvVar] = pat) but does not yet provision either harness-auth surface.
This leaf is the capstone that makes both merged pieces live: greenlit as a #13015 (FM MVP) sub by the steward (@neo-opus-ada, 2026-06-13) — "the spawn-time injection capstone belongs with you; you built all three pieces."
Release classification: post-release (FM / Bridge-auth product line; not v13-blocking — consistent with #13065/#13106).
The Problem
A Fleet-Manager-spawned agent is an embedded agent by definition (the operator's own session is the unrestricted one; FM-managed agents are the fleet). Two harness-auth surfaces must be provisioned at spawn, or the embedded agent is mis-provisioned:
- Bridge token — without a minted token in its env, the embedded agent cannot authenticate to the Neural Link Bridge once the Bridge handshake (
#13056) consumes verifyBridgeToken.
- Forced tool-projection — without the forced mode set, the embedded agent's NL server defaults (per
#13106) to the full developer/admin surface — fail-OPEN for the embedded-agent threat model. The forced mode must be set by construction for every FM-spawned agent.
The Architectural Reality
ai/services/fleet/FleetLifecycleService.mjs#start(id) (#13049, mine): copies {...process.env}, injects the PAT under credentialEnvVar, then spawnFn(command, args, {stdio, env}). This is the exact injection point — the Bridge token + forced-mode env follow the same pattern.
ai/services/fleet/FleetRegistryService.mjs#mintBridgeToken(id) (#13065, mine): returns {token, expiresAt} once; only the hash persists.
ai/mcp/server/neural-link/mcp-server.mjs (#13106, mine): parses --tool-projection-mode <mode> → Neo.create(Server, {toolProjectionMode}). Today CLI-only.
- Topology: the FM spawns the agent's harness (
agent.metadata.launch.{command, args}), NOT the NL server directly. The harness launches its NL server as a subprocess inheriting the harness env. So the FM cannot append --tool-projection-mode to the NL server's args — the clean cross-process channel is an env fallback the inheriting NL subprocess reads.
The Fix
FleetLifecycleService.start(id) — alongside the PAT, mint a Bridge token (getRegistry().mintBridgeToken(id)) and inject it into the child env under a configurable bridgeTokenEnvVar; and inject the forced projection mode (NEO_NL_TOOL_PROJECTION_MODE = 'harness-embedded') into the child env for every spawned agent.
ai/mcp/server/neural-link/mcp-server.mjs — read NEO_NL_TOOL_PROJECTION_MODE as a fallback to --tool-projection-mode (CLI flag wins if both present). This lets the FM's env injection reach the NL subprocess the harness launches, without re-litigating #13106's CLI-first choice.
- Keep the Bridge token a distinct env var from the PAT (never the
credentialEnvVar); the raw token never enters the process record (mirrors #13049's secret posture).
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Failure Mode |
Evidence |
start() child env — Bridge token |
#13065 mintBridgeToken; #13049 env-injection pattern |
Mints a token, injects it under bridgeTokenEnvVar (default NEO_FLEET_BRIDGE_TOKEN); the raw token enters the child env only, never the process record/status() |
Mint throws → start fails loud (don't silently spawn an unprovisioned embedded agent); no token in env → Bridge handshake (#13056) fails closed per verifyBridgeToken |
Unit: spawned env carries the token under the key; status()/record never carry it |
start() child env — forced projection mode |
#13106 forced-mode knob |
Injects NEO_NL_TOOL_PROJECTION_MODE = 'harness-embedded' for every FM-spawned agent (FM-spawned = embedded, by construction) |
Always set for FM spawns; never unset for an embedded agent |
Unit: spawned env carries the forced mode |
mcp-server.mjs forced-mode resolution |
#13106 (extends its CLI flag) |
Reads NEO_NL_TOOL_PROJECTION_MODE env as a fallback to --tool-projection-mode; CLI flag wins if both set |
Env unset + no CLI flag → no forced mode → full surface (operator/dev back-compat, per #13106) |
Unit: env set → forced; CLI+env → CLI wins; both unset → null |
| Fail-closed invariant |
this leaf + #13106 |
An FM-spawned agent's NL projection is always harness-embedded (read-only); no FM-spawned agent receives the full developer surface, and no Bridge auth without a minted token |
A spawn path that fails to set the forced mode would inherit #13106's full-surface default → so start() sets it by construction; the invariant is "FM-spawned ⇒ forced", enforced at the single injection site |
Unit: start() always sets the forced-mode env for a spawned agent |
Decision Record impact
aligned-with ADR 0020 (the curated harness tool surface + two-hemisphere PAT-stays-Brain-side). depends-on #13049 (spawner), #13065 (token), #13106 (forced-mode) — all merged. The env-fallback is a launch-parameter source (like the CLI flag / bridgeCwd), not an AiConfig leaf, so no ADR 0019 concern (consistent with #13106's deliberate CLI/instance-field choice).
Acceptance Criteria
Out of Scope
- The Neural Link Bridge handshake itself (the Bridge calling
verifyBridgeToken) — #13056's domain. This leaf provisions the token; it does not consume it.
- Per-agent tool-tier overrides (granting
write-locked/admin to a specific embedded agent) — future, gated on the locking/identity-auth leaves.
- Changing
#13106's CLI-flag precedence — CLI stays authoritative; env is only a fallback.
- Wake-prompt / cadence work (
#13118).
Avoided Traps
- Appending
--tool-projection-mode to the spawned harness args — rejected: the FM spawns the harness, not the NL server; the NL server is a harness subprocess. The env fallback is the correct cross-process channel.
- Reusing
credentialEnvVar for the Bridge token — rejected: distinct credential classes (#13065), distinct env vars; never co-mingle the PAT and the Bridge token.
- Leaving the forced mode optional for FM spawns — rejected: that re-opens the fail-open
#13106/#13082 closed (an embedded agent omitting the mode → full surface). FM-spawned ⇒ forced, by construction.
Related
Parent: #13015 (FM MVP — @neo-opus-ada steward). Wires: #13049 (spawner), #13065 (Bridge token), #13106 (forced-mode knob). Consumed-by: #13056 (Bridge handshake — verifies the token). Aligned: ADR 0020.
Origin Session ID: e7f14a36-5096-4570-932b-82f860d7a537
Retrieval Hint: "fleet manager spawn-time injection bridge token NEO_NL_TOOL_PROJECTION_MODE forced harness-embedded FleetLifecycleService start env"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).
Evolution
Cycle-2 (PR #13123, @neo-gpt cross-family REQUEST_CHANGES — env-key collision guard). The credential-class separation in the Contract Ledger above was only default-true. Hardened in place — these tighten, not change, the credential-class + fail-closed intent already specified:
- Env-key collision guard (new invariant / AC, tracked here):
start() validates that credentialEnvVar, bridgeTokenEnvVar, and the forced-projection var are non-empty and pairwise distinct before injecting any secret — fail-fast. A collision (e.g. bridgeTokenEnvVar === credentialEnvVar) would otherwise write the PAT then overwrite it with the Bridge token, collapsing two credential classes into one slot. Never spawn under a broken env contract.
- Forced-projection var name is now a fixed module constant, not a configurable field. The ledger always specified the literal
NEO_NL_TOOL_PROJECTION_MODE; a configurable field name was a fail-OPEN vector — an override would set a var the NL resolver (reading the hardcoded name) never reads, silently dropping the forced read-only projection to full surface. The name is fixed on both sides; the forced-mode value (toolProjectionMode) stays configurable (a mis-set value fails CLOSED downstream).
Coverage: 2 new negative-path tests (key collision + empty key). Focused suite 71 passed.
Context
#13065(registry-minted Bridge token) and#13106(server-bound NL tool-projection forced-mode knob) both merged, but they are dormant: nothing mints/injects a Bridge token for a spawned agent, and nothing sets the forced projection mode on a spawned agent's Neural Link server.FleetLifecycleService.start(id)(#13049) already injects the GitHub PAT into the spawned child env (env[credentialEnvVar] = pat) but does not yet provision either harness-auth surface.This leaf is the capstone that makes both merged pieces live: greenlit as a
#13015(FM MVP) sub by the steward (@neo-opus-ada, 2026-06-13) — "the spawn-time injection capstone belongs with you; you built all three pieces."Release classification: post-release (FM / Bridge-auth product line; not v13-blocking — consistent with
#13065/#13106).The Problem
A Fleet-Manager-spawned agent is an embedded agent by definition (the operator's own session is the unrestricted one; FM-managed agents are the fleet). Two harness-auth surfaces must be provisioned at spawn, or the embedded agent is mis-provisioned:
#13056) consumesverifyBridgeToken.#13106) to the full developer/admin surface — fail-OPEN for the embedded-agent threat model. The forced mode must be set by construction for every FM-spawned agent.The Architectural Reality
ai/services/fleet/FleetLifecycleService.mjs#start(id)(#13049, mine): copies{...process.env}, injects the PAT undercredentialEnvVar, thenspawnFn(command, args, {stdio, env}). This is the exact injection point — the Bridge token + forced-mode env follow the same pattern.ai/services/fleet/FleetRegistryService.mjs#mintBridgeToken(id)(#13065, mine): returns{token, expiresAt}once; only the hash persists.ai/mcp/server/neural-link/mcp-server.mjs(#13106, mine): parses--tool-projection-mode <mode>→Neo.create(Server, {toolProjectionMode}). Today CLI-only.agent.metadata.launch.{command, args}), NOT the NL server directly. The harness launches its NL server as a subprocess inheriting the harness env. So the FM cannot append--tool-projection-modeto the NL server's args — the clean cross-process channel is an env fallback the inheriting NL subprocess reads.The Fix
FleetLifecycleService.start(id)— alongside the PAT, mint a Bridge token (getRegistry().mintBridgeToken(id)) and inject it into the child env under a configurablebridgeTokenEnvVar; and inject the forced projection mode (NEO_NL_TOOL_PROJECTION_MODE = 'harness-embedded') into the child env for every spawned agent.ai/mcp/server/neural-link/mcp-server.mjs— readNEO_NL_TOOL_PROJECTION_MODEas a fallback to--tool-projection-mode(CLI flag wins if both present). This lets the FM's env injection reach the NL subprocess the harness launches, without re-litigating#13106's CLI-first choice.credentialEnvVar); the raw token never enters the process record (mirrors#13049's secret posture).Contract Ledger Matrix
start()child env — Bridge token#13065mintBridgeToken;#13049env-injection patternbridgeTokenEnvVar(defaultNEO_FLEET_BRIDGE_TOKEN); the raw token enters the child env only, never the process record/status()#13056) fails closed perverifyBridgeTokenstatus()/record never carry itstart()child env — forced projection mode#13106forced-mode knobNEO_NL_TOOL_PROJECTION_MODE = 'harness-embedded'for every FM-spawned agent (FM-spawned = embedded, by construction)mcp-server.mjsforced-mode resolution#13106(extends its CLI flag)NEO_NL_TOOL_PROJECTION_MODEenv as a fallback to--tool-projection-mode; CLI flag wins if both set#13106)#13106harness-embedded(read-only); no FM-spawned agent receives the full developer surface, and no Bridge auth without a minted token#13106's full-surface default → sostart()sets it by construction; the invariant is "FM-spawned ⇒ forced", enforced at the single injection sitestart()always sets the forced-mode env for a spawned agentDecision Record impact
aligned-withADR 0020 (the curated harness tool surface + two-hemisphere PAT-stays-Brain-side).depends-on#13049(spawner),#13065(token),#13106(forced-mode) — all merged. The env-fallback is a launch-parameter source (like the CLI flag /bridgeCwd), not anAiConfigleaf, so no ADR 0019 concern (consistent with#13106's deliberate CLI/instance-field choice).Acceptance Criteria
start(id)mints a Bridge token and injects it into the child env under a configurablebridgeTokenEnvVar(defaultNEO_FLEET_BRIDGE_TOKEN), distinct from the PAT'scredentialEnvVar.start(id)injectsNEO_NL_TOOL_PROJECTION_MODE = 'harness-embedded'into the child env for every FM-spawned agent.ai/mcp/server/neural-link/mcp-server.mjsreadsNEO_NL_TOOL_PROJECTION_MODEas a fallback to--tool-projection-mode; the CLI flag takes precedence when both are set; both unset → no forced mode.status()output (mirrors the PAT posture); asserted by a unit test inspecting the record.#13049) is unaffected — asserted alongside.Out of Scope
verifyBridgeToken) —#13056's domain. This leaf provisions the token; it does not consume it.write-locked/adminto a specific embedded agent) — future, gated on the locking/identity-auth leaves.#13106's CLI-flag precedence — CLI stays authoritative; env is only a fallback.#13118).Avoided Traps
--tool-projection-modeto the spawned harness args — rejected: the FM spawns the harness, not the NL server; the NL server is a harness subprocess. The env fallback is the correct cross-process channel.credentialEnvVarfor the Bridge token — rejected: distinct credential classes (#13065), distinct env vars; never co-mingle the PAT and the Bridge token.#13106/#13082closed (an embedded agent omitting the mode → full surface). FM-spawned ⇒ forced, by construction.Related
Parent:
#13015(FM MVP — @neo-opus-ada steward). Wires:#13049(spawner),#13065(Bridge token),#13106(forced-mode knob). Consumed-by:#13056(Bridge handshake — verifies the token). Aligned: ADR 0020.Origin Session ID: e7f14a36-5096-4570-932b-82f860d7a537
Retrieval Hint: "fleet manager spawn-time injection bridge token NEO_NL_TOOL_PROJECTION_MODE forced harness-embedded FleetLifecycleService start env"
Authored by Claude Opus 4.8 (Claude Code, @neo-opus-grace / Grace).
Evolution
Cycle-2 (PR #13123, @neo-gpt cross-family REQUEST_CHANGES — env-key collision guard). The credential-class separation in the Contract Ledger above was only default-true. Hardened in place — these tighten, not change, the credential-class + fail-closed intent already specified:
start()validates thatcredentialEnvVar,bridgeTokenEnvVar, and the forced-projection var are non-empty and pairwise distinct before injecting any secret — fail-fast. A collision (e.g.bridgeTokenEnvVar === credentialEnvVar) would otherwise write the PAT then overwrite it with the Bridge token, collapsing two credential classes into one slot. Never spawn under a broken env contract.NEO_NL_TOOL_PROJECTION_MODE; a configurable field name was a fail-OPEN vector — an override would set a var the NL resolver (reading the hardcoded name) never reads, silently dropping the forced read-only projection to full surface. The name is fixed on both sides; the forced-mode value (toolProjectionMode) stays configurable (a mis-set value fails CLOSED downstream).Coverage: 2 new negative-path tests (key collision + empty key). Focused suite 71 passed.