@tangle-network/agent-runtime 0.86.0 → 0.88.0
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/dist/agent.js +3 -3
- package/dist/analyst-loop.js +2 -2
- package/dist/{chunk-3TZOXS7B.js → chunk-22HPUH77.js} +325 -159
- package/dist/chunk-22HPUH77.js.map +1 -0
- package/dist/{chunk-4J6RBI3K.js → chunk-FVJ7M3DA.js} +80 -1
- package/dist/chunk-FVJ7M3DA.js.map +1 -0
- package/dist/{chunk-EJ7MAQN4.js → chunk-HBE77SWV.js} +4 -4
- package/dist/{chunk-U4TS65XZ.js → chunk-JHULWWQD.js} +2 -2
- package/dist/{chunk-F7OO2SKB.js → chunk-LRNRPJAV.js} +282 -34
- package/dist/chunk-LRNRPJAV.js.map +1 -0
- package/dist/{chunk-YPA5MLVE.js → chunk-ZQZX77MM.js} +2 -2
- package/dist/index.d.ts +8 -3
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/loop-runner-bin.js +5 -5
- package/dist/loops.d.ts +338 -9
- package/dist/loops.js +19 -3
- package/dist/mcp/bin.js +2 -2
- package/dist/mcp/index.js +4 -4
- package/package.json +2 -2
- package/skills/build-with-agent-runtime/SKILL.md +28 -3
- package/skills/loop-writer/SKILL.md +73 -0
- package/dist/chunk-3TZOXS7B.js.map +0 -1
- package/dist/chunk-4J6RBI3K.js.map +0 -1
- package/dist/chunk-F7OO2SKB.js.map +0 -1
- /package/dist/{chunk-EJ7MAQN4.js.map → chunk-HBE77SWV.js.map} +0 -0
- /package/dist/{chunk-U4TS65XZ.js.map → chunk-JHULWWQD.js.map} +0 -0
- /package/dist/{chunk-YPA5MLVE.js.map → chunk-ZQZX77MM.js.map} +0 -0
package/dist/agent.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-22HPUH77.js";
|
|
2
2
|
import {
|
|
3
3
|
createSandboxForSpec
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LRNRPJAV.js";
|
|
5
5
|
import "./chunk-UD4BHQMI.js";
|
|
6
6
|
import "./chunk-BZF3KQ6G.js";
|
|
7
7
|
import {
|
|
8
8
|
mapSandboxEvent
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FVJ7M3DA.js";
|
|
10
10
|
import "./chunk-7LO5GMAO.js";
|
|
11
11
|
import "./chunk-YEJR7IXO.js";
|
|
12
12
|
import "./chunk-DPEUKJRO.js";
|
package/dist/analyst-loop.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
settledToIteration,
|
|
16
16
|
supervise,
|
|
17
17
|
withDriverExecutor
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-LRNRPJAV.js";
|
|
19
19
|
import {
|
|
20
20
|
addTokenUsage,
|
|
21
21
|
isAbortError,
|
|
@@ -25,8 +25,10 @@ import {
|
|
|
25
25
|
zeroTokenUsage
|
|
26
26
|
} from "./chunk-BZF3KQ6G.js";
|
|
27
27
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
createSandboxToolPartState,
|
|
29
|
+
mapSandboxEvent,
|
|
30
|
+
mapSandboxToolEvent
|
|
31
|
+
} from "./chunk-FVJ7M3DA.js";
|
|
30
32
|
import {
|
|
31
33
|
AnalystError,
|
|
32
34
|
BackendTransportError,
|
|
@@ -1325,7 +1327,7 @@ function isAsyncIterable2(v) {
|
|
|
1325
1327
|
return typeof v === "object" && v !== null && Symbol.asyncIterator in v;
|
|
1326
1328
|
}
|
|
1327
1329
|
function inProcessSandboxClient(options) {
|
|
1328
|
-
const { onPrompt, workdir: workdirPrefix, id: idOption } = options;
|
|
1330
|
+
const { onPrompt, onTask, workdir: workdirPrefix, id: idOption } = options;
|
|
1329
1331
|
let seq = 0;
|
|
1330
1332
|
return {
|
|
1331
1333
|
async create(_options) {
|
|
@@ -1364,19 +1366,34 @@ function inProcessSandboxClient(options) {
|
|
|
1364
1366
|
}
|
|
1365
1367
|
}
|
|
1366
1368
|
} : {};
|
|
1369
|
+
async function* drive(behavior, mode, message, opts) {
|
|
1370
|
+
const prompt = typeof message === "string" ? message : JSON.stringify(message);
|
|
1371
|
+
const { signal: optSignal, ...rest } = opts ?? {};
|
|
1372
|
+
const signal = optSignal ?? new AbortController().signal;
|
|
1373
|
+
const ctx = {
|
|
1374
|
+
round,
|
|
1375
|
+
workdir: boxWorkdir,
|
|
1376
|
+
signal,
|
|
1377
|
+
mode,
|
|
1378
|
+
...Object.keys(rest).length > 0 ? { options: rest } : {}
|
|
1379
|
+
};
|
|
1380
|
+
round += 1;
|
|
1381
|
+
const produced = await behavior(prompt, ctx);
|
|
1382
|
+
if (isAsyncIterable2(produced)) {
|
|
1383
|
+
for await (const ev of produced) yield ev;
|
|
1384
|
+
} else {
|
|
1385
|
+
for (const ev of produced) yield ev;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1367
1388
|
const box = {
|
|
1368
1389
|
id,
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
for await (const ev of produced) yield ev;
|
|
1377
|
-
} else {
|
|
1378
|
-
for (const ev of produced) yield ev;
|
|
1379
|
-
}
|
|
1390
|
+
streamPrompt(message, opts) {
|
|
1391
|
+
return drive(onPrompt, "prompt", message, opts);
|
|
1392
|
+
},
|
|
1393
|
+
// Task-mode verb (`streamAgentTurn`'s `box-task` backend). Drives
|
|
1394
|
+
// `onTask` when configured; otherwise the box's one behavior callback.
|
|
1395
|
+
streamTask(message, opts) {
|
|
1396
|
+
return drive(onTask ?? onPrompt, "task", message, opts);
|
|
1380
1397
|
},
|
|
1381
1398
|
...fsMembers,
|
|
1382
1399
|
async delete() {
|
|
@@ -1388,6 +1405,263 @@ function inProcessSandboxClient(options) {
|
|
|
1388
1405
|
};
|
|
1389
1406
|
}
|
|
1390
1407
|
|
|
1408
|
+
// src/runtime/loop-author.ts
|
|
1409
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1410
|
+
import { join as join4 } from "path";
|
|
1411
|
+
|
|
1412
|
+
// src/runtime/strategy-author.ts
|
|
1413
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1414
|
+
import { join as join3 } from "path";
|
|
1415
|
+
var strategyAuthorContract = `
|
|
1416
|
+
You author an OPTIMIZATION STRATEGY for an agentic loop system. A strategy decides how to
|
|
1417
|
+
spend a compute budget to beat a task's deployable check. You compose exactly two steps:
|
|
1418
|
+
|
|
1419
|
+
shot(spec?: { handle?, messages?, steer?, persona?, tools? }): Promise<ShotResult | null>
|
|
1420
|
+
Runs ONE worker attempt (a bounded tool loop) over an artifact.
|
|
1421
|
+
- omit handle => the shot opens its OWN fresh artifact and closes it after (a sample).
|
|
1422
|
+
- pass handle => the shot CONTINUES that artifact (state accumulates across shots).
|
|
1423
|
+
- messages => the carried conversation (pass the previous ShotResult.messages to continue).
|
|
1424
|
+
- steer => a corrective instruction injected before the shot.
|
|
1425
|
+
- persona => { systemPrompt?, model? } \u2014 give THIS shot its own role and/or model
|
|
1426
|
+
(multi-agent strategies: a researcher shot then an engineer shot, a panel of k
|
|
1427
|
+
personas over one budget). On a fresh shot the systemPrompt replaces the task's; on
|
|
1428
|
+
a carried conversation it arrives as a hand-off message. Same conserved budget.
|
|
1429
|
+
- tools => string[] \u2014 restrict THIS shot to a subset of the task's tools by
|
|
1430
|
+
name (focus an explore shot on read-only tools, an execute shot on write tools).
|
|
1431
|
+
Restriction-only; unknown names make the shot fail. ALWAYS select from
|
|
1432
|
+
await listTools(handle) \u2014 never hardcode. Omitted => the shot sees every tool.
|
|
1433
|
+
ShotResult = { messages, score (0..1 on the task's check), passes, total, completions, toolErrors }
|
|
1434
|
+
Returns null if the attempt failed infra-wise.
|
|
1435
|
+
|
|
1436
|
+
critique(messages): Promise<string | null>
|
|
1437
|
+
A firewalled trace-analyst reads the attempt's trajectory and returns ONE corrective
|
|
1438
|
+
instruction (or null when it judges the work complete). Costs ~1 completion.
|
|
1439
|
+
|
|
1440
|
+
consult(messages, instruction): Promise<string | null>
|
|
1441
|
+
The RAW analyst channel: the same firewalled critic answers YOUR instruction over the
|
|
1442
|
+
trajectory verbatim (no reformatting) \u2014 use it when you need a specific reply format
|
|
1443
|
+
(a decision, a prediction). Costs ~1 completion.
|
|
1444
|
+
|
|
1445
|
+
surface.open(task) / surface.close(handle)
|
|
1446
|
+
Open a persistent artifact you manage yourself (remember to close in a finally).
|
|
1447
|
+
close is idempotent \u2014 closing an already-closed handle is a safe no-op.
|
|
1448
|
+
|
|
1449
|
+
listTools(handle): Promise<Array<{ name, description? }>>
|
|
1450
|
+
The tools THIS task actually offers. TOOL SETS VARY PER TASK \u2014 if you restrict a
|
|
1451
|
+
shot with \`tools\`, you MUST pick names from await listTools(handle); hardcoding
|
|
1452
|
+
names from an example kills your shots on every task whose tools differ.
|
|
1453
|
+
|
|
1454
|
+
Rules:
|
|
1455
|
+
- ALWAYS await every shot/critique/surface call \u2014 a floating promise that rejects
|
|
1456
|
+
crashes the whole benchmark run.
|
|
1457
|
+
- Stay within ~budget total shots; every shot/critique spends from a conserved pool.
|
|
1458
|
+
- For a FRESH attempt OMIT \`messages\` entirely (never pass \`[]\` \u2014 an empty array is a
|
|
1459
|
+
fresh conversation too, but be explicit). To CONTINUE, pass the previous
|
|
1460
|
+
ShotResult.messages unchanged.
|
|
1461
|
+
- Return { score, resolved, completions, progression, shots } \u2014 score = the BEST checkpoint
|
|
1462
|
+
you reached (keep-best, never final-state), progression = score after each shot.
|
|
1463
|
+
- The module must be EXACTLY this shape (no other imports, no commentary outside code):
|
|
1464
|
+
|
|
1465
|
+
import { defineStrategy } from '@tangle-network/agent-runtime/loops'
|
|
1466
|
+
export default defineStrategy('your-strategy-name', async ({ surface, task, budget, shot, critique, listTools }) => {
|
|
1467
|
+
// your composition (listTools comes from the destructured context \u2014 it is NOT a global)
|
|
1468
|
+
})
|
|
1469
|
+
`;
|
|
1470
|
+
function assertStrategyContract(code) {
|
|
1471
|
+
const allowedImport = /^\s*import\s+\{[^}]*\}\s+from\s+['"]@tangle-network\/agent-runtime\/loops['"]/;
|
|
1472
|
+
for (const line of code.split("\n")) {
|
|
1473
|
+
if (/^\s*import\s/.test(line) && !allowedImport.test(line)) {
|
|
1474
|
+
throw new Error(`authored code rejected: foreign import \u2014 ${line.trim().slice(0, 120)}`);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
const banned = [
|
|
1478
|
+
[/\brequire\s*\(/, "require()"],
|
|
1479
|
+
[/\bimport\s*\(/, "dynamic import()"],
|
|
1480
|
+
[/\beval\s*\(/, "eval()"],
|
|
1481
|
+
[/new\s+Function\s*\(/, "new Function()"],
|
|
1482
|
+
[/\bprocess\s*[.[]/, "process access"],
|
|
1483
|
+
[/\bglobalThis\s*[.[]/, "globalThis access"],
|
|
1484
|
+
[/\bfetch\s*\(/, "network access"],
|
|
1485
|
+
[/child_process|node:fs|node:net|node:http|worker_threads/, "node builtin access"]
|
|
1486
|
+
];
|
|
1487
|
+
for (const [re, what] of banned) {
|
|
1488
|
+
if (re.test(code)) throw new Error(`authored code rejected: ${what}`);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async function requestAuthoredCode(opts, model) {
|
|
1492
|
+
const res = await opts.chat.chat(
|
|
1493
|
+
{
|
|
1494
|
+
...model ? { model } : {},
|
|
1495
|
+
...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
1496
|
+
...opts.maxTokens !== void 0 ? { maxTokens: opts.maxTokens } : {},
|
|
1497
|
+
messages: [
|
|
1498
|
+
{
|
|
1499
|
+
role: "system",
|
|
1500
|
+
content: "You are a senior engineer authoring optimization strategies for agent loops. Output exactly one fenced ```ts code block and nothing else."
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
role: "user",
|
|
1504
|
+
content: `${opts.contract ?? strategyAuthorContract}
|
|
1505
|
+
|
|
1506
|
+
BASELINE RESULTS on the "${opts.environmentName}" environment (budget=${opts.budget}):
|
|
1507
|
+
${opts.lossesJson}
|
|
1508
|
+
|
|
1509
|
+
Author ONE new strategy that you expect to beat the baselines on THIS environment at the same budget. Use the losses to target the observed failure mode. Output only the module code block.`
|
|
1510
|
+
}
|
|
1511
|
+
]
|
|
1512
|
+
},
|
|
1513
|
+
{ ...opts.signal ? { signal: opts.signal } : {} }
|
|
1514
|
+
);
|
|
1515
|
+
const match = res.content.match(/```(?:ts|typescript)?\s*\n([\s\S]*?)```/);
|
|
1516
|
+
if (!match?.[1]) {
|
|
1517
|
+
throw new Error(
|
|
1518
|
+
`authorStrategy: no code block in the author's reply (model=${model ?? "default"}): ${res.content.slice(0, 300)}`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
return match[1];
|
|
1522
|
+
}
|
|
1523
|
+
async function authorStrategy(opts) {
|
|
1524
|
+
let code;
|
|
1525
|
+
try {
|
|
1526
|
+
code = await requestAuthoredCode(opts, opts.model);
|
|
1527
|
+
} catch (primaryError) {
|
|
1528
|
+
if (!opts.fallbackModel) throw primaryError;
|
|
1529
|
+
code = await requestAuthoredCode(opts, opts.fallbackModel);
|
|
1530
|
+
}
|
|
1531
|
+
assertStrategyContract(code);
|
|
1532
|
+
mkdirSync2(opts.outDir, { recursive: true });
|
|
1533
|
+
const file = join3(opts.outDir, `authored-${Date.now()}.mts`);
|
|
1534
|
+
writeFileSync2(file, code);
|
|
1535
|
+
const mod = await import(`file://${file}`);
|
|
1536
|
+
if (!mod.default || typeof mod.default.driver !== "function" || !mod.default.name) {
|
|
1537
|
+
throw new Error(`authorStrategy: ${file} does not export a default Strategy`);
|
|
1538
|
+
}
|
|
1539
|
+
return { strategy: mod.default, file, code };
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/runtime/loop-author.ts
|
|
1543
|
+
var loopAuthorContract = `
|
|
1544
|
+
You author a LOOP ATOM for an agent supervisor. A loop runs a bounded, multi-round journey
|
|
1545
|
+
toward a deployable check; a supervisor spawns / observes / steers it exactly like a worker.
|
|
1546
|
+
You write ONE round; the runtime owns the round ceiling, the conserved budget, the gate, and
|
|
1547
|
+
folding a supervisor's steer into the next round. So write real control flow inside a round,
|
|
1548
|
+
but never write the loop's stop condition as "hope the model stops" \u2014 that is the runtime's job.
|
|
1549
|
+
|
|
1550
|
+
You export ONE module of EXACTLY this shape (no other imports, no commentary outside the code):
|
|
1551
|
+
|
|
1552
|
+
import { defineLoop } from '@tangle-network/agent-runtime/loops'
|
|
1553
|
+
export default defineLoop('your-loop-name', {
|
|
1554
|
+
maxRounds: 3,
|
|
1555
|
+
round: async ({ task, scope, round, maxRounds, steer, budget, signal }) => {
|
|
1556
|
+
// arbitrary code for ONE round. Do real work by spawning children on the nested scope:
|
|
1557
|
+
// const w = scope.spawn(childAgent, subtask, { budget: perRound, label: \`r\${round}\` })
|
|
1558
|
+
// if (!w.ok) throw new Error(w.reason) // fail loud: budget-exhausted | depth-exceeded
|
|
1559
|
+
// const settled = await scope.next() // conserved child work; settles gated
|
|
1560
|
+
// 'steer' is the supervisor's messages since the last round (fold them into what you do next).
|
|
1561
|
+
// 'signal' aborts if a forceful steer arrives mid-round \u2014 pass it to your awaited work.
|
|
1562
|
+
return { out: /* the running result */ undefined, done: false } // done:true stops early
|
|
1563
|
+
},
|
|
1564
|
+
check: (out) => /* the deployable completion oracle */ Boolean(out),
|
|
1565
|
+
})
|
|
1566
|
+
|
|
1567
|
+
For a MULTI-AGENT loop (a proposer then a verifier, a researcher then an engineer), do NOT
|
|
1568
|
+
hand-write the pipeline in 'round' \u2014 use 'agents' instead: an ordered list of named agents piped
|
|
1569
|
+
each round (task -> agents[0] -> agents[1] -> ... -> out). "How many agents" is then self-evident.
|
|
1570
|
+
|
|
1571
|
+
import { defineLoop } from '@tangle-network/agent-runtime/loops'
|
|
1572
|
+
export default defineLoop('your-loop-name', {
|
|
1573
|
+
maxRounds: 3,
|
|
1574
|
+
agents: [
|
|
1575
|
+
{ name: 'proposer', run: async (ctx, prior) => { /* spawn a worker, produce a draft */ return draft } },
|
|
1576
|
+
{ name: 'verifier', run: async (ctx, prior) => { /* verify/refine the proposer's draft */ return checked } },
|
|
1577
|
+
],
|
|
1578
|
+
check: (out) => Boolean(out),
|
|
1579
|
+
})
|
|
1580
|
+
|
|
1581
|
+
Provide EXACTLY one of 'round' or 'agents'. The round context (for the 'round' form, and passed to
|
|
1582
|
+
each agent's run as its first arg):
|
|
1583
|
+
task the spawn task, verbatim.
|
|
1584
|
+
scope the NESTED conserved scope. scope.spawn(agent, task, { budget, label }) reserves
|
|
1585
|
+
budget and fails closed; scope.next() awaits one child settlement. Budget NESTS \u2014
|
|
1586
|
+
the pool reserves each spawn's full ceiling until it settles, so give children a
|
|
1587
|
+
per-round budget smaller than the loop's own.
|
|
1588
|
+
round 1-based round index. maxRounds is the declared ceiling.
|
|
1589
|
+
steer readonly string[] \u2014 supervisor steer_agent messages that arrived since last round.
|
|
1590
|
+
budget conserved-pool readouts (tokensLeft, usdLeft, deadlineMs) for in-body awareness.
|
|
1591
|
+
signal AbortSignal \u2014 the spawn signal + a fresh per-round interrupt; honor it in awaits.
|
|
1592
|
+
|
|
1593
|
+
The round result: { out: unknown; done?: boolean }. 'out' is the running result the gate reads;
|
|
1594
|
+
'done: true' is YOUR early stop (distinct from the runtime's gate 'check').
|
|
1595
|
+
|
|
1596
|
+
check(out): boolean | Promise<boolean>. The DEPLOYABLE oracle \u2014 an executable test, a state
|
|
1597
|
+
verifier, a readiness score threshold \u2014 read off 'out', never the model judging itself. The loop
|
|
1598
|
+
settles valid IFF check passes, and the runtime polls it after each round to stop the instant the
|
|
1599
|
+
loop has delivered. A loop that exhausts maxRounds without check passing settles valid:false.
|
|
1600
|
+
|
|
1601
|
+
Rules:
|
|
1602
|
+
- ALWAYS await every scope.spawn drain / async call \u2014 a floating rejection crashes the run.
|
|
1603
|
+
- Do real work by SPAWNING children; raw un-metered inference in the body is not budget-conserved.
|
|
1604
|
+
- Give 'check' a real oracle. A loop whose check is a self-judged score cannot be trusted.
|
|
1605
|
+
- The only import allowed is '@tangle-network/agent-runtime/loops'. No require/eval/fetch/process/
|
|
1606
|
+
node builtins \u2014 the runtime meters and gates you; out-of-band compute breaks that.
|
|
1607
|
+
`;
|
|
1608
|
+
async function requestAuthoredCode2(opts, model) {
|
|
1609
|
+
const res = await opts.chat.chat(
|
|
1610
|
+
{
|
|
1611
|
+
...model ? { model } : {},
|
|
1612
|
+
...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
1613
|
+
...opts.maxTokens !== void 0 ? { maxTokens: opts.maxTokens } : {},
|
|
1614
|
+
messages: [
|
|
1615
|
+
{
|
|
1616
|
+
role: "system",
|
|
1617
|
+
content: "You are a senior engineer authoring coded loop atoms for an agent supervisor. Output exactly one fenced ```ts code block and nothing else."
|
|
1618
|
+
},
|
|
1619
|
+
{
|
|
1620
|
+
role: "user",
|
|
1621
|
+
content: `${opts.contract ?? loopAuthorContract}
|
|
1622
|
+
|
|
1623
|
+
GOAL: ${opts.goal}
|
|
1624
|
+
MAX ROUNDS: ${opts.maxRounds}${opts.context ? `
|
|
1625
|
+
|
|
1626
|
+
CONTEXT:
|
|
1627
|
+
${opts.context}` : ""}
|
|
1628
|
+
|
|
1629
|
+
Author ONE loop that reaches the goal within the round ceiling, gated on a real check. Output only the module code block.`
|
|
1630
|
+
}
|
|
1631
|
+
]
|
|
1632
|
+
},
|
|
1633
|
+
{ ...opts.signal ? { signal: opts.signal } : {} }
|
|
1634
|
+
);
|
|
1635
|
+
const match = res.content.match(/```(?:ts|typescript)?\s*\n([\s\S]*?)```/);
|
|
1636
|
+
if (!match?.[1]) {
|
|
1637
|
+
throw new Error(
|
|
1638
|
+
`authorLoop: no code block in the author's reply (model=${model ?? "default"}): ${res.content.slice(0, 300)}`
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
return match[1];
|
|
1642
|
+
}
|
|
1643
|
+
function isLoopDef(value) {
|
|
1644
|
+
return typeof value === "object" && value !== null && typeof value.round === "function" && typeof value.name === "string" && Number.isInteger(value.maxRounds);
|
|
1645
|
+
}
|
|
1646
|
+
async function authorLoop(opts) {
|
|
1647
|
+
let code;
|
|
1648
|
+
try {
|
|
1649
|
+
code = await requestAuthoredCode2(opts, opts.model);
|
|
1650
|
+
} catch (primaryError) {
|
|
1651
|
+
if (!opts.fallbackModel) throw primaryError;
|
|
1652
|
+
code = await requestAuthoredCode2(opts, opts.fallbackModel);
|
|
1653
|
+
}
|
|
1654
|
+
assertStrategyContract(code);
|
|
1655
|
+
mkdirSync3(opts.outDir, { recursive: true });
|
|
1656
|
+
const file = join4(opts.outDir, `authored-loop-${Date.now()}.mts`);
|
|
1657
|
+
writeFileSync3(file, code);
|
|
1658
|
+
const mod = await import(`file://${file}`);
|
|
1659
|
+
if (!isLoopDef(mod.default)) {
|
|
1660
|
+
throw new Error(`authorLoop: ${file} does not default-export a LoopDef (defineLoop(...))`);
|
|
1661
|
+
}
|
|
1662
|
+
return { loop: mod.default, file, code };
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1391
1665
|
// src/runtime/mcp-environment.ts
|
|
1392
1666
|
async function rpc(endpoint, body) {
|
|
1393
1667
|
let lastErr;
|
|
@@ -3515,7 +3789,8 @@ async function openSandboxRun(client, options, deliverable) {
|
|
|
3515
3789
|
const r = await lineage.start(
|
|
3516
3790
|
options.agentRun,
|
|
3517
3791
|
prompt,
|
|
3518
|
-
options.signal
|
|
3792
|
+
options.signal,
|
|
3793
|
+
options.promptOptions
|
|
3519
3794
|
);
|
|
3520
3795
|
handle = r.handle;
|
|
3521
3796
|
const result = await settle2(handle.box, r.events);
|
|
@@ -3560,7 +3835,7 @@ async function openSandboxRun(client, options, deliverable) {
|
|
|
3560
3835
|
try {
|
|
3561
3836
|
const result = await settle2(
|
|
3562
3837
|
handle.box,
|
|
3563
|
-
await lineage.continue(handle, prompt, options.signal)
|
|
3838
|
+
await lineage.continue(handle, prompt, options.signal, options.promptOptions)
|
|
3564
3839
|
);
|
|
3565
3840
|
turnCount += 1;
|
|
3566
3841
|
emit({
|
|
@@ -3628,138 +3903,8 @@ function errorMessage(error) {
|
|
|
3628
3903
|
return error instanceof Error ? error.message : String(error);
|
|
3629
3904
|
}
|
|
3630
3905
|
|
|
3631
|
-
// src/runtime/strategy-author.ts
|
|
3632
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3633
|
-
import { join as join3 } from "path";
|
|
3634
|
-
var strategyAuthorContract = `
|
|
3635
|
-
You author an OPTIMIZATION STRATEGY for an agentic loop system. A strategy decides how to
|
|
3636
|
-
spend a compute budget to beat a task's deployable check. You compose exactly two steps:
|
|
3637
|
-
|
|
3638
|
-
shot(spec?: { handle?, messages?, steer?, persona?, tools? }): Promise<ShotResult | null>
|
|
3639
|
-
Runs ONE worker attempt (a bounded tool loop) over an artifact.
|
|
3640
|
-
- omit handle => the shot opens its OWN fresh artifact and closes it after (a sample).
|
|
3641
|
-
- pass handle => the shot CONTINUES that artifact (state accumulates across shots).
|
|
3642
|
-
- messages => the carried conversation (pass the previous ShotResult.messages to continue).
|
|
3643
|
-
- steer => a corrective instruction injected before the shot.
|
|
3644
|
-
- persona => { systemPrompt?, model? } \u2014 give THIS shot its own role and/or model
|
|
3645
|
-
(multi-agent strategies: a researcher shot then an engineer shot, a panel of k
|
|
3646
|
-
personas over one budget). On a fresh shot the systemPrompt replaces the task's; on
|
|
3647
|
-
a carried conversation it arrives as a hand-off message. Same conserved budget.
|
|
3648
|
-
- tools => string[] \u2014 restrict THIS shot to a subset of the task's tools by
|
|
3649
|
-
name (focus an explore shot on read-only tools, an execute shot on write tools).
|
|
3650
|
-
Restriction-only; unknown names make the shot fail. ALWAYS select from
|
|
3651
|
-
await listTools(handle) \u2014 never hardcode. Omitted => the shot sees every tool.
|
|
3652
|
-
ShotResult = { messages, score (0..1 on the task's check), passes, total, completions, toolErrors }
|
|
3653
|
-
Returns null if the attempt failed infra-wise.
|
|
3654
|
-
|
|
3655
|
-
critique(messages): Promise<string | null>
|
|
3656
|
-
A firewalled trace-analyst reads the attempt's trajectory and returns ONE corrective
|
|
3657
|
-
instruction (or null when it judges the work complete). Costs ~1 completion.
|
|
3658
|
-
|
|
3659
|
-
consult(messages, instruction): Promise<string | null>
|
|
3660
|
-
The RAW analyst channel: the same firewalled critic answers YOUR instruction over the
|
|
3661
|
-
trajectory verbatim (no reformatting) \u2014 use it when you need a specific reply format
|
|
3662
|
-
(a decision, a prediction). Costs ~1 completion.
|
|
3663
|
-
|
|
3664
|
-
surface.open(task) / surface.close(handle)
|
|
3665
|
-
Open a persistent artifact you manage yourself (remember to close in a finally).
|
|
3666
|
-
close is idempotent \u2014 closing an already-closed handle is a safe no-op.
|
|
3667
|
-
|
|
3668
|
-
listTools(handle): Promise<Array<{ name, description? }>>
|
|
3669
|
-
The tools THIS task actually offers. TOOL SETS VARY PER TASK \u2014 if you restrict a
|
|
3670
|
-
shot with \`tools\`, you MUST pick names from await listTools(handle); hardcoding
|
|
3671
|
-
names from an example kills your shots on every task whose tools differ.
|
|
3672
|
-
|
|
3673
|
-
Rules:
|
|
3674
|
-
- ALWAYS await every shot/critique/surface call \u2014 a floating promise that rejects
|
|
3675
|
-
crashes the whole benchmark run.
|
|
3676
|
-
- Stay within ~budget total shots; every shot/critique spends from a conserved pool.
|
|
3677
|
-
- For a FRESH attempt OMIT \`messages\` entirely (never pass \`[]\` \u2014 an empty array is a
|
|
3678
|
-
fresh conversation too, but be explicit). To CONTINUE, pass the previous
|
|
3679
|
-
ShotResult.messages unchanged.
|
|
3680
|
-
- Return { score, resolved, completions, progression, shots } \u2014 score = the BEST checkpoint
|
|
3681
|
-
you reached (keep-best, never final-state), progression = score after each shot.
|
|
3682
|
-
- The module must be EXACTLY this shape (no other imports, no commentary outside code):
|
|
3683
|
-
|
|
3684
|
-
import { defineStrategy } from '@tangle-network/agent-runtime/loops'
|
|
3685
|
-
export default defineStrategy('your-strategy-name', async ({ surface, task, budget, shot, critique, listTools }) => {
|
|
3686
|
-
// your composition (listTools comes from the destructured context \u2014 it is NOT a global)
|
|
3687
|
-
})
|
|
3688
|
-
`;
|
|
3689
|
-
function assertStrategyContract(code) {
|
|
3690
|
-
const allowedImport = /^\s*import\s+\{[^}]*\}\s+from\s+['"]@tangle-network\/agent-runtime\/loops['"]/;
|
|
3691
|
-
for (const line of code.split("\n")) {
|
|
3692
|
-
if (/^\s*import\s/.test(line) && !allowedImport.test(line)) {
|
|
3693
|
-
throw new Error(`authored code rejected: foreign import \u2014 ${line.trim().slice(0, 120)}`);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
const banned = [
|
|
3697
|
-
[/\brequire\s*\(/, "require()"],
|
|
3698
|
-
[/\bimport\s*\(/, "dynamic import()"],
|
|
3699
|
-
[/\beval\s*\(/, "eval()"],
|
|
3700
|
-
[/new\s+Function\s*\(/, "new Function()"],
|
|
3701
|
-
[/\bprocess\s*[.[]/, "process access"],
|
|
3702
|
-
[/\bglobalThis\s*[.[]/, "globalThis access"],
|
|
3703
|
-
[/\bfetch\s*\(/, "network access"],
|
|
3704
|
-
[/child_process|node:fs|node:net|node:http|worker_threads/, "node builtin access"]
|
|
3705
|
-
];
|
|
3706
|
-
for (const [re, what] of banned) {
|
|
3707
|
-
if (re.test(code)) throw new Error(`authored code rejected: ${what}`);
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
async function requestAuthoredCode(opts, model) {
|
|
3711
|
-
const res = await opts.chat.chat(
|
|
3712
|
-
{
|
|
3713
|
-
...model ? { model } : {},
|
|
3714
|
-
...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
|
|
3715
|
-
...opts.maxTokens !== void 0 ? { maxTokens: opts.maxTokens } : {},
|
|
3716
|
-
messages: [
|
|
3717
|
-
{
|
|
3718
|
-
role: "system",
|
|
3719
|
-
content: "You are a senior engineer authoring optimization strategies for agent loops. Output exactly one fenced ```ts code block and nothing else."
|
|
3720
|
-
},
|
|
3721
|
-
{
|
|
3722
|
-
role: "user",
|
|
3723
|
-
content: `${opts.contract ?? strategyAuthorContract}
|
|
3724
|
-
|
|
3725
|
-
BASELINE RESULTS on the "${opts.environmentName}" environment (budget=${opts.budget}):
|
|
3726
|
-
${opts.lossesJson}
|
|
3727
|
-
|
|
3728
|
-
Author ONE new strategy that you expect to beat the baselines on THIS environment at the same budget. Use the losses to target the observed failure mode. Output only the module code block.`
|
|
3729
|
-
}
|
|
3730
|
-
]
|
|
3731
|
-
},
|
|
3732
|
-
{ ...opts.signal ? { signal: opts.signal } : {} }
|
|
3733
|
-
);
|
|
3734
|
-
const match = res.content.match(/```(?:ts|typescript)?\s*\n([\s\S]*?)```/);
|
|
3735
|
-
if (!match?.[1]) {
|
|
3736
|
-
throw new Error(
|
|
3737
|
-
`authorStrategy: no code block in the author's reply (model=${model ?? "default"}): ${res.content.slice(0, 300)}`
|
|
3738
|
-
);
|
|
3739
|
-
}
|
|
3740
|
-
return match[1];
|
|
3741
|
-
}
|
|
3742
|
-
async function authorStrategy(opts) {
|
|
3743
|
-
let code;
|
|
3744
|
-
try {
|
|
3745
|
-
code = await requestAuthoredCode(opts, opts.model);
|
|
3746
|
-
} catch (primaryError) {
|
|
3747
|
-
if (!opts.fallbackModel) throw primaryError;
|
|
3748
|
-
code = await requestAuthoredCode(opts, opts.fallbackModel);
|
|
3749
|
-
}
|
|
3750
|
-
assertStrategyContract(code);
|
|
3751
|
-
mkdirSync2(opts.outDir, { recursive: true });
|
|
3752
|
-
const file = join3(opts.outDir, `authored-${Date.now()}.mts`);
|
|
3753
|
-
writeFileSync2(file, code);
|
|
3754
|
-
const mod = await import(`file://${file}`);
|
|
3755
|
-
if (!mod.default || typeof mod.default.driver !== "function" || !mod.default.name) {
|
|
3756
|
-
throw new Error(`authorStrategy: ${file} does not export a default Strategy`);
|
|
3757
|
-
}
|
|
3758
|
-
return { strategy: mod.default, file, code };
|
|
3759
|
-
}
|
|
3760
|
-
|
|
3761
3906
|
// src/runtime/strategy-evolution.ts
|
|
3762
|
-
import { existsSync, readFileSync, writeFileSync as
|
|
3907
|
+
import { existsSync, readFileSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3763
3908
|
import { gzipSync } from "zlib";
|
|
3764
3909
|
function discriminatingMeans(report, fieldOrder) {
|
|
3765
3910
|
const rows = report.perTask.filter((r) => {
|
|
@@ -3871,7 +4016,7 @@ async function runStrategyEvolution(cfg) {
|
|
|
3871
4016
|
}
|
|
3872
4017
|
const save = (state) => {
|
|
3873
4018
|
if (cfg.checkpoint)
|
|
3874
|
-
|
|
4019
|
+
writeFileSync4(cfg.checkpoint.path, JSON.stringify({ ...state, fingerprint }, null, 1));
|
|
3875
4020
|
};
|
|
3876
4021
|
const bench = async (phase, tasks, strategies) => {
|
|
3877
4022
|
await cfg.onPhase?.(phase);
|
|
@@ -4320,6 +4465,12 @@ function createOpenAICompatibleBackend(options) {
|
|
|
4320
4465
|
if (options.responseFormat !== void 0) {
|
|
4321
4466
|
bodyPayload.response_format = options.responseFormat;
|
|
4322
4467
|
}
|
|
4468
|
+
if (options.temperature !== void 0) {
|
|
4469
|
+
bodyPayload.temperature = options.temperature;
|
|
4470
|
+
}
|
|
4471
|
+
if (options.maxTokens !== void 0) {
|
|
4472
|
+
bodyPayload.max_tokens = options.maxTokens;
|
|
4473
|
+
}
|
|
4323
4474
|
const requestBody = JSON.stringify(bodyPayload);
|
|
4324
4475
|
let response;
|
|
4325
4476
|
let lastStatus = 0;
|
|
@@ -4800,11 +4951,17 @@ async function* streamAgentTurn(backend, prompt, opts = {}) {
|
|
|
4800
4951
|
session = await startTurnSession(backend, task, prompt, deadline.signal, label);
|
|
4801
4952
|
yield { type: "backend_start", task, session, backend: label, timestamp: nowIso() };
|
|
4802
4953
|
const inner = backend.kind === "chat" ? driveChatTurn(backend.backend, task, session, prompt, deadline.signal, acc) : driveBoxTurn(
|
|
4803
|
-
backend.kind === "
|
|
4954
|
+
backend.kind === "executor" ? await inlineSandboxClient(backend.factory).create() : backend.box,
|
|
4804
4955
|
prompt,
|
|
4805
4956
|
deadline.signal,
|
|
4806
4957
|
backend.agentRunName ?? "agent",
|
|
4807
|
-
acc
|
|
4958
|
+
acc,
|
|
4959
|
+
{
|
|
4960
|
+
mode: backend.kind === "box-task" ? "task" : "prompt",
|
|
4961
|
+
...backend.kind !== "executor" && backend.options ? { options: backend.options } : {},
|
|
4962
|
+
preserveToolParts: opts.preserveToolParts === true,
|
|
4963
|
+
...opts.onRawEvent ? { onRawEvent: opts.onRawEvent } : {}
|
|
4964
|
+
}
|
|
4808
4965
|
);
|
|
4809
4966
|
for await (const event of inner) {
|
|
4810
4967
|
yield event;
|
|
@@ -4868,10 +5025,17 @@ async function startTurnSession(backend, task, prompt, signal, label) {
|
|
|
4868
5025
|
}
|
|
4869
5026
|
return newRuntimeSession(label);
|
|
4870
5027
|
}
|
|
4871
|
-
async function* driveBoxTurn(box, prompt, signal, agentRunName, acc) {
|
|
4872
|
-
|
|
5028
|
+
async function* driveBoxTurn(box, prompt, signal, agentRunName, acc, cfg) {
|
|
5029
|
+
const callOptions = { ...cfg.options ?? {}, signal };
|
|
5030
|
+
const stream = cfg.mode === "task" ? box.streamTask(prompt, callOptions) : box.streamPrompt(prompt, callOptions);
|
|
5031
|
+
const toolParts = cfg.preserveToolParts ? createSandboxToolPartState() : void 0;
|
|
5032
|
+
for await (const event of stream) {
|
|
5033
|
+
if (cfg.onRawEvent) await cfg.onRawEvent(event);
|
|
4873
5034
|
const terminalText = terminalTextFromSandboxEvent(event);
|
|
4874
5035
|
if (terminalText !== void 0) acc.terminalText = terminalText;
|
|
5036
|
+
if (toolParts) {
|
|
5037
|
+
for (const toolEvent of mapSandboxToolEvent(event, toolParts)) yield toolEvent;
|
|
5038
|
+
}
|
|
4875
5039
|
const mapped = mapSandboxEvent(event, { agentRunName });
|
|
4876
5040
|
if (!mapped) continue;
|
|
4877
5041
|
foldEvent(mapped, acc, agentRunName);
|
|
@@ -5750,8 +5914,8 @@ function jjWorkspace(opts) {
|
|
|
5750
5914
|
async function runInWorkspace(ws, body, opts = {}) {
|
|
5751
5915
|
const { mkdtempSync: mkdtempSync2, rmSync: rmSync2 } = await import("fs");
|
|
5752
5916
|
const { tmpdir: tmpdir3 } = await import("os");
|
|
5753
|
-
const { join:
|
|
5754
|
-
const dir = mkdtempSync2(
|
|
5917
|
+
const { join: join5 } = await import("path");
|
|
5918
|
+
const dir = mkdtempSync2(join5(tmpdir3(), opts.tmpPrefix ?? "ws-run-"));
|
|
5755
5919
|
try {
|
|
5756
5920
|
await ws.materialize(dir);
|
|
5757
5921
|
const r = await body(dir);
|
|
@@ -5805,6 +5969,11 @@ export {
|
|
|
5805
5969
|
renderReport,
|
|
5806
5970
|
harvestCorpus,
|
|
5807
5971
|
inProcessSandboxClient,
|
|
5972
|
+
strategyAuthorContract,
|
|
5973
|
+
assertStrategyContract,
|
|
5974
|
+
authorStrategy,
|
|
5975
|
+
loopAuthorContract,
|
|
5976
|
+
authorLoop,
|
|
5808
5977
|
createMcpEnvironment,
|
|
5809
5978
|
assertTraceDerivedFindings,
|
|
5810
5979
|
createScopeAnalyst,
|
|
@@ -5841,9 +6010,6 @@ export {
|
|
|
5841
6010
|
printBenchmarkReport,
|
|
5842
6011
|
SandboxRunAbortError,
|
|
5843
6012
|
openSandboxRun,
|
|
5844
|
-
strategyAuthorContract,
|
|
5845
|
-
assertStrategyContract,
|
|
5846
|
-
authorStrategy,
|
|
5847
6013
|
discriminatingMeans,
|
|
5848
6014
|
pickChampion,
|
|
5849
6015
|
selectChampion,
|
|
@@ -5870,4 +6036,4 @@ export {
|
|
|
5870
6036
|
computeFindingId,
|
|
5871
6037
|
makeFinding2 as makeFinding
|
|
5872
6038
|
};
|
|
5873
|
-
//# sourceMappingURL=chunk-
|
|
6039
|
+
//# sourceMappingURL=chunk-22HPUH77.js.map
|