@tangle-network/agent-runtime 0.79.3 → 0.80.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/README.md +54 -325
- package/dist/agent.js +5 -5
- package/dist/analyst-loop.js +2 -2
- package/dist/{chunk-VMNEQHJR.js → chunk-3X53HYRW.js} +17 -2
- package/dist/chunk-3X53HYRW.js.map +1 -0
- package/dist/{chunk-T2HVQVB4.js → chunk-4J6RBI3K.js} +15 -1
- package/dist/chunk-4J6RBI3K.js.map +1 -0
- package/dist/{chunk-IODKUOBA.js → chunk-C2FZ6GR6.js} +2 -2
- package/dist/{chunk-63MHOCIE.js → chunk-DH72UOYR.js} +4 -4
- package/dist/{chunk-Z3RRRPRB.js → chunk-H7IBHAFT.js} +23 -14
- package/dist/chunk-H7IBHAFT.js.map +1 -0
- package/dist/{chunk-PVPFDTO3.js → chunk-LLJRUTE7.js} +2 -2
- package/dist/{chunk-PBE35ULD.js → chunk-M3Y362RW.js} +2 -2
- package/dist/{chunk-AG335EXG.js → chunk-QSO2TVDS.js} +668 -74
- package/dist/chunk-QSO2TVDS.js.map +1 -0
- package/dist/{chunk-KRULXIWS.js → chunk-TPII5AU7.js} +55 -7
- package/dist/chunk-TPII5AU7.js.map +1 -0
- package/dist/{coordination-DCmljYDf.d.ts → coordination-BI9tpcmF.d.ts} +12 -3
- 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 +30 -2
- package/dist/intelligence.js +33 -23
- package/dist/intelligence.js.map +1 -1
- package/dist/{loop-runner-bin-C4X0FZ2Z.d.ts → loop-runner-bin-CBkLsyFg.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 +265 -14
- package/dist/loops.js +28 -6
- package/dist/mcp/bin.js +5 -5
- package/dist/mcp/index.d.ts +6 -6
- package/dist/mcp/index.js +7 -7
- package/dist/{router-client-Ak2IGuXq.d.ts → router-client-D6Ocf4jG.d.ts} +19 -0
- package/dist/{types-C1sozrte.d.ts → types-By9LXllv.d.ts} +9 -2
- package/dist/{worktree-fanout-CXGzHET4.d.ts → worktree-fanout-D9Z2dMS9.d.ts} +2 -2
- package/dist/{worktree-harness-Bmho9SH0.d.ts → worktree-harness-Dx8XULW3.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-KRULXIWS.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-IODKUOBA.js.map → chunk-C2FZ6GR6.js.map} +0 -0
- /package/dist/{chunk-63MHOCIE.js.map → chunk-DH72UOYR.js.map} +0 -0
- /package/dist/{chunk-PVPFDTO3.js.map → chunk-LLJRUTE7.js.map} +0 -0
- /package/dist/{chunk-PBE35ULD.js.map → chunk-M3Y362RW.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,371 +1,100 @@
|
|
|
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
|
-
|
|
11
|
+
**See it run in 30 seconds** (offline, no keys) — the one move everything else builds on, a driver reading a worker's output and composing the next step from it:
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
13
|
+
```bash
|
|
14
|
+
pnpm tsx examples/driver-loop/driver-loop.ts
|
|
15
|
+
```
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
## What you do with it
|
|
26
18
|
|
|
27
19
|
| You want to… | Call |
|
|
28
20
|
|---|---|
|
|
29
|
-
| Run
|
|
30
|
-
| Have
|
|
31
|
-
| **
|
|
21
|
+
| Run a **chat turn** — what every product agent does in production | `handleChatTurn(...)` |
|
|
22
|
+
| Have one agent **supervise a team of agents** toward a goal | `supervise(profile, task, opts)` |
|
|
23
|
+
| **Improve** an agent and prove the gain on fresh tasks | `improve(profile, findings, opts)` |
|
|
32
24
|
|
|
33
25
|
### Run a chat turn
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
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
28
|
|
|
37
29
|
```ts
|
|
38
30
|
import { handleChatTurn } from '@tangle-network/agent-runtime'
|
|
39
31
|
|
|
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
|
-
}
|
|
32
|
+
const result = handleChatTurn({
|
|
33
|
+
identity: { tenantId, sessionId: threadId, userId, turnIndex: 0 },
|
|
34
|
+
hooks: {
|
|
35
|
+
produce: () => ({ stream: box.streamPrompt(userMessage), finalText: () => box.lastResponse() }),
|
|
36
|
+
persistAssistantMessage: async ({ identity, finalText }) => db.insertMessage(identity, finalText),
|
|
37
|
+
},
|
|
38
|
+
waitUntil,
|
|
39
|
+
})
|
|
40
|
+
return new Response(result.body, { headers: { 'content-type': result.contentType } })
|
|
58
41
|
```
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
### Supervise a team of agents
|
|
61
44
|
|
|
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.
|
|
45
|
+
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
46
|
|
|
66
47
|
```ts
|
|
67
48
|
import { supervise } from '@tangle-network/agent-runtime/loops'
|
|
68
49
|
|
|
69
50
|
const result = await supervise(
|
|
70
51
|
{ name: 'supervisor', harness: null, systemPrompt: 'Delegate to workers; do not solve the task yourself.' },
|
|
71
|
-
'Implement the feature and make
|
|
72
|
-
{ budget, router, backend }, //
|
|
52
|
+
'Implement the feature and make the tests pass.',
|
|
53
|
+
{ budget, router, backend }, // backend = where workers run: router-tools | sandbox+harness | bridge
|
|
73
54
|
)
|
|
74
55
|
```
|
|
75
56
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
### Self-improve an agent
|
|
57
|
+
### Improve an agent
|
|
79
58
|
|
|
80
|
-
`improve`
|
|
59
|
+
`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
60
|
|
|
82
61
|
```ts
|
|
83
62
|
import { improve } from '@tangle-network/agent-runtime'
|
|
84
63
|
|
|
85
64
|
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 },
|
|
65
|
+
surface: 'prompt', // what to optimize: prompt | skills | code
|
|
66
|
+
gate: 'holdout', // certified on a held-back exam, never the practice set
|
|
67
|
+
scenarios, judge, agent, // how to measure a candidate
|
|
197
68
|
})
|
|
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
69
|
```
|
|
321
|
-
agent-runtime handleChatTurn, runLoop + drivers, runProgram, runDelegatedLoop, createMcpServer,
|
|
322
|
-
improvementDriver, createKbGate, buildLoopOtelSpans, defineAgent
|
|
323
70
|
|
|
324
|
-
|
|
325
|
-
runImprovementLoop (gepaDriver), heldOutGate, runAgentMatrix, analyzeRuns.
|
|
326
|
-
Consumes runtime traces, scores, gates promotion. agent-runtime depends on it,
|
|
327
|
-
never the reverse.
|
|
71
|
+
## How it works (the short version)
|
|
328
72
|
|
|
329
|
-
|
|
330
|
-
|
|
73
|
+
- **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.
|
|
74
|
+
- **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.
|
|
75
|
+
- **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.
|
|
76
|
+
- **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
77
|
|
|
332
|
-
|
|
333
|
-
execution surface every loop runs on.
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Subpath exports
|
|
78
|
+
## Examples
|
|
337
79
|
|
|
338
|
-
|
|
80
|
+
Runnable, grouped by what they show — copy the one nearest your task:
|
|
339
81
|
|
|
340
|
-
|
|
|
82
|
+
| Do this | Example |
|
|
341
83
|
|---|---|
|
|
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`).
|
|
84
|
+
| Run a product chat turn | [`chat-handler`](./examples/chat-handler) |
|
|
85
|
+
| Drive a team of agents to a goal | [`supervise`](./examples/supervise) · [`recursive-supervisor`](./examples/recursive-supervisor) |
|
|
86
|
+
| Benchmark strategies on your own domain | [`coding-benchmark`](./examples/coding-benchmark) |
|
|
87
|
+
| Benchmark **harnesses × models** over a real task suite (the real WebCode dataset) | [`webcode-matrix`](./examples/webcode-matrix) |
|
|
88
|
+
| Render a **multi-profile leaderboard** — ranked board + score matrix + SVG/HTML charts, any domain | `leaderboard(records)` → `renderLeaderboardMarkdown` / `Svg` / `Html` |
|
|
89
|
+
| Trace + bill + effort-gate the WebCode benchmark (the Intelligence SDK) | [`intelligence-webcode`](./examples/intelligence-webcode) |
|
|
90
|
+
| Self-improve an agent, gated on a held-out set | [`improve`](./examples/improve) · [`self-improving-coder`](./examples/self-improving-coder) |
|
|
91
|
+
| Study coordination vs raw compute | [`ablation-suite`](./examples/ablation-suite) |
|
|
92
|
+
|
|
93
|
+
All 28 live in [`examples/`](./examples).
|
|
94
|
+
|
|
95
|
+
## Where to go next
|
|
96
|
+
|
|
97
|
+
- New here? [`docs/concepts.md`](./docs/concepts.md) — the mental model in plain terms.
|
|
98
|
+
- [`docs/canonical-api.md`](./docs/canonical-api.md) — find the primitive: "I want to ___ → use ___".
|
|
99
|
+
- [`docs/architecture.md`](./docs/architecture.md) — the design, end to end.
|
|
100
|
+
- [`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-QSO2TVDS.js";
|
|
2
2
|
import {
|
|
3
3
|
createSandboxForSpec
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-TPII5AU7.js";
|
|
5
|
+
import "./chunk-3X53HYRW.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,5 +1,5 @@
|
|
|
1
1
|
// src/otel-export.ts
|
|
2
|
-
var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.
|
|
2
|
+
var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.80.0" };
|
|
3
3
|
var GEN_AI = {
|
|
4
4
|
operation: "gen_ai.operation.name",
|
|
5
5
|
agentName: "gen_ai.agent.name",
|
|
@@ -98,6 +98,20 @@ function loopEventToOtelSpan(event, traceId, parentSpanId) {
|
|
|
98
98
|
status: { code: 1 }
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
+
function flatOtelSpan(name, attributes, traceId, timestampMs, parentSpanId) {
|
|
102
|
+
const ts = msToNs(timestampMs);
|
|
103
|
+
return {
|
|
104
|
+
traceId: padTraceId(traceId),
|
|
105
|
+
spanId: generateSpanId(),
|
|
106
|
+
parentSpanId: parentSpanId ? padSpanId(parentSpanId) : void 0,
|
|
107
|
+
name,
|
|
108
|
+
kind: 1,
|
|
109
|
+
startTimeUnixNano: ts,
|
|
110
|
+
endTimeUnixNano: ts,
|
|
111
|
+
attributes: toAttributes(attributes),
|
|
112
|
+
status: { code: 1 }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
101
115
|
function buildLoopOtelSpans(events, traceId, rootParentSpanId) {
|
|
102
116
|
const tid = padTraceId(traceId);
|
|
103
117
|
return buildLoopSpanNodes(events).map((node) => ({
|
|
@@ -358,9 +372,10 @@ async function exportEvalRuns(events, config) {
|
|
|
358
372
|
export {
|
|
359
373
|
createOtelExporter,
|
|
360
374
|
loopEventToOtelSpan,
|
|
375
|
+
flatOtelSpan,
|
|
361
376
|
buildLoopOtelSpans,
|
|
362
377
|
buildLoopSpanNodes,
|
|
363
378
|
INTELLIGENCE_WIRE_VERSION,
|
|
364
379
|
exportEvalRuns
|
|
365
380
|
};
|
|
366
|
-
//# sourceMappingURL=chunk-
|
|
381
|
+
//# sourceMappingURL=chunk-3X53HYRW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/otel-export.ts"],"sourcesContent":["/**\n * OTEL span exporter — streams LoopTraceEvents to an OTLP/HTTP collector.\n *\n * Reads OTEL_EXPORTER_OTLP_ENDPOINT + OTEL_EXPORTER_OTLP_HEADERS from env\n * when no explicit config is given. Keeps the runtime dep-free from\n * @opentelemetry/sdk-trace-base — minimal OTLP/JSON serializer.\n *\n * The exporter accepts both raw OtelSpan objects and LoopTraceEvents\n * (which get converted to OTLP spans automatically).\n */\n\nexport interface OtelExportConfig {\n /** OTLP endpoint. Reads OTEL_EXPORTER_OTLP_ENDPOINT env by default. */\n endpoint?: string\n /** OTLP headers. Reads OTEL_EXPORTER_OTLP_HEADERS env by default. */\n headers?: Record<string, string>\n /** Batch size before flush. Default 64. */\n batchSize?: number\n /** Flush interval ms. Default 5000. */\n flushIntervalMs?: number\n /** Resource attributes stamped on every export. */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Service name. Default 'agent-runtime'. */\n serviceName?: string\n}\n\nexport interface OtelExporter {\n /** Export a span. */\n exportSpan(span: OtelSpan): void\n /** Force flush pending spans. */\n flush(): Promise<void>\n /** Shutdown cleanly. */\n shutdown(): Promise<void>\n}\n\nexport interface OtelSpan {\n traceId: string\n spanId: string\n parentSpanId?: string\n name: string\n kind?: number\n startTimeUnixNano: string\n endTimeUnixNano: string\n attributes?: OtelAttribute[]\n status?: { code: number; message?: string }\n}\n\nexport interface OtelAttribute {\n key: string\n value: { stringValue?: string; intValue?: string; doubleValue?: number; boolValue?: boolean }\n}\n\ninterface OtlpResourceSpans {\n resource: { attributes: OtelAttribute[] }\n scopeSpans: Array<{ scope: { name: string; version: string }; spans: OtelSpan[] }>\n}\n\ninterface OtlpExport {\n resourceSpans: OtlpResourceSpans[]\n}\n\nconst SCOPE = { name: '@tangle-network/agent-runtime', version: '0.80.0' }\n\n/**\n * Current (non-deprecated) OpenTelemetry GenAI semantic-convention keys.\n * Registry: https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/\n * NB: `gen_ai.system` / `gen_ai.usage.prompt_tokens` / `completion_tokens` are\n * DEPRECATED — do not emit them. We use `provider.name` + `input/output_tokens`.\n */\nconst GEN_AI = {\n operation: 'gen_ai.operation.name',\n agentName: 'gen_ai.agent.name',\n conversationId: 'gen_ai.conversation.id',\n inputTokens: 'gen_ai.usage.input_tokens',\n outputTokens: 'gen_ai.usage.output_tokens',\n} as const\n\n/**\n * Create an OTEL exporter. Returns undefined when no endpoint is configured.\n */\nexport function createOtelExporter(config?: OtelExportConfig): OtelExporter | undefined {\n const resolvedEndpoint =\n config?.endpoint ??\n (typeof process !== 'undefined' ? process.env.OTEL_EXPORTER_OTLP_ENDPOINT : undefined)\n if (!resolvedEndpoint) return undefined\n const endpoint: string = resolvedEndpoint\n\n const headers = config?.headers ?? parseHeadersFromEnv()\n const batchSize = config?.batchSize ?? 64\n const flushIntervalMs = config?.flushIntervalMs ?? 5000\n const serviceName = config?.serviceName ?? 'agent-runtime'\n const resourceAttrs = config?.resourceAttributes ?? {}\n\n const pending: OtelSpan[] = []\n let timer: ReturnType<typeof setInterval> | undefined\n let stopped = false\n\n const exporter: OtelExporter = {\n exportSpan(span: OtelSpan): void {\n if (stopped) return\n pending.push(span)\n if (pending.length >= batchSize) {\n void doFlush()\n }\n },\n\n async flush(): Promise<void> {\n await doFlush()\n },\n\n async shutdown(): Promise<void> {\n stopped = true\n if (timer !== undefined) {\n clearInterval(timer)\n timer = undefined\n }\n await doFlush()\n },\n }\n\n timer = setInterval(() => {\n if (pending.length > 0) void doFlush()\n }, flushIntervalMs)\n if (typeof timer === 'object' && 'unref' in timer) {\n ;(timer as NodeJS.Timeout).unref()\n }\n\n async function doFlush(): Promise<void> {\n if (pending.length === 0) return\n const batch = pending.splice(0)\n const body: OtlpExport = {\n resourceSpans: [\n {\n resource: {\n attributes: toAttributes({\n 'service.name': serviceName,\n ...resourceAttrs,\n }),\n },\n scopeSpans: [{ scope: SCOPE, spans: batch }],\n },\n ],\n }\n const url = `${endpoint.replace(/\\/+$/, '')}/v1/traces`\n try {\n await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...headers },\n body: JSON.stringify(body),\n })\n } catch {\n // Best-effort — telemetry export must not crash the runtime.\n }\n }\n\n return exporter\n}\n\n/**\n * Convert a LoopTraceEvent into an OtelSpan for export.\n */\nexport function loopEventToOtelSpan(\n event: {\n kind: string\n runId: string\n timestamp: number\n payload: object\n },\n traceId: string,\n parentSpanId?: string,\n): OtelSpan {\n const spanId = generateSpanId()\n const attrs: Record<string, string | number | boolean> = {\n 'loop.event_kind': event.kind,\n 'loop.run_id': event.runId,\n }\n for (const [k, v] of Object.entries(event.payload)) {\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n attrs[`loop.${k}`] = v\n }\n }\n const ts = msToNs(event.timestamp)\n return {\n traceId: padTraceId(traceId),\n spanId,\n parentSpanId: parentSpanId ? padSpanId(parentSpanId) : undefined,\n name: event.kind,\n kind: 1,\n startTimeUnixNano: ts,\n endTimeUnixNano: ts,\n attributes: toAttributes(attrs),\n status: { code: 1 },\n }\n}\n\n/**\n * Build a single flat OtelSpan whose attribute keys are emitted VERBATIM — no\n * `loop.` namespace. Use for non-loop spans (e.g. the intelligence per-run\n * span) whose keys ARE the downstream contract (`gen_ai.request.model`,\n * `tangle.sessionId`) and are exact-matched by readers. `loopEventToOtelSpan`\n * namespaces payload keys because loop payload fields are free-form; do not\n * reuse it for spans with contract keys.\n */\nexport function flatOtelSpan(\n name: string,\n attributes: Record<string, string | number | boolean>,\n traceId: string,\n timestampMs: number,\n parentSpanId?: string,\n): OtelSpan {\n const ts = msToNs(timestampMs)\n return {\n traceId: padTraceId(traceId),\n spanId: generateSpanId(),\n parentSpanId: parentSpanId ? padSpanId(parentSpanId) : undefined,\n name,\n kind: 1,\n startTimeUnixNano: ts,\n endTimeUnixNano: ts,\n attributes: toAttributes(attributes),\n status: { code: 1 },\n }\n}\n\n/**\n * Sink-neutral node in a reconstructed loop span tree. The root node's\n * `parentSpanId` is `undefined` — sinks decide how to parent it (the OTEL\n * mapper attaches the inherited delegation span; the delegation journal\n * leaves it as the tree root).\n */\nexport interface LoopSpanNode {\n spanId: string\n parentSpanId?: string\n /** `'loop'` | `'loop.round'` | `'loop.iteration'`. */\n name: string\n /** Topology level: loop root, plan round, or iteration branch. */\n kind: 'loop' | 'round' | 'branch'\n startMs: number\n endMs: number\n attrs: Record<string, string | number | boolean>\n /** True when the iteration carried an error — maps to OTEL status code 2. */\n error: boolean\n}\n\n/**\n * Build a nested, real-duration OTLP span tree for ONE loop run from its full\n * ordered `LoopTraceEvent` stream. Unlike `loopEventToOtelSpan` (one flat,\n * zero-duration span per event), this reconstructs the topology hierarchy a\n * GenAI trace viewer renders natively:\n *\n * loop (invoke_workflow)\n * └─ loop.round[k] (invoke_workflow) ← tangle.loop.move.{kind,width,rationale}\n * ├─ loop.iteration[i] (invoke_agent) ← gen_ai.agent.name + usage + verdict + placement\n * └─ …\n *\n * Attributes follow the current GenAI semconv (`gen_ai.*`) where they apply and\n * a namespaced `tangle.loop.*` / `tangle.cost.usd` extension for topology /\n * verdict / placement / cost (not yet standardized). Pure: feed it a buffered\n * per-runId event array (e.g. flushed on `loop.ended`) and export the result.\n */\nexport function buildLoopOtelSpans(\n events: ReadonlyArray<{ kind: string; runId: string; timestamp: number; payload: object }>,\n traceId: string,\n rootParentSpanId?: string,\n): OtelSpan[] {\n const tid = padTraceId(traceId)\n return buildLoopSpanNodes(events).map((node) => ({\n traceId: tid,\n spanId: node.spanId,\n parentSpanId: node.parentSpanId\n ? padSpanId(node.parentSpanId)\n : rootParentSpanId\n ? padSpanId(rootParentSpanId)\n : undefined,\n name: node.name,\n kind: 1,\n startTimeUnixNano: msToNs(node.startMs),\n endTimeUnixNano: msToNs(node.endMs),\n attributes: toAttributes(node.attrs),\n status: { code: node.error ? 2 : 1 },\n }))\n}\n\n/**\n * Sink-neutral core behind {@link buildLoopOtelSpans}: reconstruct the\n * loop → round → branch span tree from one run's ordered `LoopTraceEvent`\n * stream. Consumed by the OTEL mapper above and by the MCP delegation\n * journal's compact trace tee — one topology reconstruction, two sinks.\n * Tolerates partial streams (a run that never reached `loop.ended` closes\n * at the last observed event's timestamp).\n */\nexport function buildLoopSpanNodes(\n events: ReadonlyArray<{ kind: string; runId: string; timestamp: number; payload: object }>,\n): LoopSpanNode[] {\n if (events.length === 0) return []\n const out: LoopSpanNode[] = []\n const num = (v: unknown): number | undefined =>\n typeof v === 'number' && Number.isFinite(v) ? v : undefined\n const str = (v: unknown): string | undefined =>\n typeof v === 'string' && v.length > 0 ? v : undefined\n const rec = (v: unknown): Record<string, unknown> =>\n v && typeof v === 'object' ? (v as Record<string, unknown>) : {}\n\n const started = events.find((e) => e.kind === 'loop.started')\n const ended = events.find((e) => e.kind === 'loop.ended')\n const runId = events[0]?.runId ?? ''\n const rootStart = started?.timestamp ?? events[0]!.timestamp\n const rootEnd = ended?.timestamp ?? events[events.length - 1]!.timestamp\n const rootId = generateSpanId()\n\n const make = (\n spanId: string,\n parentSpanId: string | undefined,\n name: string,\n kind: LoopSpanNode['kind'],\n startMs: number,\n endMs: number,\n attrs: Record<string, string | number | boolean>,\n error = false,\n ): LoopSpanNode => ({\n spanId,\n parentSpanId,\n name,\n kind,\n startMs,\n endMs,\n attrs,\n error,\n })\n\n // root\n const sp = rec(started?.payload)\n const rootAttrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_workflow',\n [GEN_AI.conversationId]: runId,\n // Explicit run identity under the tangle namespace so a consuming system of\n // record (Tangle Intelligence's run spine) maps this span tree to one run\n // deterministically, instead of inferring it from the conversation id.\n 'tangle.run.id': runId,\n 'tangle.loop.driver': str(sp.driver) ?? 'driver',\n }\n if (Array.isArray(sp.agentRunNames) && sp.agentRunNames.length > 0) {\n rootAttrs['tangle.loop.agents'] = sp.agentRunNames.map(String).join(',')\n // Subject grain: the primary agent/profile this run drove. Lets the spine\n // group runs under the thing being worked on rather than one service-wide\n // bucket. `agentRunNames[0]` is the lead agent on a fan-out.\n rootAttrs['tangle.subject.key'] = String(sp.agentRunNames[0])\n }\n if (ended) {\n const ep = rec(ended.payload)\n const win = num(ep.winnerIterationIndex)\n if (win !== undefined) rootAttrs['tangle.loop.winner.iteration_index'] = win\n const cost = num(ep.totalCostUsd)\n if (cost !== undefined) rootAttrs['tangle.cost.usd'] = cost\n const dur = num(ep.durationMs)\n if (dur !== undefined) rootAttrs['tangle.loop.duration_ms'] = dur\n const iters = num(ep.iterations)\n if (iters !== undefined) rootAttrs['tangle.loop.iterations'] = iters\n }\n out.push(make(rootId, undefined, 'loop', 'loop', rootStart, rootEnd, rootAttrs))\n\n // rounds + iterations\n const iterStartTs = new Map<number, number>()\n const placementByIdx = new Map<number, Record<string, string>>()\n let currentRoundId: string | undefined\n let pendingRound:\n | { id: string; start: number; attrs: Record<string, string | number | boolean> }\n | undefined\n const flushRound = (endMs: number) => {\n if (!pendingRound) return\n out.push(\n make(\n pendingRound.id,\n rootId,\n 'loop.round',\n 'round',\n pendingRound.start,\n endMs,\n pendingRound.attrs,\n ),\n )\n pendingRound = undefined\n }\n\n for (const e of events) {\n const p = rec(e.payload)\n switch (e.kind) {\n case 'loop.plan': {\n flushRound(e.timestamp)\n const id = generateSpanId()\n const roundIdx = num(p.roundIndex) ?? 0\n const attrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_workflow',\n 'tangle.loop.round.index': roundIdx,\n 'tangle.loop.move.kind': str(p.moveKind) ?? 'unknown',\n 'tangle.loop.move.round': roundIdx,\n 'tangle.loop.move.width': num(p.plannedCount) ?? 0,\n }\n const r = str(p.rationale)\n if (r) attrs['tangle.loop.move.rationale'] = r\n const parent = num(p.parentIndex)\n if (parent !== undefined) attrs['tangle.loop.move.parent_index'] = parent\n if (Array.isArray(p.childIndices) && p.childIndices.length > 0) {\n attrs['tangle.loop.move.child_indices'] = p.childIndices.map(String).join(',')\n }\n pendingRound = { id, start: e.timestamp, attrs }\n currentRoundId = id\n break\n }\n case 'loop.iteration.started': {\n const idx = num(p.iterationIndex)\n if (idx !== undefined) iterStartTs.set(idx, e.timestamp)\n break\n }\n case 'loop.iteration.dispatch': {\n const idx = num(p.iterationIndex)\n if (idx === undefined) break\n const place: Record<string, string> = {}\n const kind = str(p.placement)\n if (kind) place['tangle.loop.placement.kind'] = kind\n const sid = str(p.sandboxId)\n if (sid) place['tangle.sandbox.id'] = sid\n const fid = str(p.fleetId)\n if (fid) place['tangle.fleet.id'] = fid\n const mid = str(p.machineId)\n if (mid) place['tangle.machine.id'] = mid\n placementByIdx.set(idx, place)\n break\n }\n case 'loop.iteration.ended': {\n const idx = num(p.iterationIndex) ?? 0\n const start = iterStartTs.get(idx) ?? e.timestamp\n const err = str(p.error)\n const attrs: Record<string, string | number | boolean> = {\n [GEN_AI.operation]: 'invoke_agent',\n 'tangle.loop.iteration.index': idx,\n }\n const agent = str(p.agentRunName)\n if (agent) attrs[GEN_AI.agentName] = agent\n const tu = rec(p.tokenUsage)\n const inTok = num(tu.input)\n if (inTok !== undefined) attrs[GEN_AI.inputTokens] = inTok\n const outTok = num(tu.output)\n if (outTok !== undefined) attrs[GEN_AI.outputTokens] = outTok\n const cost = num(p.costUsd)\n if (cost !== undefined) attrs['tangle.cost.usd'] = cost\n const verdict = rec(p.verdict)\n if (typeof verdict.valid === 'boolean') attrs['tangle.loop.verdict.valid'] = verdict.valid\n const score = num(verdict.score)\n if (score !== undefined) attrs['tangle.loop.verdict.score'] = score\n if (err) attrs['tangle.loop.error'] = err\n const gid = num(p.groupId)\n if (gid !== undefined) attrs['tangle.loop.iteration.group_id'] = gid\n const par = num(p.parentIndex)\n if (par !== undefined) attrs['tangle.loop.iteration.parent_index'] = par\n const dur = num(p.durationMs)\n if (dur !== undefined) attrs['tangle.loop.iteration.duration_ms'] = dur\n const preview = str(p.outputPreview)\n if (preview) attrs['tangle.loop.iteration.output_preview'] = preview\n Object.assign(attrs, placementByIdx.get(idx) ?? {})\n out.push(\n make(\n generateSpanId(),\n currentRoundId ?? rootId,\n 'loop.iteration',\n 'branch',\n start,\n e.timestamp,\n attrs,\n err !== undefined,\n ),\n )\n break\n }\n case 'loop.decision': {\n if (pendingRound) {\n const dec = str(p.decision)\n if (dec) pendingRound.attrs['tangle.loop.decision'] = dec\n flushRound(e.timestamp)\n }\n currentRoundId = undefined\n break\n }\n }\n }\n flushRound(rootEnd)\n return out\n}\n\nfunction parseHeadersFromEnv(): Record<string, string> {\n if (typeof process === 'undefined') return {}\n const raw = process.env.OTEL_EXPORTER_OTLP_HEADERS\n if (!raw) return {}\n const out: Record<string, string> = {}\n for (const pair of raw.split(',')) {\n const eq = pair.indexOf('=')\n if (eq < 0) continue\n const key = pair.slice(0, eq).trim()\n const value = pair.slice(eq + 1).trim()\n if (key) out[key] = value\n }\n return out\n}\n\nfunction toAttributes(record: Record<string, string | number | boolean>): OtelAttribute[] {\n return Object.entries(record).map(([key, value]) => ({\n key,\n value:\n typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value.toString() }\n : { doubleValue: value }\n : typeof value === 'boolean'\n ? { boolValue: value }\n : { stringValue: value },\n }))\n}\n\nfunction msToNs(ms: number): string {\n return (BigInt(Math.floor(ms)) * 1_000_000n).toString()\n}\n\nfunction padSpanId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 16).padEnd(16, '0')\n}\n\nfunction padTraceId(id: string): string {\n const cleaned = id.replace(/-/g, '')\n return cleaned.slice(0, 32).padEnd(32, '0')\n}\n\nfunction generateSpanId(): string {\n const bytes = new Uint8Array(8)\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes)\n } else {\n for (let i = 0; i < 8; i++) bytes[i] = Math.floor(Math.random() * 256)\n }\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('')\n}\n\n// ─── Eval-run ingest (self-improvement provenance) ───────────────────────────\n//\n// Tangle Intelligence has a first-class, non-trace record for self-improvement\n// runs: POST /v1/ingest/eval-runs (\"Mode D\"). Each generation carries a\n// `surfaceHash` (the proposed-change identity) + arbitrary `surface` provenance;\n// a later `gate-decided` event re-emits the same `runId` (idempotent upsert) with\n// a real `gateDecision` + `holdoutLift`, so proposal→verdict is one diffable\n// record. This is how a consumer's RSI loop records WHAT it changed, WHY, from\n// which evidence — the audit trail behind agentic self-improvement.\n\n/** Wire version the eval-runs ingest enforces (X-Tangle-Wire-Version + body). */\nexport const INTELLIGENCE_WIRE_VERSION = '2026-05-26.v1'\n\nexport interface EvalRunGeneration {\n /** 0-based ordinal of this generation within the run (required by ingest). */\n index: number\n /** Identity of the proposed surface change (content-addressed hash). */\n surfaceHash: string\n /** Arbitrary provenance for this generation (rationale, evidence, source). */\n surface?: unknown\n /** Per-scenario results; empty until the generation is measured. */\n cells?: unknown[]\n /** Mean composite score (0 when unmeasured — pair with labels.measured). */\n compositeMean: number\n costUsd: number\n durationMs: number\n}\n\nexport interface EvalRunEvent {\n runId: string\n runDir: string\n /** ISO timestamp. */\n timestamp: string\n status:\n | 'started'\n | 'baseline-complete'\n | 'generation-complete'\n | 'gate-decided'\n | 'finished'\n | 'errored'\n labels?: Record<string, string>\n baseline?: EvalRunGeneration\n generations?: EvalRunGeneration[]\n gateDecision?: 'ship' | 'hold' | 'need_more_work' | 'model_ceiling' | 'arch_ceiling'\n holdoutLift?: number\n totalCostUsd: number\n totalDurationMs: number\n errorMessage?: string\n}\n\nexport interface EvalRunsExportConfig {\n /** Bearer key — tenant is resolved server-side from it. Reads TANGLE_API_KEY. */\n apiKey?: string\n /** Intelligence base. Reads INTELLIGENCE_BASE env, else prod. */\n base?: string\n /** Idempotency-Key header (e.g. the runId) — safe retries + upsert. */\n idempotencyKey?: string\n}\n\nexport interface EvalRunsExportResult {\n ok: boolean\n status: number\n accepted: number\n rejected: Array<{ index: number; reason: string }>\n}\n\nconst DEFAULT_INTELLIGENCE_BASE = 'https://intelligence.tangle.tools'\n\n/**\n * Ship self-improvement eval-run events to Tangle Intelligence. Unlike the\n * best-effort span exporter, this RESOLVES with the ingest verdict (accepted /\n * rejected per event) so a consumer's loop can assert its provenance landed.\n * Throws only on a missing key or network failure.\n */\nexport async function exportEvalRuns(\n events: EvalRunEvent[],\n config?: EvalRunsExportConfig,\n): Promise<EvalRunsExportResult> {\n if (events.length === 0) return { ok: true, status: 0, accepted: 0, rejected: [] }\n const apiKey =\n config?.apiKey ?? (typeof process !== 'undefined' ? process.env.TANGLE_API_KEY : undefined)\n if (!apiKey)\n throw new Error('exportEvalRuns: apiKey required (pass config.apiKey or set TANGLE_API_KEY)')\n const base =\n config?.base ??\n (typeof process !== 'undefined' ? process.env.INTELLIGENCE_BASE : undefined) ??\n DEFAULT_INTELLIGENCE_BASE\n const url = `${base.replace(/\\/+$/, '')}/v1/ingest/eval-runs`\n const res = await fetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${apiKey}`,\n 'X-Tangle-Wire-Version': INTELLIGENCE_WIRE_VERSION,\n ...(config?.idempotencyKey ? { 'Idempotency-Key': config.idempotencyKey } : {}),\n },\n body: JSON.stringify({ wireVersion: INTELLIGENCE_WIRE_VERSION, events }),\n })\n let parsed: { accepted?: number; rejected?: Array<{ index: number; reason: string }> } = {}\n try {\n parsed = (await res.json()) as typeof parsed\n } catch {\n // non-JSON body (e.g. 5xx HTML) — leave parsed empty\n }\n return {\n ok: res.ok,\n status: res.status,\n accepted: parsed.accepted ?? (res.ok ? events.length : 0),\n rejected: parsed.rejected ?? [],\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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
|