@tangle-network/agent-runtime 0.20.4 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/dist/{chunk-VFUEE6DF.js → chunk-EDVCVFQB.js} +36 -1
- package/dist/chunk-EDVCVFQB.js.map +1 -0
- package/dist/{chunk-LPPM7EGS.js → chunk-QDNJLAEU.js} +134 -4
- package/dist/chunk-QDNJLAEU.js.map +1 -0
- package/dist/loops.d.ts +5 -3
- package/dist/loops.js +1 -1
- package/dist/mcp/bin.js +27 -6
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +136 -4
- package/dist/mcp/index.js +8 -2
- package/dist/profiles.d.ts +1 -1
- package/dist/{types-Bx-tArkc.d.ts → types-Cu-SkGa0.d.ts} +39 -1
- package/package.json +1 -1
- package/dist/chunk-LPPM7EGS.js.map +0 -1
- package/dist/chunk-VFUEE6DF.js.map +0 -1
package/README.md
CHANGED
|
@@ -219,10 +219,89 @@ delegate. Environment knobs:
|
|
|
219
219
|
|
|
220
220
|
- `TANGLE_API_KEY` — required (unless both `MCP_DISABLE_*` are set)
|
|
221
221
|
- `SANDBOX_BASE_URL` — sandbox-SDK base URL override
|
|
222
|
+
- `TANGLE_FLEET_ID` — switches placement from sibling-sandbox to fleet-workspace (see [Placement modes](#placement-modes))
|
|
223
|
+
- `TANGLE_FLEET_EXCLUDE_MACHINES` — comma-separated machine ids to skip during fleet-mode round-robin (typically the coordinator)
|
|
222
224
|
- `MCP_MAX_CONCURRENT_SANDBOXES` — kernel `maxConcurrency` cap (default 4)
|
|
223
225
|
- `MCP_CODER_FANOUT_HARNESSES` — comma-separated harness ids for `variants > 1`
|
|
224
226
|
- `MCP_DISABLE_CODER` / `MCP_DISABLE_RESEARCHER` — omit the matching tool
|
|
225
227
|
|
|
228
|
+
### Placement modes
|
|
229
|
+
|
|
230
|
+
Where worker iterations land — sibling sandboxes vs the caller's fleet
|
|
231
|
+
workspace — is controlled by `TANGLE_FLEET_ID`.
|
|
232
|
+
|
|
233
|
+
**Sibling-sandbox mode (default).** No `TANGLE_FLEET_ID` set. Every
|
|
234
|
+
`delegate_code` / `delegate_research` call invokes `sandboxClient.create(...)`
|
|
235
|
+
and runs the worker in a fresh sandbox. The worker's diff lives in the
|
|
236
|
+
worker's filesystem; the caller pulls it back via the structured tool
|
|
237
|
+
result. Use this when the MCP server runs as a standalone CLI mounted
|
|
238
|
+
outside a fleet (developer workflows, single-process integrations).
|
|
239
|
+
|
|
240
|
+
**Fleet-workspace mode.** `TANGLE_FLEET_ID` set by the parent sandbox when
|
|
241
|
+
it launches the MCP server. Each delegation dispatches onto an existing
|
|
242
|
+
machine in that fleet via `fleet.sandbox(machineId).streamPrompt(...)`.
|
|
243
|
+
The fleet's shared-workspace policy means worker machines mount the same
|
|
244
|
+
filesystem as the caller — diffs land in-place, no cross-sandbox copy
|
|
245
|
+
step. The bin logs `fleet-aware delegation: fleetId=...` to stderr on
|
|
246
|
+
startup so the operator can confirm the placement.
|
|
247
|
+
|
|
248
|
+
Pass `TANGLE_FLEET_ID` from a parent sandbox's `AgentProfile.mcpServers`
|
|
249
|
+
config:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import { defineAgentProfile } from '@tangle-network/sandbox'
|
|
253
|
+
|
|
254
|
+
const parentProfile = defineAgentProfile({
|
|
255
|
+
name: 'tax-orchestrator',
|
|
256
|
+
mcp: {
|
|
257
|
+
'agent-runtime': {
|
|
258
|
+
transport: 'stdio',
|
|
259
|
+
command: 'agent-runtime-mcp',
|
|
260
|
+
env: {
|
|
261
|
+
TANGLE_API_KEY: '${TANGLE_API_KEY}',
|
|
262
|
+
TANGLE_FLEET_ID: '${TANGLE_FLEET_ID}', // injected by orchestrator
|
|
263
|
+
TANGLE_FLEET_EXCLUDE_MACHINES: 'coordinator', // skip the machine running this MCP server
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For non-bin entry points, wire an executor directly:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { Sandbox } from '@tangle-network/sandbox'
|
|
274
|
+
import {
|
|
275
|
+
createMcpServer,
|
|
276
|
+
createDefaultCoderDelegate,
|
|
277
|
+
createFleetWorkspaceExecutor,
|
|
278
|
+
createSiblingSandboxExecutor,
|
|
279
|
+
detectExecutor,
|
|
280
|
+
} from '@tangle-network/agent-runtime/mcp'
|
|
281
|
+
|
|
282
|
+
const sandboxClient = new Sandbox({ apiKey: process.env.TANGLE_API_KEY! })
|
|
283
|
+
|
|
284
|
+
// Either pick automatically from env:
|
|
285
|
+
const executor = await detectExecutor({ sandboxClient })
|
|
286
|
+
|
|
287
|
+
// Or pin it explicitly:
|
|
288
|
+
const fleet = await sandboxClient.fleets.get(process.env.TANGLE_FLEET_ID!)
|
|
289
|
+
const fleetExecutor = createFleetWorkspaceExecutor({
|
|
290
|
+
fleet,
|
|
291
|
+
excludeMachineIds: ['coordinator'],
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const server = createMcpServer({
|
|
295
|
+
coderDelegate: createDefaultCoderDelegate({ executor: fleetExecutor }),
|
|
296
|
+
})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The kernel emits a `loop.iteration.dispatch` trace event for every
|
|
300
|
+
iteration: `{ placement: 'sibling', sandboxId }` in sibling mode,
|
|
301
|
+
`{ placement: 'fleet', fleetId, machineId, sandboxId }` in fleet mode.
|
|
302
|
+
Analyst loops use this to correlate worker activity with the caller's
|
|
303
|
+
machine.
|
|
304
|
+
|
|
226
305
|
### Async semantics
|
|
227
306
|
|
|
228
307
|
Coder + researcher delegations are **fire-and-poll**. The handler returns
|
|
@@ -342,6 +421,10 @@ Runnable in [`examples/`](./examples/). Every example imports from
|
|
|
342
421
|
- [`model-resolution/`](./examples/model-resolution/) — router catalog + fail-closed admission
|
|
343
422
|
- [`agent-into-reviewer/`](./examples/agent-into-reviewer/) — pipe one runtime's stream into a reviewer agent
|
|
344
423
|
- [`chat-handler/`](./examples/chat-handler/) — `handleChatTurn` (the centerpiece production pattern)
|
|
424
|
+
- [`coder-loop/`](./examples/coder-loop/) — `coderProfile` + `runLoop` + `FanoutVote` (driven-loop kernel)
|
|
425
|
+
- [`researcher-loop/`](./examples/researcher-loop/) — `researcherProfile` + `runLoop` + `FanoutVote` (peer dep: `@tangle-network/agent-knowledge`)
|
|
426
|
+
- [`mcp-delegation/`](./examples/mcp-delegation/) — mount `agent-runtime-mcp` in a product `AgentProfile` + stdio `tools/list` smoke
|
|
427
|
+
- [`fleet-delegation/`](./examples/fleet-delegation/) — `TANGLE_FLEET_ID` env flip + `createFleetWorkspaceExecutor` topology
|
|
345
428
|
|
|
346
429
|
## Tests
|
|
347
430
|
|
|
@@ -181,6 +181,20 @@ async function executeIteration(args) {
|
|
|
181
181
|
});
|
|
182
182
|
try {
|
|
183
183
|
const box = await createSandboxForSpec(args.ctx.sandboxClient, spec, args.signal);
|
|
184
|
+
const placement = describePlacementSafe(args.ctx.sandboxClient, box);
|
|
185
|
+
await emitTrace(args.ctx.traceEmitter, {
|
|
186
|
+
kind: "loop.iteration.dispatch",
|
|
187
|
+
runId: args.runId,
|
|
188
|
+
timestamp: args.now(),
|
|
189
|
+
payload: {
|
|
190
|
+
iterationIndex: args.item.index,
|
|
191
|
+
agentRunName: slot.agentRunName,
|
|
192
|
+
placement: placement.kind,
|
|
193
|
+
sandboxId: placement.sandboxId,
|
|
194
|
+
fleetId: placement.fleetId,
|
|
195
|
+
machineId: placement.machineId
|
|
196
|
+
}
|
|
197
|
+
});
|
|
184
198
|
const message = spec.taskToPrompt(args.item.task);
|
|
185
199
|
const events = [];
|
|
186
200
|
for await (const event of box.streamPrompt(message, { signal: args.signal })) {
|
|
@@ -219,6 +233,27 @@ async function executeIteration(args) {
|
|
|
219
233
|
});
|
|
220
234
|
}
|
|
221
235
|
}
|
|
236
|
+
function describePlacementSafe(client, box) {
|
|
237
|
+
if (typeof client.describePlacement === "function") {
|
|
238
|
+
try {
|
|
239
|
+
const result = client.describePlacement(box);
|
|
240
|
+
if (result && typeof result === "object" && (result.kind === "sibling" || result.kind === "fleet")) {
|
|
241
|
+
return {
|
|
242
|
+
kind: result.kind,
|
|
243
|
+
sandboxId: result.sandboxId ?? readSandboxId(box),
|
|
244
|
+
fleetId: result.fleetId,
|
|
245
|
+
machineId: result.machineId
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return { kind: "sibling", sandboxId: readSandboxId(box) };
|
|
252
|
+
}
|
|
253
|
+
function readSandboxId(box) {
|
|
254
|
+
const raw = box.id;
|
|
255
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
256
|
+
}
|
|
222
257
|
async function createSandboxForSpec(client, spec, signal) {
|
|
223
258
|
const overrides = spec.sandboxOverrides ?? {};
|
|
224
259
|
const overrideBackend = overrides.backend;
|
|
@@ -370,4 +405,4 @@ export {
|
|
|
370
405
|
refineWinnerIndex,
|
|
371
406
|
runLoop
|
|
372
407
|
};
|
|
373
|
-
//# sourceMappingURL=chunk-
|
|
408
|
+
//# sourceMappingURL=chunk-EDVCVFQB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/loops/drivers/refine.ts","../src/loops/run-loop.ts"],"sourcesContent":["/**\n * @experimental\n *\n * Refine driver — single task per iteration, validator-gated.\n *\n * `plan` returns `[task]` (possibly transformed via `refineTask`) until the\n * prior verdict is valid OR the local cap is hit, then `[]`.\n * `decide` returns `'stop'` once the latest verdict is valid OR the cap is\n * reached. The kernel's `maxIterations` is an orthogonal safety cap;\n * whichever is lower wins.\n */\n\nimport { ValidationError } from '../../errors'\nimport type { DefaultVerdict, Driver, Iteration } from '../types'\n\nexport type RefineDecision = 'continue' | 'stop'\n\n/** @experimental */\nexport interface CreateRefineDriverOptions<Task> {\n /** Hard cap on iterations. Default 5. */\n maxIterations?: number\n /**\n * Optional task transform applied each round based on the prior verdict.\n * When omitted, the same task is replayed and the agent is expected to\n * inspect the sandbox session state for prior attempts.\n */\n refineTask?: (task: Task, prior: DefaultVerdict) => Task\n /** Stable identifier surfaced in trace events. Default `'refine'`. */\n name?: string\n}\n\n/** @experimental */\nexport function createRefineDriver<Task, Output>(\n options: CreateRefineDriverOptions<Task> = {},\n): Driver<Task, Output, RefineDecision> {\n const maxIterations = options.maxIterations ?? 5\n if (!Number.isFinite(maxIterations) || maxIterations <= 0) {\n throw new ValidationError('createRefineDriver: maxIterations must be > 0')\n }\n const refineTask = options.refineTask\n return {\n name: options.name ?? 'refine',\n async plan(task, history) {\n if (history.length >= maxIterations) return []\n if (history.length === 0) return [task]\n const prior = history.at(-1)\n if (!prior) return [task]\n if (prior.verdict?.valid === true) return []\n // Worker error: replay the same task so the agent can self-correct.\n // The driver has no signal beyond `verdict`; only the validator\n // controls \"good enough\".\n if (!refineTask || !prior.verdict) return [prior.task]\n return [refineTask(prior.task, prior.verdict)]\n },\n decide(history) {\n const last = history.at(-1)\n if (!last) return 'continue'\n if (last.verdict?.valid === true) return 'stop'\n if (history.length >= maxIterations) return 'stop'\n return 'continue'\n },\n }\n}\n\n/**\n * Test helper: select the last-valid iteration (or the last attempt if\n * none passed). Mirrors the kernel's default selector ordering for refine\n * topologies — the most recent successful attempt wins.\n *\n * @experimental\n */\nexport function refineWinnerIndex<Task, Output>(\n iterations: ReadonlyArray<Iteration<Task, Output>>,\n): number | undefined {\n for (let i = iterations.length - 1; i >= 0; i -= 1) {\n if (iterations[i]?.verdict?.valid) return i\n }\n return iterations.length > 0 ? iterations.length - 1 : undefined\n}\n","/**\n * @experimental\n *\n * `runLoop` — the topology-agnostic kernel built atop the sandbox SDK.\n *\n * Each iteration:\n * 1. `driver.plan(task, history)` → N tasks (1 = refine, N = fanout, 0 = stop)\n * 2. For each task (parallel, bounded by `maxConcurrency`):\n * a. round-robin an `AgentRunSpec` from `agentRuns`\n * b. `sandboxClient.create({ backend: { profile }, ...overrides })`\n * c. emit `loop.iteration.dispatch` with the placement\n * (`{ sibling, sandboxId }` or `{ fleet, fleetId, machineId, sandboxId }`)\n * d. iterate `box.streamPrompt(taskToPrompt(task))` and collect events\n * 3. `output.parse(events)` → typed `Output`\n * 4. `validator?.validate(output)` → `DefaultVerdict`\n * 5. Append `Iteration` to history; emit `loop.iteration.ended`\n * 6. `driver.decide(history)` → if terminal, return result + winner\n *\n * The kernel owns: iteration accounting, per-iteration timing, error\n * capture, abort propagation, concurrency cap, cost aggregation, and trace\n * emission. The kernel does NOT own: what the agent runs (sandbox SDK +\n * profile), how outputs are decoded (output adapter), how outputs are\n * scored (validator), or topology (driver).\n */\n\nimport type {\n AgentProfile,\n CreateSandboxOptions,\n SandboxEvent,\n SandboxInstance,\n} from '@tangle-network/sandbox'\nimport { ValidationError } from '../errors'\nimport type { RuntimeStreamEvent } from '../types'\nimport type {\n AgentRunSpec,\n Driver,\n ExecCtx,\n Iteration,\n LoopResult,\n LoopSandboxClient,\n LoopSandboxPlacement,\n LoopTraceEmitter,\n LoopTraceEvent,\n LoopWinner,\n OutputAdapter,\n Validator,\n} from './types'\n\nconst DEFAULT_MAX_ITERATIONS = 10\nconst DEFAULT_MAX_CONCURRENCY = 4\n\n/** @experimental */\nexport interface RunLoopOptions<Task, Output, Decision> {\n driver: Driver<Task, Output, Decision>\n /**\n * Single agent spec — every iteration uses this profile. Mutually\n * exclusive with `agentRuns`.\n */\n agentRun?: AgentRunSpec<Task>\n /**\n * Multiple specs for heterogeneous fanout. The kernel round-robins\n * through them when the driver plans N tasks. Mutually exclusive with\n * `agentRun`.\n */\n agentRuns?: AgentRunSpec<Task>[]\n output: OutputAdapter<Output>\n validator?: Validator<Output>\n task: Task\n ctx: ExecCtx\n /** Default 10. Hard cap on total iterations across all `plan()` rounds. */\n maxIterations?: number\n /** Default 4. In-flight worker cap within a single `plan()` batch. */\n maxConcurrency?: number\n /**\n * Pre-allocated id for trace correlation. Default = `loop-${random}`.\n * Surfaces as `runId` on every emitted `LoopTraceEvent`.\n */\n runId?: string\n /**\n * Clock override; default `Date.now`. Deterministic tests pass a\n * monotonic counter to stabilize iteration timing fields.\n */\n now?: () => number\n /**\n * Override the default winner selector (highest-valid-score, ties broken\n * by earliest iteration).\n */\n selectWinner?: (iterations: Iteration<Task, Output>[]) => LoopWinner<Task, Output> | undefined\n}\n\n/** @experimental */\nexport async function runLoop<Task, Output, Decision>(\n options: RunLoopOptions<Task, Output, Decision>,\n): Promise<LoopResult<Task, Output, Decision>> {\n const specs = resolveAgentRuns(options)\n const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS\n if (!Number.isFinite(maxIterations) || maxIterations <= 0) {\n throw new ValidationError('runLoop: maxIterations must be > 0')\n }\n const maxConcurrency = options.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY\n if (!Number.isFinite(maxConcurrency) || maxConcurrency <= 0) {\n throw new ValidationError('runLoop: maxConcurrency must be > 0')\n }\n if (!options.ctx?.sandboxClient || typeof options.ctx.sandboxClient.create !== 'function') {\n throw new ValidationError('runLoop: ctx.sandboxClient.create is required')\n }\n const now = options.now ?? Date.now\n const runId = options.runId ?? `loop-${randomSuffix()}`\n const loopStart = now()\n const driverName = options.driver.name ?? 'driver'\n const iterations: Iteration<Task, Output>[] = []\n\n await emitTrace(options.ctx.traceEmitter, {\n kind: 'loop.started',\n runId,\n timestamp: now(),\n payload: {\n driver: driverName,\n agentRunNames: specs.map((spec) => spec.name ?? spec.profile.name ?? 'agent'),\n maxIterations,\n maxConcurrency,\n },\n })\n\n const controller = new AbortController()\n const onOuterAbort = () => controller.abort()\n if (options.ctx.signal) {\n if (options.ctx.signal.aborted) controller.abort()\n else options.ctx.signal.addEventListener('abort', onOuterAbort, { once: true })\n }\n\n try {\n while (iterations.length < maxIterations) {\n if (controller.signal.aborted) throwAbort()\n const planned = await options.driver.plan(options.task, iterations)\n if (planned.length === 0) break\n\n const remaining = maxIterations - iterations.length\n const slice = planned.slice(0, remaining)\n const baseIndex = iterations.length\n // Reserve slots up front so concurrent workers may mutate by index.\n for (let i = 0; i < slice.length; i += 1) {\n const spec = specs[(baseIndex + i) % specs.length]!\n iterations.push({\n index: baseIndex + i,\n task: slice[i] as Task,\n agentRunName: spec.name ?? spec.profile.name ?? 'agent',\n events: [],\n startedAt: now(),\n endedAt: 0,\n costUsd: 0,\n })\n }\n\n await runBatch({\n slice,\n baseIndex,\n iterations,\n specs,\n output: options.output,\n validator: options.validator,\n maxConcurrency,\n signal: controller.signal,\n ctx: options.ctx,\n runId,\n now,\n })\n\n if (controller.signal.aborted) throwAbort()\n\n const decision = await options.driver.decide(iterations)\n await emitTrace(options.ctx.traceEmitter, {\n kind: 'loop.decision',\n runId,\n timestamp: now(),\n payload: { decision: serializeDecision(decision), historyLength: iterations.length },\n })\n if (isTerminalDecision(decision)) {\n return finalize({\n options,\n decision,\n iterations,\n startMs: loopStart,\n now,\n runId,\n })\n }\n }\n\n if (iterations.length >= maxIterations) {\n // Cap reached without a terminal decision — ask the driver one more time\n // for its final state, then close out.\n const decision = await options.driver.decide(iterations)\n await emitTrace(options.ctx.traceEmitter, {\n kind: 'loop.decision',\n runId,\n timestamp: now(),\n payload: { decision: serializeDecision(decision), historyLength: iterations.length },\n })\n return finalize({ options, decision, iterations, startMs: loopStart, now, runId })\n }\n // `plan()` returned `[]` before `decide()` reached a terminal state.\n const decision = await options.driver.decide(iterations)\n await emitTrace(options.ctx.traceEmitter, {\n kind: 'loop.decision',\n runId,\n timestamp: now(),\n payload: { decision: serializeDecision(decision), historyLength: iterations.length },\n })\n return finalize({ options, decision, iterations, startMs: loopStart, now, runId })\n } finally {\n if (options.ctx.signal) options.ctx.signal.removeEventListener('abort', onOuterAbort)\n }\n}\n\ninterface RunBatchArgs<Task, Output> {\n slice: Task[]\n baseIndex: number\n iterations: Iteration<Task, Output>[]\n specs: AgentRunSpec<Task>[]\n output: OutputAdapter<Output>\n validator: Validator<Output> | undefined\n maxConcurrency: number\n signal: AbortSignal\n ctx: ExecCtx\n runId: string\n now: () => number\n}\n\nasync function runBatch<Task, Output>(args: RunBatchArgs<Task, Output>) {\n const queue = args.slice.map((task, offset) => ({ task, index: args.baseIndex + offset }))\n const inflight = new Set<Promise<void>>()\n while (queue.length > 0 || inflight.size > 0) {\n while (inflight.size < args.maxConcurrency && queue.length > 0) {\n const item = queue.shift()!\n const p = executeIteration({ ...args, item }).finally(() => inflight.delete(p))\n inflight.add(p)\n }\n if (inflight.size === 0) break\n await Promise.race(inflight)\n }\n}\n\ninterface ExecuteIterationArgs<Task, Output> extends RunBatchArgs<Task, Output> {\n item: { task: Task; index: number }\n}\n\nasync function executeIteration<Task, Output>(args: ExecuteIterationArgs<Task, Output>) {\n const slot = args.iterations[args.item.index]\n if (!slot)\n throw new ValidationError(`runLoop: missing iteration slot at index ${args.item.index}`)\n const spec = args.specs[args.item.index % args.specs.length]\n if (!spec) throw new ValidationError('runLoop: no AgentRunSpec available for iteration')\n slot.startedAt = args.now()\n slot.agentRunName = spec.name ?? spec.profile.name ?? 'agent'\n\n await emitTrace(args.ctx.traceEmitter, {\n kind: 'loop.iteration.started',\n runId: args.runId,\n timestamp: args.now(),\n payload: {\n iterationIndex: args.item.index,\n agentRunName: slot.agentRunName,\n taskHash: hashJson(args.item.task),\n },\n })\n\n try {\n const box = await createSandboxForSpec(args.ctx.sandboxClient, spec, args.signal)\n const placement = describePlacementSafe(args.ctx.sandboxClient, box)\n await emitTrace(args.ctx.traceEmitter, {\n kind: 'loop.iteration.dispatch',\n runId: args.runId,\n timestamp: args.now(),\n payload: {\n iterationIndex: args.item.index,\n agentRunName: slot.agentRunName,\n placement: placement.kind,\n sandboxId: placement.sandboxId,\n fleetId: placement.fleetId,\n machineId: placement.machineId,\n },\n })\n const message = spec.taskToPrompt(args.item.task)\n const events: SandboxEvent[] = []\n for await (const event of box.streamPrompt(message, { signal: args.signal })) {\n events.push(event)\n const llmCall = extractLlmCallEvent(event, slot.agentRunName)\n if (llmCall) {\n slot.costUsd += llmCall.costUsd ?? 0\n args.ctx.runHandle?.observe(llmCall)\n }\n }\n slot.events = events\n slot.output = args.output.parse(events)\n if (args.validator) {\n slot.verdict = await args.validator.validate(slot.output, {\n iteration: args.item.index,\n signal: args.signal,\n })\n }\n } catch (err) {\n slot.error = err instanceof Error ? err : new Error(String(err))\n } finally {\n slot.endedAt = args.now()\n await emitTrace(args.ctx.traceEmitter, {\n kind: 'loop.iteration.ended',\n runId: args.runId,\n timestamp: args.now(),\n payload: {\n iterationIndex: args.item.index,\n agentRunName: slot.agentRunName,\n outputHash: slot.output !== undefined ? hashJson(slot.output) : undefined,\n verdict: slot.verdict,\n error: slot.error?.message,\n costUsd: slot.costUsd,\n durationMs: slot.endedAt - slot.startedAt,\n },\n })\n }\n}\n\nfunction describePlacementSafe(\n client: LoopSandboxClient,\n box: SandboxInstance,\n): LoopSandboxPlacement {\n if (typeof client.describePlacement === 'function') {\n try {\n const result = client.describePlacement(box)\n if (\n result &&\n typeof result === 'object' &&\n (result.kind === 'sibling' || result.kind === 'fleet')\n ) {\n return {\n kind: result.kind,\n sandboxId: result.sandboxId ?? readSandboxId(box),\n fleetId: result.fleetId,\n machineId: result.machineId,\n }\n }\n } catch {\n // Adapter bug must not corrupt the iteration; fall through to default.\n }\n }\n return { kind: 'sibling', sandboxId: readSandboxId(box) }\n}\n\nfunction readSandboxId(box: SandboxInstance): string | undefined {\n const raw = (box as unknown as { id?: unknown }).id\n return typeof raw === 'string' && raw.length > 0 ? raw : undefined\n}\n\nasync function createSandboxForSpec<Task>(\n client: LoopSandboxClient,\n spec: AgentRunSpec<Task>,\n signal: AbortSignal,\n): Promise<SandboxInstance> {\n const overrides = spec.sandboxOverrides ?? {}\n const overrideBackend = overrides.backend\n const opts: CreateSandboxOptions = {\n ...overrides,\n backend: {\n type: overrideBackend?.type ?? inferBackendType(spec.profile),\n profile: spec.profile satisfies AgentProfile,\n ...(overrideBackend?.model ? { model: overrideBackend.model } : {}),\n ...(overrideBackend?.server ? { server: overrideBackend.server } : {}),\n },\n }\n // Cooperative cancellation: if the abort signal fires while .create is\n // pending, the promise itself is not abortable but the inflight prompt is.\n if (signal.aborted) throwAbort()\n return client.create(opts)\n}\n\nfunction inferBackendType(\n profile: AgentProfile,\n): CreateSandboxOptions['backend'] extends infer B\n ? B extends { type: infer T }\n ? T\n : never\n : never {\n // The sandbox SDK accepts profile-driven backend selection by name. When the\n // profile has no explicit hint we fall through to the SDK's default\n // ('opencode' on the platform side). Returning a literal here would lie\n // about provenance — let the SDK pick.\n type BackendType = NonNullable<CreateSandboxOptions['backend']>['type']\n const explicit = profile.metadata?.backendType\n if (typeof explicit === 'string') return explicit as BackendType\n return 'opencode' as BackendType\n}\n\ninterface FinalizeArgs<Task, Output, Decision> {\n options: RunLoopOptions<Task, Output, Decision>\n decision: Decision\n iterations: Iteration<Task, Output>[]\n startMs: number\n now: () => number\n runId: string\n}\n\nfunction finalize<Task, Output, Decision>(\n args: FinalizeArgs<Task, Output, Decision>,\n): LoopResult<Task, Output, Decision> {\n const winner = (args.options.selectWinner ?? defaultSelectWinner)(args.iterations)\n const costUsd = args.iterations.reduce((sum, iter) => sum + (iter.costUsd || 0), 0)\n const result: LoopResult<Task, Output, Decision> = {\n decision: args.decision,\n iterations: args.iterations,\n winner,\n durationMs: args.now() - args.startMs,\n costUsd,\n }\n void emitTrace(args.options.ctx.traceEmitter, {\n kind: 'loop.ended',\n runId: args.runId,\n timestamp: args.now(),\n payload: {\n winnerIterationIndex: winner?.iterationIndex,\n totalCostUsd: costUsd,\n durationMs: result.durationMs,\n iterations: args.iterations.length,\n },\n })\n return result\n}\n\nfunction defaultSelectWinner<Task, Output>(\n iterations: Iteration<Task, Output>[],\n): LoopWinner<Task, Output> | undefined {\n const candidates = iterations.filter((iter) => iter.output !== undefined && !iter.error)\n if (candidates.length === 0) return undefined\n const valid = candidates.filter((iter) => iter.verdict?.valid === true)\n const pool = valid.length > 0 ? valid : candidates\n const sorted = [...pool].sort(\n (a, b) => (b.verdict?.score ?? 0) - (a.verdict?.score ?? 0) || a.index - b.index,\n )\n const top = sorted[0]\n if (!top || top.output === undefined) return undefined\n return {\n task: top.task,\n output: top.output,\n verdict: top.verdict,\n iterationIndex: top.index,\n agentRunName: top.agentRunName,\n }\n}\n\nfunction resolveAgentRuns<Task, Output, Decision>(\n options: RunLoopOptions<Task, Output, Decision>,\n): AgentRunSpec<Task>[] {\n if (options.agentRun && options.agentRuns) {\n throw new ValidationError('runLoop: pass exactly one of `agentRun` or `agentRuns`')\n }\n if (options.agentRun) return [options.agentRun]\n if (options.agentRuns && options.agentRuns.length > 0) return options.agentRuns\n throw new ValidationError('runLoop: `agentRun` or non-empty `agentRuns` is required')\n}\n\nfunction isTerminalDecision(decision: unknown): boolean {\n return (\n decision === 'stop' || decision === 'pick-winner' || decision === 'fail' || decision === 'done'\n )\n}\n\nfunction serializeDecision(decision: unknown): string {\n if (typeof decision === 'string') return decision\n if (decision === null || decision === undefined) return 'null'\n try {\n return JSON.stringify(decision)\n } catch {\n return String(decision)\n }\n}\n\nasync function emitTrace(\n emitter: LoopTraceEmitter | undefined,\n event: LoopTraceEvent,\n): Promise<void> {\n if (!emitter) return\n await emitter.emit(event)\n}\n\nfunction randomSuffix(len = 8): string {\n return Math.random()\n .toString(36)\n .slice(2, 2 + len)\n}\n\nfunction throwAbort(): never {\n const err = new Error('aborted')\n err.name = 'AbortError'\n throw err\n}\n\n/**\n * Extract a `RuntimeStreamEvent`-shaped `llm_call` from a sandbox event when\n * the event carries usage/cost data. Returns `undefined` for non-cost events\n * so the kernel can iterate the full stream without branching.\n *\n * Sandbox SDK emits a polymorphic `SandboxEvent = { type, data, id? }`. The\n * canonical cost-carrying types observed in the wild:\n * - `llm_call` — `data: { model, tokensIn, tokensOut, costUsd, ... }`\n * - `message.completed` / `result` — `data: { usage: { inputTokens,\n * outputTokens, totalCostUsd? } }`\n * - `cost.usage` — same shape under a dedicated type\n *\n * Numeric coercion is strict: `Number.isFinite` gates every accumulator\n * write so a sentinel `NaN` from a misbehaving backend cannot poison the\n * ledger.\n */\nfunction extractLlmCallEvent(\n event: SandboxEvent,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n if (!event || typeof event !== 'object') return undefined\n const type = String(event.type ?? '')\n const data =\n event.data && typeof event.data === 'object'\n ? (event.data as Record<string, unknown>)\n : ({} as Record<string, unknown>)\n\n if (type === 'llm_call' || type === 'cost.usage' || type === 'usage') {\n return buildLlmCall(data, agentRunName)\n }\n if (type === 'message.completed' || type === 'result' || type === 'final') {\n const usage = data.usage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n return buildLlmCall({ ...usage, model: data.model ?? usage.model }, agentRunName)\n }\n return undefined\n}\n\nfunction buildLlmCall(\n data: Record<string, unknown>,\n agentRunName: string,\n): (RuntimeStreamEvent & { type: 'llm_call' }) | undefined {\n const tokensIn = pickFiniteNumber(data, ['tokensIn', 'inputTokens', 'prompt_tokens'])\n const tokensOut = pickFiniteNumber(data, ['tokensOut', 'outputTokens', 'completion_tokens'])\n const costUsd = pickFiniteNumber(data, ['costUsd', 'totalCostUsd', 'cost_usd', 'cost'])\n if (tokensIn === undefined && tokensOut === undefined && costUsd === undefined) {\n return undefined\n }\n const model = typeof data.model === 'string' && data.model.length > 0 ? data.model : agentRunName\n const event: RuntimeStreamEvent & { type: 'llm_call' } = {\n type: 'llm_call',\n model,\n }\n if (tokensIn !== undefined) event.tokensIn = tokensIn\n if (tokensOut !== undefined) event.tokensOut = tokensOut\n if (costUsd !== undefined) event.costUsd = costUsd\n return event\n}\n\nfunction pickFiniteNumber(data: Record<string, unknown>, keys: string[]): number | undefined {\n for (const key of keys) {\n const value = data[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n }\n return undefined\n}\n\n/**\n * Stable hash for the trace payload. Not cryptographic — only used so\n * downstream eval pipelines can group iterations whose task / output is the\n * same. Bare structural hash; non-JSON values stringify via their `toString`.\n */\nfunction hashJson(value: unknown): string {\n let str: string\n try {\n str = JSON.stringify(value) ?? String(value)\n } catch {\n str = String(value)\n }\n // FNV-1a 32-bit — branch-free, dependency-free, good enough for grouping.\n let h = 0x811c9dc5\n for (let i = 0; i < str.length; i += 1) {\n h ^= str.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(16).padStart(8, '0')\n}\n"],"mappings":"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","names":["decision"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runLoop
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EDVCVFQB.js";
|
|
4
4
|
import {
|
|
5
5
|
coderProfile,
|
|
6
6
|
multiHarnessCoderFanout
|
|
@@ -9,9 +9,126 @@ import {
|
|
|
9
9
|
NotFoundError
|
|
10
10
|
} from "./chunk-RZAOYKCO.js";
|
|
11
11
|
|
|
12
|
+
// src/mcp/executor.ts
|
|
13
|
+
function createSiblingSandboxExecutor(options) {
|
|
14
|
+
const underlying = options.client;
|
|
15
|
+
const client = {
|
|
16
|
+
create(opts) {
|
|
17
|
+
return underlying.create(opts);
|
|
18
|
+
},
|
|
19
|
+
describePlacement(box) {
|
|
20
|
+
return { kind: "sibling", sandboxId: readId(box) };
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
client,
|
|
25
|
+
describe() {
|
|
26
|
+
return "sibling-sandbox (each delegation = fresh sandbox via client.create)";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createFleetWorkspaceExecutor(options) {
|
|
31
|
+
const fleet = options.fleet;
|
|
32
|
+
const exclude = new Set(options.excludeMachineIds ?? []);
|
|
33
|
+
let callIndex = 0;
|
|
34
|
+
const placementBySandboxId = /* @__PURE__ */ new Map();
|
|
35
|
+
const client = {
|
|
36
|
+
async create() {
|
|
37
|
+
const ids = fleet.ids.filter((id) => !exclude.has(id));
|
|
38
|
+
if (ids.length === 0) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`agent-runtime: fleet ${fleet.fleetId} has no eligible worker machines (ids=[${fleet.ids.join(",")}], excluded=[${[...exclude].join(",")}])`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const selector = options.selectMachine;
|
|
44
|
+
const machineId = selector ? selector({ callIndex, ids }) : ids[callIndex % ids.length];
|
|
45
|
+
callIndex += 1;
|
|
46
|
+
if (typeof machineId !== "string" || machineId.length === 0) {
|
|
47
|
+
throw new Error("agent-runtime: fleet executor selectMachine returned an empty machine id");
|
|
48
|
+
}
|
|
49
|
+
const box = await fleet.sandbox(machineId);
|
|
50
|
+
const sandboxId = readId(box);
|
|
51
|
+
if (sandboxId) placementBySandboxId.set(sandboxId, { machineId });
|
|
52
|
+
return box;
|
|
53
|
+
},
|
|
54
|
+
describePlacement(box) {
|
|
55
|
+
const sandboxId = readId(box);
|
|
56
|
+
const recorded = sandboxId ? placementBySandboxId.get(sandboxId) : void 0;
|
|
57
|
+
return {
|
|
58
|
+
kind: "fleet",
|
|
59
|
+
sandboxId,
|
|
60
|
+
fleetId: fleet.fleetId,
|
|
61
|
+
machineId: recorded?.machineId
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
client,
|
|
67
|
+
describe() {
|
|
68
|
+
const excluded = exclude.size > 0 ? ` (excluded=[${[...exclude].join(",")}])` : "";
|
|
69
|
+
return `fleet-workspace (fleetId=${fleet.fleetId}, machines=[${fleet.ids.join(",")}]${excluded})`;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function readId(box) {
|
|
74
|
+
const raw = box.id;
|
|
75
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/mcp/bin-helpers.ts
|
|
79
|
+
async function detectExecutor(args) {
|
|
80
|
+
const env = args.env ?? process.env;
|
|
81
|
+
const fleetId = parseFleetId(env.TANGLE_FLEET_ID);
|
|
82
|
+
if (!fleetId) {
|
|
83
|
+
return createSiblingSandboxExecutor({ client: args.sandboxClient });
|
|
84
|
+
}
|
|
85
|
+
const resolveFleet = args.resolveFleet ?? defaultResolveFleet;
|
|
86
|
+
const fleet = await resolveFleet(args.sandboxClient, fleetId);
|
|
87
|
+
const excludeMachineIds = parseList(env.TANGLE_FLEET_EXCLUDE_MACHINES);
|
|
88
|
+
return createFleetWorkspaceExecutor({
|
|
89
|
+
fleet,
|
|
90
|
+
excludeMachineIds
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function defaultResolveFleet(sandboxClient, fleetId) {
|
|
94
|
+
const fleets = sandboxClient.fleets;
|
|
95
|
+
if (!fleets || typeof fleets.get !== "function") {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"agent-runtime-mcp: the configured sandbox client does not expose `.fleets.get`; upgrade @tangle-network/sandbox to >= 0.2.1 or unset TANGLE_FLEET_ID."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const raw = await fleets.get(fleetId);
|
|
101
|
+
if (!raw || typeof raw !== "object") {
|
|
102
|
+
throw new Error(`agent-runtime-mcp: fleets.get(${fleetId}) returned no handle`);
|
|
103
|
+
}
|
|
104
|
+
const handle = raw;
|
|
105
|
+
if (typeof handle.fleetId !== "string" || !Array.isArray(handle.ids)) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`agent-runtime-mcp: fleet handle for ${fleetId} is missing fleetId/ids \u2014 incompatible sandbox SDK shape`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (typeof handle.sandbox !== "function") {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`agent-runtime-mcp: fleet handle for ${fleetId} is missing sandbox(machineId) \u2014 incompatible sandbox SDK shape`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return handle;
|
|
116
|
+
}
|
|
117
|
+
function parseFleetId(raw) {
|
|
118
|
+
if (typeof raw !== "string") return void 0;
|
|
119
|
+
const trimmed = raw.trim();
|
|
120
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
121
|
+
}
|
|
122
|
+
function parseList(raw) {
|
|
123
|
+
if (!raw) return void 0;
|
|
124
|
+
const list = raw.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
125
|
+
return list.length > 0 ? list : void 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
12
128
|
// src/mcp/delegates.ts
|
|
13
129
|
function createDefaultCoderDelegate(options) {
|
|
14
|
-
const
|
|
130
|
+
const executor = resolveExecutor(options);
|
|
131
|
+
const sandboxClient = executor.client;
|
|
15
132
|
const fanoutHarnesses = options.fanoutHarnesses;
|
|
16
133
|
const maxConcurrency = options.maxConcurrency ?? 4;
|
|
17
134
|
return async (args, ctx) => {
|
|
@@ -70,6 +187,16 @@ function buildCoderGoal(args) {
|
|
|
70
187
|
if (!args.contextHint) return args.goal;
|
|
71
188
|
return [args.goal, "", "## Context", args.contextHint].join("\n");
|
|
72
189
|
}
|
|
190
|
+
function resolveExecutor(options) {
|
|
191
|
+
if (options.executor && options.sandboxClient) {
|
|
192
|
+
throw new Error("createDefaultCoderDelegate: pass exactly one of `executor` or `sandboxClient`");
|
|
193
|
+
}
|
|
194
|
+
if (options.executor) return options.executor;
|
|
195
|
+
if (options.sandboxClient) {
|
|
196
|
+
return createSiblingSandboxExecutor({ client: options.sandboxClient });
|
|
197
|
+
}
|
|
198
|
+
throw new Error("createDefaultCoderDelegate: `executor` or `sandboxClient` is required");
|
|
199
|
+
}
|
|
73
200
|
var singleShotDriver = {
|
|
74
201
|
name: "mcp-single-shot",
|
|
75
202
|
async plan(task, history) {
|
|
@@ -921,7 +1048,7 @@ import { createInterface } from "readline";
|
|
|
921
1048
|
import { Readable, Writable } from "stream";
|
|
922
1049
|
var PROTOCOL_VERSION = "2024-11-05";
|
|
923
1050
|
var DEFAULT_SERVER_NAME = "agent-runtime-mcp";
|
|
924
|
-
var DEFAULT_SERVER_VERSION = "0.
|
|
1051
|
+
var DEFAULT_SERVER_VERSION = "0.21.1";
|
|
925
1052
|
function createMcpServer(options = {}) {
|
|
926
1053
|
const queue = options.queue ?? new DelegationTaskQueue();
|
|
927
1054
|
const feedbackStore = options.feedbackStore ?? new InMemoryFeedbackStore();
|
|
@@ -1105,6 +1232,9 @@ function createInProcessTransport() {
|
|
|
1105
1232
|
}
|
|
1106
1233
|
|
|
1107
1234
|
export {
|
|
1235
|
+
createSiblingSandboxExecutor,
|
|
1236
|
+
createFleetWorkspaceExecutor,
|
|
1237
|
+
detectExecutor,
|
|
1108
1238
|
createDefaultCoderDelegate,
|
|
1109
1239
|
InMemoryFeedbackStore,
|
|
1110
1240
|
eventToSnapshot,
|
|
@@ -1138,4 +1268,4 @@ export {
|
|
|
1138
1268
|
createMcpServer,
|
|
1139
1269
|
createInProcessTransport
|
|
1140
1270
|
};
|
|
1141
|
-
//# sourceMappingURL=chunk-
|
|
1271
|
+
//# sourceMappingURL=chunk-QDNJLAEU.js.map
|