@tangle-network/traces 0.11.0 → 0.11.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # traces
2
2
 
3
- > Point `traces` at the session logs your coding agent already writes to disk: Claude Code, Codex, OpenCode, Gemini, and more. Get failure-mode and efficiency findings with zero instrumentation. A CLI *and* an SDK.
3
+ > Point `traces` at any agent trace — OTLP spans your own system emits, or the session logs Claude Code, Codex, OpenCode and Gemini already write to disk. Get failure-mode and efficiency findings, plus a straight answer about what the trace cannot tell you. A CLI *and* an SDK.
4
4
 
5
5
  ![traces analyzing a real Claude Code session](https://raw.githubusercontent.com/tangle-network/traces/main/docs/demo.gif)
6
6
 
@@ -8,12 +8,22 @@
8
8
  [![license](https://img.shields.io/npm/l/@tangle-network/traces.svg)](./LICENSE)
9
9
  [![node](https://img.shields.io/node/v/@tangle-network/traces.svg)](https://nodejs.org)
10
10
 
11
- It reads the transcripts your harness leaves on disk, reconstructs the run as spans, and reports where the agent got stuck, burned tokens, or stopped checking its own work. The deterministic pass runs locally, with no API key and no cost.
11
+ It reads a run as spans and reports where the agent got stuck, burned tokens, or stopped checking its own work. The deterministic pass runs locally, with no API key and no cost.
12
+
13
+ There are two ways in, and they are not equal:
14
+
15
+ | | Use when | How |
16
+ |---|---|---|
17
+ | **Emit the contract** | You control the system | Emit [`@tangle-network/agent-trace-contract`](https://github.com/tangle-network/agent-sdk/tree/main/packages/agent-trace-contract) spans, then `traces analyze --otlp <file\|dir>` |
18
+ | **Adapters** | You do *not* control the format | `traces analyze --harness claude-code` reads a proprietary session store and translates it |
19
+
20
+ Emitting the contract is the supported way to integrate a new system. The adapters are the legacy edge — they exist because the coding agents write formats nobody standardised, and each one is a translation somebody has to maintain. If you can choose, choose `--otlp`.
12
21
 
13
22
  ## Contents
14
23
 
15
24
  - [Install](#install)
16
25
  - [Quick start](#quick-start)
26
+ - [Integrate your own system](#integrate-your-own-system)
17
27
  - [What it finds](#what-it-finds)
18
28
  - [Supported harnesses](#supported-harnesses)
19
29
  - [CLI reference](#cli-reference)
@@ -45,7 +55,9 @@ Requires Node ≥ 22.
45
55
  ## Quick start
46
56
 
47
57
  ```bash
48
- traces analyze --harness claude-code --last 1
58
+ traces validate spans.otlp.jsonl # what can this trace answer?
59
+ traces analyze --otlp spans.otlp.jsonl # analyse it, no adapter involved
60
+ traces analyze --harness claude-code --last 1 # or read a coding agent's own log
49
61
  traces improve --harness claude-code --last 5 --dir .traces/improvement
50
62
  traces watch --all
51
63
  traces stream --all --mode findings
@@ -66,6 +78,110 @@ See [Trace analysts](#trace-analysts).
66
78
  It writes one typed result, one report, flattened evidence rows, and the canonical OTLP trace.
67
79
  Each finding already contains the claim, evidence, recommended action, confidence, and validation plan.
68
80
 
81
+ ## Integrate your own system
82
+
83
+ No adapter, no plugin. Emit the span contract and point `--otlp` at the file.
84
+
85
+ ```bash
86
+ npm i @tangle-network/agent-trace-contract # zero runtime dependencies
87
+ ```
88
+
89
+ You install it to PRODUCE spans. This package already depends on it to read
90
+ them, so `npx @tangle-network/traces@latest` arrives with the contract-backed
91
+ analyses working: trace conformance, `traces validate`, `--otlp` inbound
92
+ reading, round-over-round convergence and the steering chain, and OpenInference
93
+ / intelligence-spans conversion.
94
+
95
+ ```ts
96
+ import { llmSpan, loopSpan, steeredBy, toolSpan } from '@tangle-network/agent-trace-contract'
97
+
98
+ // One round of a loop, the model call and tool call inside it, then the next
99
+ // round — linked to the verdict that CAUSED it, not parented to it.
100
+ const round1 = loopSpan({ traceId, spanId: 'round-1', parentSpanId: null, name: 'round 1',
101
+ loopId: 'fix-loop', iteration: 1, startTime, endTime })
102
+ const call = llmSpan({ traceId, spanId: 'llm-1', parentSpanId: 'round-1', name: 'coder turn',
103
+ model: 'glm-5.2', inputTokens: 1200, outputTokens: 340, costUsd: 0.0042, startTime, endTime })
104
+ const tool = toolSpan({ traceId, spanId: 'tool-1', parentSpanId: 'round-1', name: 'bash',
105
+ toolName: 'bash', startTime, endTime })
106
+ const round2 = loopSpan({ traceId, spanId: 'round-2', parentSpanId: null, name: 'round 2',
107
+ loopId: 'fix-loop', iteration: 2, resumed: true, startTime, endTime,
108
+ links: [steeredBy('llm-1', traceId)] })
109
+ ```
110
+
111
+ Write one span per line to a `.jsonl` file, then:
112
+
113
+ ```bash
114
+ traces validate spans.otlp.jsonl # exit 1 only when the file is not a trace
115
+ traces analyze --otlp spans.otlp.jsonl # a directory reads the OTLP files under it
116
+ ```
117
+
118
+ A directory is a run directory, not a span directory: it holds the span export
119
+ next to raw event, stream and SDK logs that are also `*.jsonl`. Only the OTLP
120
+ files are read — every other JSONL is listed in the report with what it actually
121
+ holds, rather than counted as several hundred broken spans. Put the exports in an
122
+ `otlp/` subdirectory and that decides it outright.
123
+
124
+ On a writing command (`convert`, `export`, `evidence`, `upload`), `--otlp` is the
125
+ DEPRECATED spelling of `--otlp-out`. It still works, with a warning, and is
126
+ removed in 0.12.
127
+
128
+ `parent_span_id` is CONTAINMENT — this span happened inside that one. `links` is
129
+ CAUSALITY — that verdict caused this retry. Encoding causality as a parent claims
130
+ a nesting that never existed, so the two edges stay separate and both survive a
131
+ round-trip through `traces`.
132
+
133
+ ### What a trace cannot tell you, said out loud
134
+
135
+ `validate` reports the conformance of any OTLP file, conforming or not — it never
136
+ throws on a foreign trace, it reports it:
137
+
138
+ - **findings** by severity, each naming the *consequence* of the defect and the capabilities it blocks
139
+ - **the capability table** — `token-accounting`, `cost-attribution`, `tool-usage`, `loop-convergence`, `tree-comparison`, `steering-chain`, `latency-analysis` — available or not, with the trace's own reason
140
+ - **span structure** — how much of the tree actually attaches, and how many rows could not be read at all
141
+
142
+ Exit code is 1 on exactly one condition: not one entry could be read as a span,
143
+ so the file is not a trace. Severity says what a finding claims about the INPUT,
144
+ not how loud it is — `error` is "nothing readable", `warn` is "readable but
145
+ degraded", `info` is "nothing measurable lost" — so a `warn` exits 0 and the
146
+ capability table, not the exit code, says what the degradation cost.
147
+
148
+ #### A re-export never validates cleaner than its source
149
+
150
+ `--otlp-out` writes the spans that were analysed. Anywhere this package
151
+ normalized a field to analyse it — an `end_time` that precedes its `start_time`,
152
+ a status code the contract does not recognise — the export writes what the
153
+ PRODUCER wrote and keeps the normalized value in an attribute beside it, so
154
+ `negative-duration` and `invalid-status` are still findings against the copy.
155
+
156
+ A row that never became a span is the one thing that cannot be re-emitted: the
157
+ artifact is also the file the analysts read, so a non-span line in it is garbage
158
+ fed to the analysis and a repaired one is invented work. The export refuses the
159
+ equivalence instead — it carries `traces.source.unreadable_rows` and the kinds
160
+ behind it, accumulating across every hop, and `validate` on a re-export leads
161
+ with how much of the original is missing from it. Round-tripping a defective
162
+ trace through `traces` cannot produce a clean bill of health.
163
+
164
+ `analyze` prints the same section, plus an **analyses skipped, and why** list —
165
+ and, crucially, carries that verdict INTO the report. A section whose inputs are
166
+ incomplete says so in its own heading and again directly above its table, so a
167
+ number and the reason it is wrong can never end up forty lines apart. A capability
168
+ the trace supports that this package has no analysis for yet is listed as
169
+ `⚠️ available, unused` rather than quietly dropped.
170
+
171
+ Two of those analyses are why the loop shape exists at all:
172
+
173
+ - **round-over-round convergence** — per `agent.loop.id`, the per-round outcome
174
+ and score series, and whether it improved, plateaued, regressed, or peaked and
175
+ fell back. The verdict is read from the round's own `agent.outcome` or from the
176
+ shallowest EVALUATOR span under it.
177
+ - **steering chain** — follows `links` (`agent.link.kind`) to report which verdict
178
+ caused which subsequent round. A link whose target is missing is kept and marked,
179
+ because a shorter chain than the producer recorded is the opposite of the truth.
180
+
181
+ Nothing is repaired on the way in. A span with an unreadable timestamp is
182
+ reported and excluded, because a synthesized value would put invented work into a
183
+ total.
184
+
69
185
  ## What it finds
70
186
 
71
187
  The deterministic pass (free, no key) surfaces:
@@ -82,6 +198,10 @@ The deterministic pass (free, no key) surfaces:
82
198
 
83
199
  ## Supported harnesses
84
200
 
201
+ Adapters are the **legacy edge**: they exist for coding agents whose on-disk format we do not
202
+ control, and each is a translation from a proprietary session store onto the span model. They are
203
+ not the way to integrate a system you own — for that, [emit the contract](#integrate-your-own-system).
204
+
85
205
  "Verified" = tested against real sessions; "fixture" = tested against schema-accurate fixtures (no real sessions available).
86
206
 
87
207
  | Harness (aliases) | Reads from | Status |
@@ -117,7 +237,11 @@ traces analyze --harness claude-code --session <path> --latest-turn # latest tas
117
237
  traces investigate --all --last 10 --out report.md # explicit investigation alias
118
238
  traces improve --all --last 10 --dir .traces/improvement
119
239
  traces analyze --all --since 2026-06-18 --out report.md
120
- traces convert --harness claude-code --last 1 --otlp spans.jsonl # OTLP only
240
+ traces validate spans.otlp.jsonl # conformance; exit 1 only when it is not a trace
241
+ traces validate results/sessions --out conformance.md # a whole directory of exports
242
+ traces analyze --otlp spans.otlp.jsonl # analyse foreign OTLP, no adapter
243
+ traces analyze --otlp results/sessions --out report.md
244
+ traces convert --harness claude-code --last 1 --otlp-out spans.jsonl # OTLP only
121
245
  traces index --all --since 24h --out session-index.json
122
246
  traces inspect session-index.json --out inspection-report.md
123
247
  traces evidence --harness codex --last 20 --out policy-evidence.jsonl
@@ -134,8 +258,13 @@ traces stream --all --mode agent # findings + deterministic re
134
258
  traces stream spans.openinference.jsonl --format openinference --no-spans
135
259
  traces upload --since 1h --dry-run # redact + dedup + preview, no network
136
260
  traces upload --since 24h # upload last day to the Intelligence Platform
261
+ traces replay-verify --steps steps.json --image <docker-image> --at 37 --cwd /home --out ./replay-out \
262
+ --fix-command "<corrected step>" # executed proof: replay prefix, reproduce failure, show fix
137
263
  ```
138
264
 
265
+ `replay-verify` replays a CodeTraceBench-style trajectory prefix in a real sandbox and executes step k twice — recorded (does the failure reproduce?) and corrected (does it vanish?).
266
+ See [Replay verification](./docs/replay-verify.md) for setup, semantics, and honest limits (SWE-style trajectories with a docker image only; commands run as the non-root sandbox user).
267
+
139
268
  | Flag | Meaning |
140
269
  |---|---|
141
270
  | `--harness <id>` | Harness or alias (default: `claude-code`) |
@@ -150,7 +279,8 @@ traces upload --since 24h # upload last day to the Inte
150
279
  | `--since <t>` | `upload`: window, `30m`/`2h`/`7d` or ISO (default 24h); `analyze`: ISO cutoff |
151
280
  | `--out <path>` | Write the report to a file |
152
281
  | `--dir <path>` | `improve`: write the full artifact pack to this directory |
153
- | `--otlp <path>` | OTLP artifact path (also evidence provenance / dry-run upload preview) |
282
+ | `--otlp <file\|dir>` | **READ** OTLP-JSONL from any system, skipping the adapters; a directory reads the OTLP files under it (only `otlp/` when the producer made one) and names the JSONL that is not OTLP. `validate`, `analyze`, `investigate`, `improve`, `stream` |
283
+ | `--otlp-out <path>` | **WRITE** the OTLP artifact here (also evidence provenance / dry-run upload preview) |
154
284
  | `--format <kind>` | File `analyze`, `export`, or `stream`: `auto`, `policy-evidence`, `sandbox-events`, `openinference`, `intelligence-spans`, or `chat-trajectory` |
155
285
  | `--llm` / `--budget <usd>` | Enable agentic analysts (needs `TANGLE_API_KEY` + Python with `agent-eval-rpc[dspy]`) / cap their spend |
156
286
  | `--config <path>` | `analyze` / `investigate` / `improve` / `stream`: load BYO analysts, live analysts, and external analyzers |
@@ -323,7 +453,7 @@ It is intentionally read-only: it points to repeated-call loops, high tool-error
323
453
  `traces` does **not** emit benchmark campaign cells. It emits normalized coding-agent session evidence that another system can mine.
324
454
 
325
455
  ```bash
326
- traces evidence --all --since 24h --out policy-evidence.jsonl --otlp spans.otlp.jsonl
456
+ traces evidence --all --since 24h --out policy-evidence.jsonl --otlp-out spans.otlp.jsonl
327
457
  ```
328
458
 
329
459
  `--last` follows recent file activity and may select a child session in multi-agent work.
@@ -465,15 +595,15 @@ const profile = { name: 'trace-reviewer', resources: { skills } }
465
595
 
466
596
  ## Release automation
467
597
 
468
- Merging to `main` publishes a patch release automatically:
598
+ Releases use a reviewed version commit because `main` is protected:
469
599
 
470
- 1. The Publish workflow bumps `package.json` from `X.Y.Z` to `X.Y.(Z+1)`.
471
- 2. It commits `chore(release): vX.Y.(Z+1) [skip release]` back to `main`.
472
- 3. It pushes the matching `vX.Y.(Z+1)` tag.
473
- 4. The same workflow verifies the tag, builds, publishes to npm, and creates a GitHub release.
600
+ 1. Update `package.json` and `pnpm-lock.yaml` to the intended version on a branch.
601
+ 2. Merge that change through a pull request.
602
+ 3. Tag the exact merge commit as `vX.Y.Z` and push the tag.
603
+ 4. The Publish workflow verifies that the tag is merged into `main` and matches `package.json`, then builds, publishes to npm, and creates a GitHub release.
474
604
  5. `pnpm check:package` proves the npm tarball contains the `traces` binary before release.
475
605
 
476
- Minor releases are manual. Run the Publish workflow from GitHub Actions and choose `minor`; it publishes `X.(Y+1).0`. Use manual `patch` only when you need a patch release without merging a new code change.
606
+ To retry a failed publish without changing source or version, manually run the Publish workflow with the existing tag.
477
607
 
478
608
  ## Library (SDK)
479
609
 
@@ -481,6 +611,16 @@ The CLI is a thin consumer of these exports.
481
611
 
482
612
  | Export | Signature | Use |
483
613
  |---|---|---|
614
+ | `readOtlpInput` | `(file\|dir) → { files, skipped, rows, spans, validation, issues }` | ingest OTLP from **any** system — no adapter; never throws on a foreign trace |
615
+ | `validateTraceSpans` | `(spans) → { ok, findings, capabilities }` | the contract's conformance check, re-exported |
616
+ | `conformanceOfSpans` | `(spans) → TraceValidation` | grade spans this package produced, on the artifact a consumer receives |
617
+ | `renderValidation` / `renderConformance` | `(validation, opts) → string` | the `validate` report / the section embedded in an analysis report |
618
+ | `skippedAnalyses` | `(validation) → SkippedAnalysis[]` | which analyses a missing capability empties, and why |
619
+ | `unavailableCapabilities` / `incompleteInputsNote` | `(validation) → Map` / `(map, name) → string` | the gate: what the trace cannot support, and the marker each affected section carries |
620
+ | `summarizeSpanStructure` | `(spans) → SpanStructure` | how much of the tree attaches: rootless, orphans, links |
621
+ | `analyzeLoopConvergence` | `(spans) → LoopConvergenceReport` | per loop, the per-round outcome/score series and whether it improved, plateaued, regressed or fell back |
622
+ | `analyzeSteeringChain` | `(spans) → SteeringChainReport` | which verdict span caused which subsequent round, following `links` |
623
+ | `llmSpan` / `toolSpan` / `loopSpan` / `branchSpan` / `steeredBy` | contract builders, re-exported | emit conforming spans from your own system |
484
624
  | `analyzeSpans` | `(spans, { registry?, ai?, budgetUsd? }) → AnalyzeResult` | run built-in analysts, or **your own** via `registry` |
485
625
  | `runTraceInvestigation` | `(TraceInvestigationOptions) → TraceInvestigationResult` | typed findings with actions/checks, execution facts, external analyzer output, and report |
486
626
  | `runTraceImprovement` | `(TraceImprovementOptions) → TraceImprovementResult` | writes the full findings, evidence, report, and trace artifact pack |
@@ -509,7 +649,15 @@ The CLI is a thin consumer of these exports.
509
649
  | `ExternalAnalyzer` / `Redactor` | `haloAnalyzer` / `hodoscopeAnalyzer` / `commandAnalyzer` / `commandRedactor` | drive engines/models you install |
510
650
 
511
651
  ```ts
512
- import { watchSessions, streamSessions, analyzeSpans, AnalystRegistry, makeFinding } from '@tangle-network/traces'
652
+ import {
653
+ AnalystRegistry, analyzeSpans, makeFinding, readOtlpInput, renderValidation,
654
+ streamSessions, watchSessions,
655
+ } from '@tangle-network/traces'
656
+
657
+ // Read OTLP any system emitted, and find out what it can answer before analysing:
658
+ const input = await readOtlpInput('spans.otlp.jsonl') // or a directory
659
+ console.log(renderValidation(input.validation, { subject: 'spans.otlp.jsonl', spans: input.spans }))
660
+ if (input.validation.ok) await analyzeSpans(input.spans)
513
661
 
514
662
  // Observe live sessions, feed findings anywhere (read-only, cancellable):
515
663
  const c = new AbortController()