Skip to content

feat(0.21.0): MCP delegations dispatch into caller's fleet workspace#50

Merged
tangletools merged 2 commits into
mainfrom
feat/mcp-fleet-aware-delegation
May 24, 2026
Merged

feat(0.21.0): MCP delegations dispatch into caller's fleet workspace#50
tangletools merged 2 commits into
mainfrom
feat/mcp-fleet-aware-delegation

Conversation

@tangletools
Copy link
Copy Markdown
Contributor

Summary

agent-runtime-mcp previously created a fresh sibling sandbox for every delegate_code / delegate_research call. Diffs landed on the worker's filesystem and the caller had to pull them back through the structured tool result.

With this change, when the parent sandbox sets TANGLE_FLEET_ID on the MCP server's env, delegations dispatch onto existing machines in the caller's fleet via fleet.sandbox(machineId).streamPrompt(...). The fleet's shared-workspace policy means worker machines mount the same filesystem as the caller — diffs land in-place, no cross-sandbox copy step.

Sibling-sandbox mode is still the default when TANGLE_FLEET_ID is unset.

Detection mechanism

TANGLE_FLEET_ID env var. When present, the bin calls client.fleets.get(fleetId) to resolve a structural FleetHandle ({ fleetId, ids[], sandbox(machineId) }) and builds a fleet-workspace executor around it. Optional TANGLE_FLEET_EXCLUDE_MACHINES skips the coordinator machine from round-robin.

Fleet mode without TANGLE_API_KEY fails loud rather than silently degrading.

Executor abstraction

interface DelegationExecutor {
  readonly client: LoopSandboxClient   // kernel consumes this
  describe(): string                    // stderr boot tag
}

createSiblingSandboxExecutor({ client })
createFleetWorkspaceExecutor({ fleet, excludeMachineIds, selectMachine? })
detectExecutor({ sandboxClient, env, resolveFleet? })

LoopSandboxClient gains an optional describePlacement(box) so the kernel emits a new loop.iteration.dispatch trace event carrying either { placement: 'sibling', sandboxId } or { placement: 'fleet', fleetId, machineId, sandboxId }. Raw Sandbox SDK clients without the method default to sibling — backward compatible.

createDefaultCoderDelegate accepts executor (new) OR sandboxClient (legacy shorthand, wraps in a sibling executor).

What is NOT in scope

  • The kernel (src/loops/) is unchanged structurally — only the new trace event variant + the optional describePlacement hook.
  • Sibling-sandbox mode remains the default and is fully wired.
  • No npm publish — version bumped to 0.21.0 locally, release happens separately.

Test plan

  • pnpm typecheck clean
  • pnpm test — 235 passed (215 existing + 20 new across tests/mcp/fleet-detection.test.ts + tests/mcp/fleet-executor.test.ts)
  • pnpm build clean
  • pnpm exec biome check src tests clean
  • Real-credential smoke against a fleet-mounted MCP — deferred; requires a live fleet workspace which isn't available in this session
  • tax-orchestrator (or other consumer) sets TANGLE_FLEET_ID in AgentProfile.mcpServers[].env and confirms agent-runtime-mcp: fleet-aware delegation: fleetId=... lands on stderr at boot

drewstone added 2 commits May 24, 2026 14:56
`agent-runtime-mcp` previously always called `client.create(...)` per
delegation, spawning each worker as a SIBLING sandbox of the caller.
Diffs landed on the worker's filesystem and the caller had to pull them
back through the structured tool result.

Switch to a placement abstraction. When the parent sandbox sets
`TANGLE_FLEET_ID` on the MCP server's env, `delegate_code` and
`delegate_research` now resolve workers via `client.fleets.get(id)` and
dispatch onto existing fleet machines through `fleet.sandbox(machineId)`.
The fleet's shared-workspace policy means the worker machine mounts the
same filesystem as the caller — diffs land in-place, no cross-sandbox
copy step.

- `LoopSandboxClient` gains an optional `describePlacement(box)` so the
  kernel emits the new `loop.iteration.dispatch` trace event with either
  `{ placement: 'sibling', sandboxId }` or
  `{ placement: 'fleet', fleetId, machineId, sandboxId }`. Sandbox SDK
  consumers without the method default to sibling.
- `DelegationExecutor` abstracts the two modes:
  `createSiblingSandboxExecutor({ client })` and
  `createFleetWorkspaceExecutor({ fleet, excludeMachineIds })`. The
  fleet executor round-robins through `fleet.ids` (skipping the
  coordinator) and records machineId-by-sandboxId so the trace event
  can recover the assignment when the kernel hands the SandboxInstance
  back.
- `createDefaultCoderDelegate` accepts either `executor` (new) or
  `sandboxClient` (legacy shorthand — wraps in a sibling executor).
- `detectExecutor({ sandboxClient, env })` picks the right executor
  from env vars; exported so custom bin entry points get the same
  detection.
- The bin logs `fleet-aware delegation: fleetId=...` and the executor's
  `describe()` to stderr at startup so operators can confirm placement.
- Fleet mode without `TANGLE_API_KEY` fails loud rather than silently
  degrading to sibling mode.

Detection mechanism: `TANGLE_FLEET_ID` env var, with optional
`TANGLE_FLEET_EXCLUDE_MACHINES` to skip the coordinator machine.

20 new tests cover sibling placement, fleet round-robin, machine-id
recovery in the trace event, exclusion failure, custom selectMachine,
sandbox-resolution errors, env detection with whitespace handling, and
the structural-shape guards on `client.fleets.get`.

Existing 215 tests pass unchanged. Build + typecheck + biome clean.
…l peer

`@tangle-network/agent-knowledge` is an optional peerDep added to
package.json a few releases ago but pnpm-lock.yaml was never refreshed.
CI runs `pnpm install --frozen-lockfile` which trips on the specifier
mismatch and fails before any tests run.

`pnpm install` with `autoInstallPeers: true` (the repo default) resolves
the peer to 1.4.0; record that in the lockfile. No code or runtime
change.
@tangletools tangletools merged commit 0f33489 into main May 24, 2026
1 check passed
@tangletools tangletools deleted the feat/mcp-fleet-aware-delegation branch May 24, 2026 21:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants