@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.
Files changed (48) hide show
  1. package/README.md +54 -325
  2. package/dist/agent.js +5 -5
  3. package/dist/analyst-loop.js +2 -2
  4. package/dist/{chunk-VMNEQHJR.js → chunk-3X53HYRW.js} +17 -2
  5. package/dist/chunk-3X53HYRW.js.map +1 -0
  6. package/dist/{chunk-T2HVQVB4.js → chunk-4J6RBI3K.js} +15 -1
  7. package/dist/chunk-4J6RBI3K.js.map +1 -0
  8. package/dist/{chunk-IODKUOBA.js → chunk-C2FZ6GR6.js} +2 -2
  9. package/dist/{chunk-63MHOCIE.js → chunk-DH72UOYR.js} +4 -4
  10. package/dist/{chunk-Z3RRRPRB.js → chunk-H7IBHAFT.js} +23 -14
  11. package/dist/chunk-H7IBHAFT.js.map +1 -0
  12. package/dist/{chunk-PVPFDTO3.js → chunk-LLJRUTE7.js} +2 -2
  13. package/dist/{chunk-PBE35ULD.js → chunk-M3Y362RW.js} +2 -2
  14. package/dist/{chunk-AG335EXG.js → chunk-QSO2TVDS.js} +668 -74
  15. package/dist/chunk-QSO2TVDS.js.map +1 -0
  16. package/dist/{chunk-KRULXIWS.js → chunk-TPII5AU7.js} +55 -7
  17. package/dist/chunk-TPII5AU7.js.map +1 -0
  18. package/dist/{coordination-DCmljYDf.d.ts → coordination-BI9tpcmF.d.ts} +12 -3
  19. package/dist/environment-provider.d.ts +1 -1
  20. package/dist/environment-provider.js +1 -1
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.js +8 -8
  23. package/dist/intelligence.d.ts +30 -2
  24. package/dist/intelligence.js +33 -23
  25. package/dist/intelligence.js.map +1 -1
  26. package/dist/{loop-runner-bin-C4X0FZ2Z.d.ts → loop-runner-bin-CBkLsyFg.d.ts} +2 -2
  27. package/dist/loop-runner-bin.d.ts +4 -4
  28. package/dist/loop-runner-bin.js +7 -7
  29. package/dist/loops.d.ts +265 -14
  30. package/dist/loops.js +28 -6
  31. package/dist/mcp/bin.js +5 -5
  32. package/dist/mcp/index.d.ts +6 -6
  33. package/dist/mcp/index.js +7 -7
  34. package/dist/{router-client-Ak2IGuXq.d.ts → router-client-D6Ocf4jG.d.ts} +19 -0
  35. package/dist/{types-C1sozrte.d.ts → types-By9LXllv.d.ts} +9 -2
  36. package/dist/{worktree-fanout-CXGzHET4.d.ts → worktree-fanout-D9Z2dMS9.d.ts} +2 -2
  37. package/dist/{worktree-harness-Bmho9SH0.d.ts → worktree-harness-Dx8XULW3.d.ts} +1 -1
  38. package/package.json +1 -1
  39. package/skills/build-with-agent-runtime/SKILL.md +53 -23
  40. package/dist/chunk-AG335EXG.js.map +0 -1
  41. package/dist/chunk-KRULXIWS.js.map +0 -1
  42. package/dist/chunk-T2HVQVB4.js.map +0 -1
  43. package/dist/chunk-VMNEQHJR.js.map +0 -1
  44. package/dist/chunk-Z3RRRPRB.js.map +0 -1
  45. /package/dist/{chunk-IODKUOBA.js.map → chunk-C2FZ6GR6.js.map} +0 -0
  46. /package/dist/{chunk-63MHOCIE.js.map → chunk-DH72UOYR.js.map} +0 -0
  47. /package/dist/{chunk-PVPFDTO3.js.map → chunk-LLJRUTE7.js.map} +0 -0
  48. /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 shared task-lifecycle skeleton for agents. It runs an agent (a chat turn, a one-shot task, or a multi-attempt loop), captures every run as a trace, and feeds those traces into eval-gated self-improvement.
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
- It owns the lifecycle, the loop kernel, and the **optimization suite** `Environment` + `Strategy` +
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
- ## The model
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
- 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
13
+ ```bash
14
+ pnpm tsx examples/driver-loop/driver-loop.ts
15
+ ```
24
16
 
25
- Three entry points, by what you're doing:
17
+ ## What you do with it
26
18
 
27
19
  | You want to… | Call |
28
20
  |---|---|
29
- | Run one **product chat turn** (gtm/legal/tax/creative run this in prod) | `handleChatTurn(...)` |
30
- | Have a **supervisor drive a team of agents** to a goal — any harness, any number of sandboxes | `supervise(profile, task, { budget, backend? })` |
31
- | **Self-improve** an agent, certified on a held-out gate | `improve(profile, findings, { surface, gate, … })` |
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
- Every product agent is a `handleChatTurn` call inside a route what the gtm, creative, legal, and tax products run in production:
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
- export async function POST({ request, env, ctx }: { request: Request; env: Env; ctx: ExecutionContext }) {
41
- const { workspaceId, threadId, userMessage } = await request.json()
42
- const box = await ensureWorkspaceSandbox(workspaceId)
43
-
44
- const result = handleChatTurn({
45
- identity: { tenantId: workspaceId, sessionId: threadId, userId: 'demo', turnIndex: 0 },
46
- hooks: {
47
- produce: () => ({
48
- stream: box.streamPrompt(userMessage),
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
- That is the common case for a single product agent. The other two entry points are below.
43
+ ### Supervise a team of agents
61
44
 
62
- ### Run a supervisor (one call)
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 CI green.',
72
- { budget, router, backend }, // `backend` = where the workers run (one data value: router-tools | sandbox+harness | bridge)
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
- See `examples/supervise/` for the full one-call entry; `examples/supervisor-loop/` for the per-backend seams.
77
-
78
- ### Self-improve an agent
57
+ ### Improve an agent
79
58
 
80
- `improve` is the one pluggable RSI verb: it optimizes a `surface` of the profile (prompt / skills / code) with the generator defaulted from the surface (GEPA for prompts, skillOpt for skills, or bring your own), certified on a frozen holdout.
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', // certified on a held-out split, never the training set
88
- scenarios, judge, agent, // how to MEASURE the profile under a candidate surface
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
- agent-eval selfImprove (the optimization entry point), runEvalCampaign,
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
- agent-knowledge proposeKnowledgeWrites, applyKnowledgeWriteBlocks. The analyst loop produces
330
- these; the runtime and createKbGate consume them.
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
- sandbox AgentProfile, Sandbox.create, streamPrompt, exportTraceBundle. The harness
333
- execution surface every loop runs on.
334
- ```
335
-
336
- ## Subpath exports
78
+ ## Examples
337
79
 
338
- Six subpaths — the public surface:
80
+ Runnable, grouped by what they show copy the one nearest your task:
339
81
 
340
- | Import | Owns |
82
+ | Do this | Example |
341
83
  |---|---|
342
- | `@tangle-network/agent-runtime` | chat turns, delegated loop-runner, OTEL export, errors, model resolution |
343
- | `.../agent` | `defineAgent` plus surface and outcome adapters |
344
- | `.../loops` | **the optimization suite** (`Environment`, `defineStrategy`, `runBenchmark`, `runStrategyEvolution`, `authorStrategy`, `promotionGate`) + the recursive atom (`Supervisor`/`Scope`, `createExecutor`), the `runLoop` kernel, the `Driver` type, `loopDispatch` |
345
- | `.../profiles` | `coderTaskToPrompt` (the coder task formatter), the `uiAuditorProfile` presets + the UI-audit workspace I/O helpers |
346
- | `.../intelligence` | `withTangleIntelligence`, `createIntelligenceClient`Observe + the provable-OFF billing boundary |
347
- | `.../mcp` | `createMcpServer`, `createDefaultCoderDelegate`, `createKbGate`, the `agent-runtime-mcp` bin |
348
-
349
- Bins: `agent-runtime-mcp` (delegation MCP server), `agent-runtime-loop` (schedulable delegated loop-runner).
350
-
351
- ## Teaching an agent to build on this
352
-
353
- Two agent-consumable skills live in the [`loops`](https://github.com/drewstone/loops) repo:
354
- **`skills/loop-builder`** (domain → `Environment` → loop → gate → operator surface, with the
355
- measured foot-gun list) and **`skills/loop-author`** (authoring a strategy body from losses;
356
- read the contract with `loops contract`). The runnable on-ramp is [`examples/`](./examples/README.md)
357
- a learning progression from the production chat turn through the strategy suite to the recursive
358
- supervisor. For the broader pipeline (trace sink, analyst loop, scorecard, CI), see the
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-AG335EXG.js";
1
+ import "./chunk-QSO2TVDS.js";
2
2
  import {
3
3
  createSandboxForSpec
4
- } from "./chunk-KRULXIWS.js";
5
- import "./chunk-VMNEQHJR.js";
6
- import "./chunk-Z3RRRPRB.js";
4
+ } from "./chunk-TPII5AU7.js";
5
+ import "./chunk-3X53HYRW.js";
6
+ import "./chunk-H7IBHAFT.js";
7
7
  import {
8
8
  mapSandboxEvent
9
- } from "./chunk-T2HVQVB4.js";
9
+ } from "./chunk-4J6RBI3K.js";
10
10
  import "./chunk-5JAUQZQA.js";
11
11
  import "./chunk-NBV35BR6.js";
12
12
  import "./chunk-WIR4HOOJ.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  iterationsToTraceStore,
3
3
  runAnalystLoop
4
- } from "./chunk-IODKUOBA.js";
5
- import "./chunk-T2HVQVB4.js";
4
+ } from "./chunk-C2FZ6GR6.js";
5
+ import "./chunk-4J6RBI3K.js";
6
6
  import "./chunk-NBV35BR6.js";
7
7
  import "./chunk-DGUM43GV.js";
8
8
  export {
@@ -1,5 +1,5 @@
1
1
  // src/otel-export.ts
2
- var SCOPE = { name: "@tangle-network/agent-runtime", version: "0.33.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-VMNEQHJR.js.map
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-T2HVQVB4.js.map
92
+ //# sourceMappingURL=chunk-4J6RBI3K.js.map