@tangle-network/agent-runtime 0.79.3 → 0.79.4
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 +50 -327
- package/dist/agent.js +5 -5
- package/dist/analyst-loop.js +2 -2
- package/dist/{chunk-KRULXIWS.js → chunk-44CX5JU6.js} +4 -4
- package/dist/{chunk-T2HVQVB4.js → chunk-4J6RBI3K.js} +15 -1
- package/dist/chunk-4J6RBI3K.js.map +1 -0
- package/dist/{chunk-AG335EXG.js → chunk-AHZ3YBL6.js} +524 -12
- package/dist/chunk-AHZ3YBL6.js.map +1 -0
- package/dist/{chunk-IODKUOBA.js → chunk-C2FZ6GR6.js} +2 -2
- package/dist/{chunk-Z3RRRPRB.js → chunk-H7IBHAFT.js} +23 -14
- package/dist/chunk-H7IBHAFT.js.map +1 -0
- package/dist/{chunk-VMNEQHJR.js → chunk-IPEQ3ERC.js} +17 -2
- package/dist/chunk-IPEQ3ERC.js.map +1 -0
- package/dist/{chunk-PBE35ULD.js → chunk-NXZEVWKP.js} +2 -2
- package/dist/{chunk-PVPFDTO3.js → chunk-RUJZK6VH.js} +2 -2
- package/dist/{chunk-63MHOCIE.js → chunk-U6AP535M.js} +4 -4
- package/dist/{coordination-DCmljYDf.d.ts → coordination-BFVtgRax.d.ts} +2 -2
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/intelligence.d.ts +29 -1
- package/dist/intelligence.js +33 -23
- package/dist/intelligence.js.map +1 -1
- package/dist/{loop-runner-bin-C4X0FZ2Z.d.ts → loop-runner-bin-CTVja8e0.d.ts} +2 -2
- package/dist/loop-runner-bin.d.ts +4 -4
- package/dist/loop-runner-bin.js +7 -7
- package/dist/loops.d.ts +242 -11
- package/dist/loops.js +26 -6
- package/dist/mcp/bin.js +5 -5
- package/dist/mcp/index.d.ts +5 -5
- package/dist/mcp/index.js +7 -7
- package/dist/{types-C1sozrte.d.ts → types-DYW0tloU.d.ts} +2 -2
- package/dist/{worktree-fanout-CXGzHET4.d.ts → worktree-fanout-DaUDwCA_.d.ts} +2 -2
- package/dist/{worktree-harness-Bmho9SH0.d.ts → worktree-harness-Dt6s_m3z.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/build-with-agent-runtime/SKILL.md +53 -23
- package/dist/chunk-AG335EXG.js.map +0 -1
- package/dist/chunk-T2HVQVB4.js.map +0 -1
- package/dist/chunk-VMNEQHJR.js.map +0 -1
- package/dist/chunk-Z3RRRPRB.js.map +0 -1
- /package/dist/{chunk-KRULXIWS.js.map → chunk-44CX5JU6.js.map} +0 -0
- /package/dist/{chunk-IODKUOBA.js.map → chunk-C2FZ6GR6.js.map} +0 -0
- /package/dist/{chunk-PBE35ULD.js.map → chunk-NXZEVWKP.js.map} +0 -0
- /package/dist/{chunk-PVPFDTO3.js.map → chunk-RUJZK6VH.js.map} +0 -0
- /package/dist/{chunk-63MHOCIE.js.map → chunk-U6AP535M.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,371 +1,94 @@
|
|
|
1
1
|
# @tangle-network/agent-runtime
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The engine Tangle's AI agents run on. It runs an agent — a **chat turn**, a **one-shot task**, or a **team of agents** working toward a goal — records every run, and uses those records to **measure and improve** agents against real pass/fail checks.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
`runBenchmark` + `runStrategyEvolution`, the published surface for measuring and evolving how an agent
|
|
7
|
-
spends compute against a deployable check. It delegates domain behavior (models, tools, knowledge) to
|
|
8
|
-
adapters, scoring statistics and the ship gate to [`@tangle-network/agent-eval`](https://www.npmjs.com/package/@tangle-network/agent-eval), and sandboxed long-running execution to [`@tangle-network/sandbox`](https://www.npmjs.com/package/@tangle-network/sandbox).
|
|
5
|
+
One loop, used three ways. Domain behavior (models, tools, knowledge) plugs in as adapters; the scoring statistics and the ship decision come from [`@tangle-network/agent-eval`](https://www.npmjs.com/package/@tangle-network/agent-eval); sandboxed execution from [`@tangle-network/sandbox`](https://www.npmjs.com/package/@tangle-network/sandbox).
|
|
9
6
|
|
|
10
7
|
```bash
|
|
11
8
|
pnpm add @tangle-network/agent-runtime @tangle-network/agent-eval @tangle-network/sandbox
|
|
12
9
|
```
|
|
13
10
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
One recursive `Agent` atom, run at two timescales, over many tasks. `docs/architecture.md` is the canonical spine. The short version:
|
|
17
|
-
|
|
18
|
-
1. **One atom.** `driver`, `worker`, `selector`, and `coordinator` are not separate types. They are what a single `Agent` returns from `act`. The recursion bottoms out at execution.
|
|
19
|
-
2. **Two timescales, one machinery.** The same loop runs at inference time (steer a worker over k attempts) and at optimization time (search the steer or the prompt with GEPA, gated on a held-out split).
|
|
20
|
-
3. **A benchmark is an adapter.** A new task is a loader plus a worker plus a judge. The loop, the drivers, the corpus, and the selector are the shared spine, written once.
|
|
21
|
-
4. **The selector is not the judge.** At inference time the selector picks which answer to return without seeing the judge's verdict. The judge is write-only. A steer may read the trace but never the verdict (the firewall that keeps the loop from gaming its own score).
|
|
22
|
-
|
|
23
|
-
## Getting started
|
|
24
|
-
|
|
25
|
-
Three entry points, by what you're doing:
|
|
11
|
+
## What you do with it
|
|
26
12
|
|
|
27
13
|
| You want to… | Call |
|
|
28
14
|
|---|---|
|
|
29
|
-
| Run
|
|
30
|
-
| Have
|
|
31
|
-
| **
|
|
15
|
+
| Run a **chat turn** — what every product agent does in production | `handleChatTurn(...)` |
|
|
16
|
+
| Have one agent **supervise a team of agents** toward a goal | `supervise(profile, task, opts)` |
|
|
17
|
+
| **Improve** an agent and prove the gain on fresh tasks | `improve(profile, findings, opts)` |
|
|
32
18
|
|
|
33
19
|
### Run a chat turn
|
|
34
20
|
|
|
35
|
-
|
|
21
|
+
A product agent is one `handleChatTurn` call inside a route. You give it how to produce the response and how to persist it; it streams, traces, and persists.
|
|
36
22
|
|
|
37
23
|
```ts
|
|
38
24
|
import { handleChatTurn } from '@tangle-network/agent-runtime'
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
finalText: () => box.lastResponse(),
|
|
50
|
-
}),
|
|
51
|
-
persistAssistantMessage: async ({ identity, finalText }) => env.db.insertMessage(identity, finalText),
|
|
52
|
-
traceFlush: () => env.traceSink.flush(),
|
|
53
|
-
},
|
|
54
|
-
waitUntil: ctx.waitUntil.bind(ctx),
|
|
55
|
-
})
|
|
56
|
-
return new Response(result.body, { headers: { 'content-type': result.contentType } })
|
|
57
|
-
}
|
|
26
|
+
const result = handleChatTurn({
|
|
27
|
+
identity: { tenantId, sessionId: threadId, userId, turnIndex: 0 },
|
|
28
|
+
hooks: {
|
|
29
|
+
produce: () => ({ stream: box.streamPrompt(userMessage), finalText: () => box.lastResponse() }),
|
|
30
|
+
persistAssistantMessage: async ({ identity, finalText }) => db.insertMessage(identity, finalText),
|
|
31
|
+
},
|
|
32
|
+
waitUntil,
|
|
33
|
+
})
|
|
34
|
+
return new Response(result.body, { headers: { 'content-type': result.contentType } })
|
|
58
35
|
```
|
|
59
36
|
|
|
60
|
-
|
|
37
|
+
### Supervise a team of agents
|
|
61
38
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
A supervisor authors and drives a team of workers to a goal. The brain is resolved from the profile's `harness`: `null` → an in-process router tool-loop; `'claude-code'`/`'opencode'`/`'codex'` → a sandboxed coding harness driving the coordination verbs. The scaffolding (blobs / per-worker budget / journal / executors / depth) is defaulted.
|
|
39
|
+
One supervisor spawns and steers workers toward a goal. Where the workers run (an in-process loop, or a sandboxed coding harness) is one data value; the budget, journaling, and stopping are handled for you.
|
|
65
40
|
|
|
66
41
|
```ts
|
|
67
42
|
import { supervise } from '@tangle-network/agent-runtime/loops'
|
|
68
43
|
|
|
69
44
|
const result = await supervise(
|
|
70
45
|
{ name: 'supervisor', harness: null, systemPrompt: 'Delegate to workers; do not solve the task yourself.' },
|
|
71
|
-
'Implement the feature and make
|
|
72
|
-
{ budget, router, backend }, //
|
|
46
|
+
'Implement the feature and make the tests pass.',
|
|
47
|
+
{ budget, router, backend }, // backend = where workers run: router-tools | sandbox+harness | bridge
|
|
73
48
|
)
|
|
74
49
|
```
|
|
75
50
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### Self-improve an agent
|
|
51
|
+
### Improve an agent
|
|
79
52
|
|
|
80
|
-
`improve`
|
|
53
|
+
`improve` optimizes one part of an agent (its prompt, skills, or code) and **only ships a change if it beats the current agent on tasks it never practiced on** — so registering an agent for self-improvement can never make it worse.
|
|
81
54
|
|
|
82
55
|
```ts
|
|
83
56
|
import { improve } from '@tangle-network/agent-runtime'
|
|
84
57
|
|
|
85
58
|
const { profile, shipped, lift } = await improve(baseProfile, findings, {
|
|
86
|
-
surface: 'prompt',
|
|
87
|
-
gate: 'holdout',
|
|
88
|
-
scenarios, judge, agent,
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Everything below is the substrate these three sit on: multi-attempt loops, delegation, optimization, and the telemetry that makes them auditable.
|
|
93
|
-
|
|
94
|
-
### The system in plain language
|
|
95
|
-
|
|
96
|
-
The internal docs use the project's own vocabulary; this is the same thing without it, for a colleague meeting the project cold. Five sentences:
|
|
97
|
-
|
|
98
|
-
1. We have tasks with **automatic pass/fail checks** — tests you can run, answer keys you can verify mechanically.
|
|
99
|
-
2. An AI attempts each task a fixed number of times under different **retry policies**: "try 3 times, keep the best", "try, get feedback, try again", and so on.
|
|
100
|
-
3. We compare policies **fairly**: identical tasks, identical attempt budgets, paired statistics, judged on fresh tasks no tuning step ever saw.
|
|
101
|
-
4. The distinctive part: the AI also **writes new retry policies itself**, as short programs, and they enter the same tournament under the same rules as human-written ones.
|
|
102
|
-
5. Every dollar and second is metered, so "better" can also mean "**equally good but cheaper**" — and that claim is statistically testable, not vibes.
|
|
103
|
-
|
|
104
|
-
The load-bearing core is six pieces: task-with-check · retry policy · the tournament runner · the AI policy-writer · the statistical promotion gate · crash-resume. Everything else is a **fairness rule** or an **experiment on the menu** (a configuration, not a machine part).
|
|
105
|
-
|
|
106
|
-
| Project term | Plain English | Standard concept |
|
|
107
|
-
|---|---|---|
|
|
108
|
-
| `Environment` | a task domain: open it, act with tools, check the result | RL environment / gym |
|
|
109
|
-
| shot | one attempt | — |
|
|
110
|
-
| steering / `refine` | feedback injected between attempts | self-refinement |
|
|
111
|
-
| `authorStrategy` | the AI writes a new retry policy as a program | program synthesis |
|
|
112
|
-
| evolution / generations | write candidates → tournament → keep the champion | evolutionary search |
|
|
113
|
-
| harness-verified scoring | never trust a policy's self-reported score; recompute it from the attempts actually run | measurement hygiene |
|
|
114
|
-
| selector ≠ judge (the firewall) | the feedback-giver never sees the answer key or the score | no reward leakage |
|
|
115
|
-
| conserved budget pool | every policy gets exactly the same attempt budget; overspending is structurally impossible | compute-matched comparison |
|
|
116
|
-
| holdout / fresh slice | final judging happens on tasks no tuning step ever touched | train/test split |
|
|
117
|
-
| `promotionGate` | a seeded paired bootstrap must show the win is real before anything is "better" | inferential statistics |
|
|
118
|
-
| non-inferiority mode | prove "not worse on quality AND significantly cheaper" | clinical-trials statistics |
|
|
119
|
-
| reproducer certificate | a fresh AI re-builds the winner from a short description; a failed rebuild means the win was memorization, not method | description-length test |
|
|
120
|
-
| waterfall | a per-step timeline of the run: seconds, dollars, tokens per step | distributed tracing |
|
|
121
|
-
|
|
122
|
-
**Honest weaknesses:** mostly one domain family per claim so far (cross-domain replication is configuration, not new code); small holdouts (12–16 tasks) mean only effects ≳6pp are detectable; and the homegrown vocabulary is heavier than the machine it names — hence this section.
|
|
123
|
-
|
|
124
|
-
## Which entry point do I reach for?
|
|
125
|
-
|
|
126
|
-
| You want to | Reach for | Subpath |
|
|
127
|
-
|---|---|---|
|
|
128
|
-
| Run a production chat turn (most products) | `handleChatTurn` | root |
|
|
129
|
-
| Declare an agent (profile, surfaces, adapters) | `defineAgent` | `/agent` |
|
|
130
|
-
| Run a one-shot task with verification and eval | `runAgentTask` | root |
|
|
131
|
-
| Compare optimization strategies on YOUR domain (5 hooks) | `runBenchmark` + `defineStrategy` | `/loops` |
|
|
132
|
-
| Let the system author + evolve its own strategies, gated | `runStrategyEvolution` · `authorStrategy` · `promotionGate` | `/loops` |
|
|
133
|
-
| Run a multi-attempt loop with a custom driver | `runLoop` + an inline `Driver` | `/loops` |
|
|
134
|
-
| Drive one agent profile from another (the canonical driver) | `createCoordinationTools` over `Supervisor` (`/loops`) | `/mcp` |
|
|
135
|
-
| Delegate a disciplined loop by mode (code, research, ...) | `runDelegatedLoop` or `agent-runtime-loop` | root |
|
|
136
|
-
| Build code reliably (reviewed, gated) | `createDefaultCoderDelegate` | `/mcp` |
|
|
137
|
-
| Grow a knowledge base with only grounded facts | `createKbGate` | `/mcp` |
|
|
138
|
-
| Improve a prompt safely (identity-gated) | `selfImprove` | `@tangle-network/agent-eval/contract` |
|
|
139
|
-
| Ship loop traces to a GenAI viewer | `buildLoopOtelSpans` plus `createOtelExporter` | root |
|
|
140
|
-
| Expose delegation as MCP tools to a sandbox agent | `createMcpServer` or `agent-runtime-mcp` | `/mcp` |
|
|
141
|
-
| Persist a run plus its cost ledger | `startRuntimeRun` | root |
|
|
142
|
-
|
|
143
|
-
## The optimization suite
|
|
144
|
-
|
|
145
|
-
The canonical surface. A domain is an `Environment` (five hooks: `open`/`tools`/`call`/`score`/`close`);
|
|
146
|
-
a **strategy** is how a compute budget is spent to beat the domain's own deployable check. Two
|
|
147
|
-
built-ins (`sample` = best-of-N, `refine` = critique-and-continue) plus `defineStrategy` to compose
|
|
148
|
-
your own from two steps — and `authorStrategy`, where the system writes new strategies from its own
|
|
149
|
-
per-task losses:
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
import { defineStrategy, runBenchmark, sample, refine } from '@tangle-network/agent-runtime/loops'
|
|
153
|
-
|
|
154
|
-
const doubleCheck = defineStrategy('double-check', async ({ shot, critique }) => {
|
|
155
|
-
const first = await shot()
|
|
156
|
-
const steer = first ? await critique(first.messages) : null
|
|
157
|
-
const second = steer ? await shot({ messages: first?.messages, steer }) : null
|
|
158
|
-
const score = Math.max(first?.score ?? 0, second?.score ?? 0)
|
|
159
|
-
return { score, resolved: score >= 1, completions: 2, progression: [first?.score ?? 0, score], shots: 2 }
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
const report = await runBenchmark({ environment, tasks, worker, strategies: [sample, refine, doubleCheck], budget: 3 })
|
|
163
|
-
report.perTask // the losses table an author/optimizer consumes
|
|
164
|
-
report.pareto // the (score, $) frontier
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
The measurement invariants are structural, not advisory: every strategy spends through a conserved
|
|
168
|
-
budget pool (equal compute by construction), the deliverable score is **harness-verified** from the
|
|
169
|
-
shots actually brokered (a body cannot fabricate a win), and the critic is firewalled from the check
|
|
170
|
-
(selector ≠ judge). `runStrategyEvolution` runs the multi-generation search — populations of authored
|
|
171
|
-
candidates, cost-aware champion selection, a phase ledger with resume, and ONE promotion decision via
|
|
172
|
-
`promotionGate` (seeded paired bootstrap) on a holdout slice the search never touched.
|
|
173
|
-
`createVerifierEnvironment` adapts answer-shaped domains (one `check` function); `createMcpEnvironment`
|
|
174
|
-
adapts any MCP server. The consumer surface — loops as a service with a CLI, detached runner, and MCP
|
|
175
|
-
server — lives in the [`loops`](https://github.com/drewstone/loops) repo; the experiment harness and
|
|
176
|
-
evidence ledger live in [`bench/HARNESS.md`](./bench/HARNESS.md).
|
|
177
|
-
|
|
178
|
-
## The loop kernel
|
|
179
|
-
|
|
180
|
-
`runLoop` is a topology-agnostic kernel. Each iteration spawns a sandbox on an `AgentRunSpec`, decodes the output, validates it, and asks a driver what to do next. The driver owns topology. The validator owns scoring. The kernel owns iteration accounting, concurrency, cost and token aggregation, and trace emission.
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
import { runLoop, type Driver } from '@tangle-network/agent-runtime/loops'
|
|
184
|
-
|
|
185
|
-
const driver: Driver<Task, Output, 'pick-winner' | 'fail'> = {
|
|
186
|
-
plan: async (task, history) => (history.length === 0 ? [task, task] : []), // fan out, then stop
|
|
187
|
-
decide: (history) => (history.some((i) => i.verdict?.valid) ? 'pick-winner' : 'fail'),
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const result = await runLoop({
|
|
191
|
-
driver, // the driver owns topology; the kernel owns accounting
|
|
192
|
-
agentRuns: [claudeSpec, codexSpec, glmSpec], // heterogeneous: one harness per branch
|
|
193
|
-
output, // events to typed Output
|
|
194
|
-
validator, // Output to { valid, score }
|
|
195
|
-
task,
|
|
196
|
-
ctx: { sandboxClient: sandbox },
|
|
59
|
+
surface: 'prompt', // what to optimize: prompt | skills | code
|
|
60
|
+
gate: 'holdout', // certified on a held-back exam, never the practice set
|
|
61
|
+
scenarios, judge, agent, // how to measure a candidate
|
|
197
62
|
})
|
|
198
|
-
result.winner // highest-scoring valid attempt
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
A `Driver` is `plan` (emit the round's `Task[]` — `[]` ends the loop) plus `decide` (the terminal
|
|
202
|
-
`Decision` over the history). Topology is orthogonal to harness: the driver never names a backend,
|
|
203
|
-
and the kernel's `agentRuns` decide which harness runs each branch. See `examples/coder-loop` for a
|
|
204
|
-
fixed-shape inline `Driver`. For recursive work prefer the **agent-driver** — an `AgentProfile`
|
|
205
|
-
driving another via `createCoordinationTools` (`/mcp`) over the budget-conserving `Scope`/`Supervisor`
|
|
206
|
-
core (`/runtime`) — plus the `personify` combinators (`fanout`, `loopUntil`, `panel`, `pipeline`) and
|
|
207
|
-
`runPersonified` on that same core.
|
|
208
|
-
|
|
209
|
-
## Self-improvement
|
|
210
|
-
|
|
211
|
-
The same machinery, run at the optimization timescale.
|
|
212
|
-
|
|
213
|
-
The one entry point is agent-eval's **`selfImprove`** (`@tangle-network/agent-eval/contract`). It runs a closed loop over any text/config surface, identity-gated by construction: it evaluates, proposes candidates (default `gepaDriver`), and a held-out gate ships a winner only if it beats the baseline. `result.winner.surface` is the baseline unless `result.gateDecision === 'ship'`, so registering a surface for optimization can never regress it.
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
import { selfImprove } from '@tangle-network/agent-eval/contract'
|
|
217
|
-
|
|
218
|
-
const result = await selfImprove({
|
|
219
|
-
baselineSurface: CURRENT_SYSTEM_PROMPT,
|
|
220
|
-
agent: (surface, scenario, ctx) => runYourThing(surface, scenario),
|
|
221
|
-
scenarios,
|
|
222
|
-
judge,
|
|
223
|
-
budget: { holdoutScenarios, generations: 3 },
|
|
224
|
-
llm: { baseUrl, apiKey, model: 'claude-sonnet-4-6' },
|
|
225
|
-
})
|
|
226
|
-
// result.winner.surface is the safe one — the baseline unless gateDecision === 'ship'
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
agent-runtime contributes the runtime-specific pieces: the **CODE-surface `improvementDriver`**
|
|
230
|
-
(`/improvement`) — a git-worktree mutator you pass to `selfImprove` as `driver` to optimize code
|
|
231
|
-
instead of a string — and **`runStrategyEvolution`** (`/loops`), the multi-generation search over
|
|
232
|
-
STRATEGY space: the system reads its own per-task losses, authors candidate strategies as code,
|
|
233
|
-
plays them against the incumbent at equal budget, and a seeded statistical gate decides promotion
|
|
234
|
-
on a never-touched holdout slice.
|
|
235
|
-
|
|
236
|
-
`runAnalystLoop` (`/analyst-loop`) mines real run traces into findings; `createAnalystDriverHook` feeds those findings to a dynamic-driver planner via `PlannerContext.analyses`, with a firewall (`assertTraceDerivedFindings`) that rejects any finding derived from a judge verdict. Production intake — turning real run traces into the corpus `selfImprove` optimizes against — is agent-eval's `analyzeRuns` / `partitionRunsByAuthoringModel` (`/contract`).
|
|
237
|
-
|
|
238
|
-
## Delegated loops
|
|
239
|
-
|
|
240
|
-
`runDelegatedLoop` is one entrypoint a worker agent or a scheduled routine calls to run a disciplined loop in a chosen mode, over the hardened engines below. It fails loud on an unwired mode; a thrown engine is captured as `{ ok: false }`, so unattended runs record rather than crash.
|
|
241
|
-
|
|
242
|
-
```ts
|
|
243
|
-
import { runDelegatedLoop, worktreeLoopRunner, researchLoopRunner, type DelegatedLoopRegistry } from '@tangle-network/agent-runtime'
|
|
244
|
-
|
|
245
|
-
const registry: DelegatedLoopRegistry = {
|
|
246
|
-
code: worktreeLoopRunner({ repoRoot: '/repo', taskPrompt: 'fix the flaky retry test', harnesses, budget }),
|
|
247
|
-
research: researchLoopRunner({ research, gate: { selfArtifactKinds: ['spec'] }, maxRounds: 3 }),
|
|
248
|
-
}
|
|
249
|
-
const result = await runDelegatedLoop('code', registry)
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Modes: `code`, `review`, `research`, `audit`, `self-improve`, `dynamic`. The `agent-runtime-loop` bin runs the registry from a cron or routine and exits 0 (ok), 1 (recorded failure), or 2 (usage or config error).
|
|
253
|
-
|
|
254
|
-
`worktreeLoopRunner` (`code` mode, the generic recursive path) authors one `AgentProfile` per harness and runs them as a `worktreeFanout` (each leaf `gateOnDeliverable`), winner by the shared valid-only selector. The sandbox-session counterpart is `detachedSessionDelegate` (`/mcp`): it drives the in-box harness over a `SandboxClient` to a mechanically-validated patch, with default-on safety gates — no-op rejection, an always-on secret-path floor (`.env`, keys, wallets), an optional `reviewer` gate, and a `winnerSelection` policy. Its worker profile is a parameter the caller authors (`workerProfile`); omit it for a minimal model-only default.
|
|
255
|
-
|
|
256
|
-
The knowledge-base gate (`createKbGate`, `/mcp`) is fail-closed: a fact's `verbatimPassage` must appear in its `sourceText`, the asserted value must be in the passage, and citations cannot point at self-generated artifacts. `researchLoopRunner` wraps it with a correct-on-veto loop that re-researches the vetoed gaps up to `maxRounds`, then returns the unverified ones rather than dropping them.
|
|
257
|
-
|
|
258
|
-
## Tracing
|
|
259
|
-
|
|
260
|
-
`runLoop` emits a structured event stream. `buildLoopOtelSpans` turns it into a nested, real-duration span tree that any GenAI trace viewer (Phoenix, Langfuse, Grafana Tempo, Tangle Intelligence) renders natively. Attributes follow the current GenAI semantic conventions (`gen_ai.operation.name`, `gen_ai.agent.name`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`) plus a `tangle.loop.*` extension for the topology (move kind and rationale, edge lineage, verdict, placement, cost).
|
|
261
|
-
|
|
262
|
-
```ts
|
|
263
|
-
import { buildLoopOtelSpans, createOtelExporter } from '@tangle-network/agent-runtime'
|
|
264
|
-
|
|
265
|
-
const exporter = createOtelExporter() // reads OTEL_EXPORTER_OTLP_ENDPOINT
|
|
266
|
-
for (const span of buildLoopOtelSpans(loopEvents, traceId)) exporter?.exportSpan(span)
|
|
267
|
-
await exporter?.flush()
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
The shape: `loop` to `loop.round` (move plus rationale) to `loop.iteration` (agent, usage, verdict, cost, parent edge).
|
|
271
|
-
|
|
272
|
-
## MCP delegation server
|
|
273
|
-
|
|
274
|
-
Expose the delegation tools to a sandbox coding agent: the generic `delegate` verb (one intent → a supervisor that authors + drives its own worker, returns the delivered output with its real spend) plus the queue-bound `delegate_feedback`, `delegation_status`, `delegation_history` (and `delegate_ui_audit` when a UI-audit runner is wired). Mount the canonical server instead of forking delegation logic.
|
|
275
|
-
|
|
276
|
-
```ts
|
|
277
|
-
import { createMcpServer } from '@tangle-network/agent-runtime/mcp'
|
|
278
|
-
|
|
279
|
-
const server = createMcpServer({ delegateSupervisor: { router, backend, deliverable } })
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Or mount the `agent-runtime-mcp` stdio bin on a production `AgentProfile.mcp` with `MCP_ENABLE_DELEGATE=1`.
|
|
283
|
-
|
|
284
|
-
Delegation state is in-memory by default — a server restart drops pending delegations and history. Set `AGENT_RUNTIME_DELEGATION_STATE_FILE=/path/state.json` on the bin (or construct via `DelegationTaskQueue.restore({ store: new FileDelegationStore({ filePath }) })`) to persist records across restarts: `delegation_status`/`delegation_history` keep answering for prior runs, idempotency keys dedupe resubmissions, and in-flight records either resume through the `resumeDelegate` seam (when submitted with a `detachedSessionRef`) or settle as failed with an explicit driver-restart error. A corrupt state file refuses to load (`DelegationStateCorruptError`); `AGENT_RUNTIME_DELEGATION_STATE_RECOVER=1` archives it and starts empty. `AGENT_RUNTIME_DELEGATION_RETAIN_TERMINAL=<n>` caps retained terminal records.
|
|
285
|
-
|
|
286
|
-
## The experiment harness (bench/)
|
|
287
|
-
|
|
288
|
-
`bench/` is the internal harness; [`bench/HARNESS.md`](./bench/HARNESS.md) is its map — read that
|
|
289
|
-
first. The canonical path is the optimization suite (`runBenchmark`/`runStrategyEvolution` over real
|
|
290
|
-
domains: the EnterpriseOps gym, commit0, answer-shaped math). The live evidence ledger is
|
|
291
|
-
`.evolve/current.json` — results never live in this README.
|
|
292
|
-
|
|
293
|
-
The recursive diverse-vs-blind gate runs through the keystone: `gate-cli.mts` →
|
|
294
|
-
`runGate` composes a `Persona` + the generic `fanout` combinator over the budget-conserving
|
|
295
|
-
`Supervisor`, with each child solved via the router and graded by the benchmark's own deployable
|
|
296
|
-
`adapter.judge` (selector ≠ oracle). Each rollout is written to a durable canonical corpus; the deep
|
|
297
|
-
statistics (paired bootstrap with Benjamini-Hochberg correction, selector replay) come from
|
|
298
|
-
`corpus-report.mts` and `corpus-replay.mts` over that corpus, computed once and offline. See
|
|
299
|
-
`bench/HARNESS.md` and `docs/learning-flywheel.md`.
|
|
300
|
-
|
|
301
|
-
## Defaults
|
|
302
|
-
|
|
303
|
-
| Knob | Default | Override |
|
|
304
|
-
|---|---|---|
|
|
305
|
-
| Backend model | `gpt-4o-mini` (via `createOpenAICompatibleBackend`) | `model` option or `MODEL_NAME` env |
|
|
306
|
-
| Backend provider | `openai-compat` when `TANGLE_API_KEY`, else `openai` if `OPENAI_API_KEY` | `MODEL_PROVIDER` env |
|
|
307
|
-
| Router base URL | `https://router.tangle.tools/v1` | `TANGLE_ROUTER_BASE_URL` env |
|
|
308
|
-
| Sandbox base URL | `https://sandbox.tangle.tools` | `SANDBOX_API_URL` env |
|
|
309
|
-
| Loop iteration cap | 10 (`runLoop`) | `runLoop({ maxIterations })` |
|
|
310
|
-
| Driver | none, required by `runLoop` | an inline `Driver` (`plan`/`decide`) |
|
|
311
|
-
| Strategy budget (suite) | 3 rollouts/shots per strategy per task | `runBenchmark({ budget })` |
|
|
312
|
-
| Winner selection (coder delegate) | `highest-score` | `winnerSelection` option |
|
|
313
|
-
| KB gate min passage | 12 chars | `createKbGate({ minPassageChars })` |
|
|
314
|
-
| `selfImprove` gate | held-out gate (default) | pass `gate: defaultProductionGate` for red-team hardening |
|
|
315
|
-
| OTEL export | off | set `OTEL_EXPORTER_OTLP_ENDPOINT` |
|
|
316
|
-
| Loop-runner mode failure | recorded as `{ ok: false }` | `runDelegatedLoop` never crashes on a thrown engine |
|
|
317
|
-
|
|
318
|
-
## Composition with the stack
|
|
319
|
-
|
|
320
63
|
```
|
|
321
|
-
agent-runtime handleChatTurn, runLoop + drivers, runProgram, runDelegatedLoop, createMcpServer,
|
|
322
|
-
improvementDriver, createKbGate, buildLoopOtelSpans, defineAgent
|
|
323
64
|
|
|
324
|
-
|
|
325
|
-
runImprovementLoop (gepaDriver), heldOutGate, runAgentMatrix, analyzeRuns.
|
|
326
|
-
Consumes runtime traces, scores, gates promotion. agent-runtime depends on it,
|
|
327
|
-
never the reverse.
|
|
65
|
+
## How it works (the short version)
|
|
328
66
|
|
|
329
|
-
|
|
330
|
-
|
|
67
|
+
- **One agent, run two ways.** The same agent runs at "do the task" speed and at "get better at the task" speed. "Driver", "worker", and "coordinator" aren't separate types — they're roles one agent plays.
|
|
68
|
+
- **Everything is measured.** Every run is a trace: tokens, dollars, time, and a pass/fail score from a real check. "Better" is a number with a denominator, not a vibe — and "equally good but cheaper" is a result you can prove.
|
|
69
|
+
- **Improvement is gated.** A change ships only after it beats the current agent on fresh tasks no tuning step ever saw, with a statistical test — not a single lucky run.
|
|
70
|
+
- **The grader is honest.** Whatever gives feedback never sees the answer key, and scores are recomputed from the attempts actually run — an agent can't fabricate its own win.
|
|
331
71
|
|
|
332
|
-
|
|
333
|
-
execution surface every loop runs on.
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Subpath exports
|
|
72
|
+
## Examples
|
|
337
73
|
|
|
338
|
-
|
|
74
|
+
Runnable, grouped by what they show — copy the one nearest your task:
|
|
339
75
|
|
|
340
|
-
|
|
|
76
|
+
| Do this | Example |
|
|
341
77
|
|---|---|
|
|
342
|
-
|
|
|
343
|
-
|
|
|
344
|
-
|
|
|
345
|
-
|
|
|
346
|
-
|
|
|
347
|
-
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
`agent-eval-adoption` and `agent-stack-adoption` skills.
|
|
360
|
-
|
|
361
|
-
## Stability, tests, docs
|
|
362
|
-
|
|
363
|
-
Every public export is annotated `@stable` or `@experimental`. `@stable` exports do not change shape inside a minor version; `@experimental` ones may, and require a deliberate consumer bump.
|
|
364
|
-
|
|
365
|
-
```bash
|
|
366
|
-
pnpm test # kernel, drivers, MCP, delegate hardening, kb-gate, loop-runner, backends
|
|
367
|
-
pnpm typecheck
|
|
368
|
-
pnpm build
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
Deeper docs: [`docs/architecture.md`](./docs/architecture.md) (the canonical spine), [`docs/canonical-api.md`](./docs/canonical-api.md) (the anti-reinvention decision table), [`docs/learning-flywheel.md`](./docs/learning-flywheel.md) (the self-improvement thesis and the open gate), [`docs/concepts.md`](./docs/concepts.md) (mental model), [`docs/agent-bus-protocol.md`](./docs/agent-bus-protocol.md) (cross-gateway header contract), [`docs/durability-adapters.md`](./docs/durability-adapters.md) (SQL-backed `ConversationJournal`).
|
|
78
|
+
| Run a product chat turn | [`chat-handler`](./examples/chat-handler) |
|
|
79
|
+
| Drive a team of agents to a goal | [`supervise`](./examples/supervise) · [`recursive-supervisor`](./examples/recursive-supervisor) |
|
|
80
|
+
| Benchmark strategies on your own domain | [`coding-benchmark`](./examples/coding-benchmark) |
|
|
81
|
+
| Benchmark **harnesses × models** over a real task suite (the real WebCode dataset) | [`webcode-matrix`](./examples/webcode-matrix) |
|
|
82
|
+
| Render a **multi-profile leaderboard** — ranked board + score matrix + SVG/HTML charts, any domain | `leaderboard(records)` → `renderLeaderboardMarkdown` / `Svg` / `Html` |
|
|
83
|
+
| Trace + bill + effort-gate the WebCode benchmark (the Intelligence SDK) | [`intelligence-webcode`](./examples/intelligence-webcode) |
|
|
84
|
+
| Self-improve an agent, gated on a held-out set | [`improve`](./examples/improve) · [`self-improving-coder`](./examples/self-improving-coder) |
|
|
85
|
+
| Study coordination vs raw compute | [`ablation-suite`](./examples/ablation-suite) |
|
|
86
|
+
|
|
87
|
+
All 28 live in [`examples/`](./examples).
|
|
88
|
+
|
|
89
|
+
## Where to go next
|
|
90
|
+
|
|
91
|
+
- New here? [`docs/concepts.md`](./docs/concepts.md) — the mental model in plain terms.
|
|
92
|
+
- [`docs/canonical-api.md`](./docs/canonical-api.md) — find the primitive: "I want to ___ → use ___".
|
|
93
|
+
- [`docs/architecture.md`](./docs/architecture.md) — the design, end to end.
|
|
94
|
+
- [`bench/HARNESS.md`](./bench/HARNESS.md) — the experiment harness and how to run a benchmark.
|
package/dist/agent.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-AHZ3YBL6.js";
|
|
2
2
|
import {
|
|
3
3
|
createSandboxForSpec
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-44CX5JU6.js";
|
|
5
|
+
import "./chunk-IPEQ3ERC.js";
|
|
6
|
+
import "./chunk-H7IBHAFT.js";
|
|
7
7
|
import {
|
|
8
8
|
mapSandboxEvent
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4J6RBI3K.js";
|
|
10
10
|
import "./chunk-5JAUQZQA.js";
|
|
11
11
|
import "./chunk-NBV35BR6.js";
|
|
12
12
|
import "./chunk-WIR4HOOJ.js";
|
package/dist/analyst-loop.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildLoopSpanNodes
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IPEQ3ERC.js";
|
|
4
4
|
import {
|
|
5
5
|
addTokenUsage,
|
|
6
6
|
deleteBoxSafe,
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
throwIfAborted,
|
|
16
16
|
withTimeout,
|
|
17
17
|
zeroTokenUsage
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-H7IBHAFT.js";
|
|
19
19
|
import {
|
|
20
20
|
extractLlmCallEvent
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-4J6RBI3K.js";
|
|
22
22
|
import {
|
|
23
23
|
harnessInvocation,
|
|
24
24
|
runLocalHarness
|
|
@@ -6479,4 +6479,4 @@ export {
|
|
|
6479
6479
|
createInProcessTransport,
|
|
6480
6480
|
serveCoordinationMcp
|
|
6481
6481
|
};
|
|
6482
|
-
//# sourceMappingURL=chunk-
|
|
6482
|
+
//# sourceMappingURL=chunk-44CX5JU6.js.map
|
|
@@ -29,6 +29,19 @@ function extractLlmCallEvent(event, agentRunName) {
|
|
|
29
29
|
}
|
|
30
30
|
return void 0;
|
|
31
31
|
}
|
|
32
|
+
function sumSandboxUsage(events, agentRunName = "agent") {
|
|
33
|
+
let input = 0;
|
|
34
|
+
let output = 0;
|
|
35
|
+
let costUsd = 0;
|
|
36
|
+
for (const ev of events) {
|
|
37
|
+
const call = extractLlmCallEvent(ev, agentRunName);
|
|
38
|
+
if (!call) continue;
|
|
39
|
+
input += call.tokensIn ?? 0;
|
|
40
|
+
output += call.tokensOut ?? 0;
|
|
41
|
+
costUsd += call.costUsd ?? 0;
|
|
42
|
+
}
|
|
43
|
+
return { input, output, costUsd };
|
|
44
|
+
}
|
|
32
45
|
function buildLlmCall(data, agentRunName) {
|
|
33
46
|
const tokensIn = pickFiniteNumber(data, ["tokensIn", "inputTokens", "prompt_tokens"]);
|
|
34
47
|
const tokensOut = pickFiniteNumber(data, ["tokensOut", "outputTokens", "completion_tokens"]);
|
|
@@ -73,6 +86,7 @@ function mapSandboxEvent(event, opts = {}) {
|
|
|
73
86
|
|
|
74
87
|
export {
|
|
75
88
|
extractLlmCallEvent,
|
|
89
|
+
sumSandboxUsage,
|
|
76
90
|
mapSandboxEvent
|
|
77
91
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-4J6RBI3K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/sandbox-events.ts"],"sourcesContent":["/**\n * Sandbox-event → runtime-event mapping.\n *\n * The sandbox SDK emits a polymorphic `SandboxEvent = { type, data, id? }`\n * whose `type` vocabulary is backend-determined (opencode, etc.) rather than\n * enumerated by the SDK. Two consumers project it:\n * - the loop kernel's cost ledger (`extractLlmCallEvent`) — sums usage off\n * every cost-bearing event, regardless of stream shape;\n * - the `AgentRuntime.act` streaming contract (`mapSandboxEvent`) — projects\n * incremental events to the `RuntimeStreamEvent` chat-UX vocabulary.\n *\n * Both live here so the empirically-observed `type` vocabulary has one home.\n */\n\nimport type { SandboxEvent } from '@tangle-network/sandbox'\nimport type { RuntimeStreamEvent } from '../types'\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 * 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` / `usage` — same shape under a dedicated type\n *\n * Numeric coercion is strict: `Number.isFinite` gates every accumulator write\n * so a sentinel `NaN` from a misbehaving backend cannot poison the ledger.\n */\nexport function 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 // sandbox 0.4.0 terminal event: `data = { tokenUsage: { inputTokens, outputTokens,\n // reasoningTokens, cacheReadInputTokens }, totalCostUsd }`. Usage lives under\n // `tokenUsage` (not `usage`) and the cost is top-level — neither matched the\n // branches above, so an in-process loopDispatch run reported {0,0} and the\n // backend-integrity guard misread a real run as a stub. Reasoning tokens are\n // billed output (reasoning models), so they fold into the output count.\n if (type === 'done') {\n const usage = data.tokenUsage as Record<string, unknown> | undefined\n if (!usage || typeof usage !== 'object') return undefined\n const out = pickFiniteNumber(usage, ['outputTokens', 'completion_tokens', 'tokensOut'])\n const reasoning = pickFiniteNumber(usage, ['reasoningTokens'])\n const mergedOut =\n out !== undefined || reasoning !== undefined ? (out ?? 0) + (reasoning ?? 0) : undefined\n return buildLlmCall(\n {\n inputTokens: usage.inputTokens,\n outputTokens: mergedOut,\n totalCostUsd: data.totalCostUsd,\n model: data.model ?? usage.model,\n },\n agentRunName,\n )\n }\n return undefined\n}\n\n/**\n * Sum the token usage + USD cost of a sandbox turn's events — the one honest way to meter an\n * `openSandboxRun` cell. Folds `extractLlmCallEvent` over the stream (which reads usage off EVERY backend\n * event shape), so a `runProfileMatrix` dispatch can report it to `ctx.cost`:\n *\n * const turn = await run.start(prompt)\n * const u = sumSandboxUsage(turn.events)\n * if (u.input || u.output) ctx.cost.observeTokens({ input: u.input, output: u.output })\n * if (u.costUsd) ctx.cost.observe(u.costUsd, 'sandbox-cell')\n *\n * Without this a cell reads `{tokens:0, cost:0}` and the backend-integrity guard correctly aborts the\n * matrix as a stub. `agentRunName` is the fallback model label for cost-only events (default `'agent'`).\n */\nexport function sumSandboxUsage(\n events: readonly SandboxEvent[],\n agentRunName = 'agent',\n): { input: number; output: number; costUsd: number } {\n let input = 0\n let output = 0\n let costUsd = 0\n for (const ev of events) {\n const call = extractLlmCallEvent(ev, agentRunName)\n if (!call) continue\n input += call.tokensIn ?? 0\n output += call.tokensOut ?? 0\n costUsd += call.costUsd ?? 0\n }\n return { input, output, costUsd }\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 * Project one `SandboxEvent` onto the `RuntimeStreamEvent` chat-UX vocabulary,\n * for runtimes that bridge a sandbox `streamPrompt` into the\n * `AgentRuntime.act` streaming contract. Returns `undefined` for events that\n * have no faithful projection — the raw stream is preserved separately for the\n * `OutputAdapter`, so an unmapped event never loses data.\n *\n * Mapped (the task-optional incremental variants — no synthesized task\n * lifecycle, no guessed tool-part shapes):\n * - `message.part.updated` text part → `text_delta`\n * - `message.part.updated` reasoning/thinking part → `reasoning_delta`\n * - cost-bearing events → `llm_call` (shared with the ledger extractor)\n *\n * The opencode backend emits incremental text as\n * `{ type: 'message.part.updated', data: { part: { type, text }, delta } }`;\n * `delta` is the increment, `part.text` the running accumulation.\n */\nexport function mapSandboxEvent(\n event: SandboxEvent,\n opts: { agentRunName?: string } = {},\n): RuntimeStreamEvent | 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 === 'message.part.updated') {\n const part =\n data.part && typeof data.part === 'object' ? (data.part as Record<string, unknown>) : {}\n const partType = String(part.type ?? '')\n const delta = typeof data.delta === 'string' ? data.delta : undefined\n const text = delta ?? (typeof part.text === 'string' ? part.text : undefined)\n if (text === undefined) return undefined\n if (partType === 'text') return { type: 'text_delta', text }\n if (partType === 'reasoning' || partType === 'thinking')\n return { type: 'reasoning_delta', text }\n return undefined\n }\n\n return extractLlmCallEvent(event, opts.agentRunName ?? 'agent')\n}\n"],"mappings":"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","names":[]}
|