@tangle-network/traces 0.23.1 → 1.0.1

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 (75) hide show
  1. package/README.md +61 -1078
  2. package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
  3. package/dist/ask-2KF4KSPB.js +39 -0
  4. package/dist/check-YF6ZHPGG.js +322 -0
  5. package/dist/chunk-2XXABIRL.js +25 -0
  6. package/dist/chunk-4VKPN432.js +138 -0
  7. package/dist/chunk-6GOG5KIK.js +53 -0
  8. package/dist/chunk-7USMM4MA.js +5695 -0
  9. package/dist/chunk-CG5QHQO4.js +492 -0
  10. package/dist/chunk-CQ6UYZG6.js +1217 -0
  11. package/dist/chunk-DU534HAX.js +263 -0
  12. package/dist/chunk-FC4V25CG.js +503 -0
  13. package/dist/chunk-GLUDGUGJ.js +232 -0
  14. package/dist/chunk-GNRXKQEQ.js +120 -0
  15. package/dist/chunk-K2KH2HHD.js +228 -0
  16. package/dist/chunk-M3W6TZHQ.js +381 -0
  17. package/dist/chunk-O52XK7OB.js +19 -0
  18. package/dist/chunk-PYH3PL3N.js +62 -0
  19. package/dist/chunk-QLVP37YB.js +782 -0
  20. package/dist/chunk-QUHUMZ4W.js +759 -0
  21. package/dist/chunk-REO2JQJY.js +535 -0
  22. package/dist/chunk-RZL5GAWH.js +60 -0
  23. package/dist/chunk-TOCNGML5.js +273 -0
  24. package/dist/chunk-UQSAGXPH.js +208 -0
  25. package/dist/chunk-VFOLC4OX.js +407 -0
  26. package/dist/chunk-WF7PTDCZ.js +3921 -0
  27. package/dist/chunk-YAX72TST.js +259 -0
  28. package/dist/cli.js +2086 -2050
  29. package/dist/diff-J4RAIOXO.js +16 -0
  30. package/dist/improvement-LCCVGGUS.js +25 -0
  31. package/dist/index.d.ts +1235 -4357
  32. package/dist/index.js +53 -1204
  33. package/dist/live-XXL7VZWB.js +14 -0
  34. package/dist/mcp-EYIDKBEK.js +85 -0
  35. package/dist/report-E3G2T5TJ.js +338 -0
  36. package/dist/run-watch-RAOOAXOF.js +649 -0
  37. package/dist/supervisor-report-RMPLLBGZ.js +29 -0
  38. package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
  39. package/dist/upload-3R7GRSKS.js +20 -0
  40. package/dist/verify-safe-C5ZXZOZ2.js +8 -0
  41. package/docs/analyze.md +76 -0
  42. package/docs/ask.md +88 -0
  43. package/docs/bundle.md +40 -0
  44. package/docs/check.md +77 -0
  45. package/docs/convert.md +44 -0
  46. package/docs/develop.md +32 -0
  47. package/docs/diff.md +38 -0
  48. package/docs/facts.md +114 -0
  49. package/docs/mcp.md +36 -0
  50. package/docs/migrate-1.0.md +33 -0
  51. package/docs/parsers.md +66 -0
  52. package/docs/release-1.0.0.md +147 -0
  53. package/docs/release-1.0.1.md +4 -0
  54. package/docs/sdk.md +77 -0
  55. package/docs/select.md +70 -0
  56. package/docs/skills.md +46 -0
  57. package/docs/upload.md +47 -0
  58. package/docs/watch-jsonl.v1.example.jsonl +5 -0
  59. package/docs/watch.md +66 -0
  60. package/examples/README.md +9 -0
  61. package/examples/custom-analyst.ts +65 -74
  62. package/install.sh +22 -2
  63. package/package.json +10 -9
  64. package/skills/inspect-agent-traces/SKILL.md +47 -110
  65. package/dist/chunk-7DVZCCZI.js +0 -20983
  66. package/docs/replay-verify.md +0 -123
  67. package/docs/stream-findings.svg +0 -39
  68. package/docs/trace-analysts.md +0 -643
  69. package/examples/custom-backend.ts +0 -21
  70. package/examples/external-engines.ts +0 -62
  71. package/examples/improvement-config.mjs +0 -63
  72. package/examples/observe-and-alert.ts +0 -19
  73. package/examples/register-harness.ts +0 -26
  74. package/skills/build-trace-analyst/SKILL.md +0 -76
  75. package/skills/build-trace-analyst/agents/openai.yaml +0 -4
@@ -0,0 +1,30 @@
1
+ import {
2
+ COORDINATION_LOG_FILE,
3
+ RESULT_FILE,
4
+ SPAWN_JOURNAL_FILE,
5
+ ZERO_ROLL,
6
+ addSpend,
7
+ fileRunContextSupervisorRunReader,
8
+ inferRole,
9
+ isFileRunContextDir,
10
+ mergeRolls,
11
+ readCoordinationLog,
12
+ readFileRunContext,
13
+ readRunContextJournal,
14
+ readRunContextSnapshot
15
+ } from "./chunk-M3W6TZHQ.js";
16
+ export {
17
+ COORDINATION_LOG_FILE,
18
+ RESULT_FILE,
19
+ SPAWN_JOURNAL_FILE,
20
+ ZERO_ROLL,
21
+ addSpend,
22
+ fileRunContextSupervisorRunReader,
23
+ inferRole,
24
+ isFileRunContextDir,
25
+ mergeRolls,
26
+ readCoordinationLog,
27
+ readFileRunContext,
28
+ readRunContextJournal,
29
+ readRunContextSnapshot
30
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ PartialUploadError,
3
+ executeUpload,
4
+ planUpload,
5
+ planUploadFrom,
6
+ toTraceSpanEvents
7
+ } from "./chunk-TOCNGML5.js";
8
+ import "./chunk-6GOG5KIK.js";
9
+ import "./chunk-7USMM4MA.js";
10
+ import "./chunk-UQSAGXPH.js";
11
+ import "./chunk-CQ6UYZG6.js";
12
+ import "./chunk-RZL5GAWH.js";
13
+ import "./chunk-DU534HAX.js";
14
+ export {
15
+ PartialUploadError,
16
+ executeUpload,
17
+ planUpload,
18
+ planUploadFrom,
19
+ toTraceSpanEvents
20
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ formatVerdict,
3
+ verifySafe
4
+ } from "./chunk-4VKPN432.js";
5
+ export {
6
+ formatVerdict,
7
+ verifySafe
8
+ };
@@ -0,0 +1,76 @@
1
+ # Analyze a session
2
+
3
+ [traces](../README.md#docs) › Analyze a session
4
+
5
+ `traces analyze` says what went wrong in the selected sessions, ranked, for $0. It calls no model: `traces ask` is the one command that spends.
6
+
7
+ ```bash
8
+ traces analyze --current
9
+ traces analyze 019a3c7e --out analysis # report.md and findings.json
10
+ traces analyze --since 1d --cwd . # a table of the sessions with the most waste
11
+ ```
12
+
13
+ ## The text
14
+
15
+ At most 40 lines, in this order. A section with nothing to say is left out, and a fact the trace cannot support is never printed as 0.
16
+
17
+ 1. Header: short id, harness, repo, window, active time and cost (list price, Router rates).
18
+ 2. What was asked: the first human turn, and how many followed.
19
+ 3. The first failure and its blame.
20
+ 4. Failing commands (3 or more identical failures in a row) and rework (reruns, reverts).
21
+ 5. Tool-error clusters: the session's own failed calls, by tool and first line of error.
22
+ 6. Stalls of 10 minutes or more that no turn ended.
23
+ 7. Interrupts and rejections, each quoted. Measured against labelled turns, none of these is called a correction.
24
+ 8. Pull requests opened and merged.
25
+ 9. Findings, HIGH first, with span ids.
26
+ 10. How many questions the trace cannot answer, and where the list is.
27
+
28
+ The header and sections 3, 4, 6, 7 and 8 are the [facts](facts.md) sheet's numbers; analyze renders them and never counts them again. Several sessions put sections 2 to 8 in a table of the 15 with the most waste.
29
+
30
+ ## Findings
31
+
32
+ | Finding | From | Severity |
33
+ |---|---|---|
34
+ | `same-failing-command` | a failing streak in the facts sheet | HIGH |
35
+ | `verification-without-change` | one check run 3 or more times with no edit between | MED (HIGH from 5) |
36
+ | `high-tool-error-rate` | half or more of the session's own tool calls failed | MED (HIGH from 75%) |
37
+ | `terminal-failure` | the session's root span ended in an error | HIGH |
38
+ | `monotonic-input-growth`, `output-length-decay` | agent-eval's behavioral analyst | HIGH, MED |
39
+ | `single-tool-dependency` | a run of 10 or more calls of one non-shell tool | MED |
40
+ | `no-self-verification` | 10 or more calls, none a check | LOW (0 of 1 held on real sessions) |
41
+
42
+ The first three are the rules `traces watch` runs live, so a finding means the same in both.
43
+
44
+ ## For programs
45
+
46
+ `--format json` prints `facts`, `packet` (the finding packet ADC's Intelligence API reads), `measures`, `conformance` and `cannotAnswer` (every question the trace cannot answer, with the reason). `measures.operating` pools the sheets' `modelCalls`, `guardRefusals` (per 1,000 tool calls) and `limitStop` (with how many terminal failures were limit stops). A `--since` sweep reads one session at a time, so a week of a busy host fits in a few gigabytes.
47
+ `--out DIR` writes exactly `report.md` (every finding and every cannot-answer reason) and `findings.json` (the packet).
48
+ A Runtime run directory as the ref reports its supervision tree. Your own analysts run through the SDK: `analyzeSpans(spans, { registry })` ([sdk](sdk.md)).
49
+
50
+ ## `traces analyze --help`
51
+
52
+ ```text
53
+ Usage: traces analyze <ref…> | --current | --last N | --since T [flags]
54
+ What went wrong, ranked, for $0: failing commands, stalls, interrupts, findings, and what the trace cannot answer.
55
+ At most 40 lines; several sessions get a table of the 15 with the most waste. A Runtime run dir reports its tree.
56
+
57
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
58
+ --last N the N newest sessions by file mtime, never the caller's own
59
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
60
+ --until T close the --since window (exclusive); same forms
61
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
62
+ --harness ID read only this harness; with a file ref, force its reader
63
+ --latest-turn only the latest task turn of a resumed Codex or Claude Code session
64
+ --format text|json text (default, at most 40 lines) or json: facts, packet, measures, conformance, cannotAnswer
65
+ --out DIR write report.md and findings.json under DIR; stdout gets one summary line
66
+
67
+ jq paths (--format json):
68
+ .packet.findings[] | {severity, subject, claim} the findings, HIGH first
69
+ .cannotAnswer[] | {analysis, reason} what the trace cannot answer, and why
70
+ .facts.sessions[0].measurements.waste failing streaks, stalls, reverts
71
+
72
+ Examples:
73
+ traces analyze --current
74
+ traces analyze 019a3c7e --out analysis
75
+ traces analyze --since 1d --cwd .
76
+ ```
package/docs/ask.md ADDED
@@ -0,0 +1,88 @@
1
+ # Ask a question
2
+
3
+ [traces](../README.md#docs) › Ask a question
4
+
5
+ `traces ask` answers a question `traces facts` cannot, with checked `trace://` citations. It is the one command that calls a model.
6
+
7
+ ```bash
8
+ traces ask 019a3c7e --question "Which command failed first, and why?" --budget 1
9
+ traces ask --last 3 --questions questions.json --budget 2 --out answers
10
+ ```
11
+
12
+ `--budget` is required: the most the run may spend, shared by every question. Each question spends at most the smaller of `--budget` and $2, within 1,800 s, and 4 run at once.
13
+ A question is at most 705 characters, so the engine sees it whole; put detail in an entry's `instructions`.
14
+
15
+ ## Questions from a file
16
+
17
+ The file is a JSON array of question strings or entries `{ id?, question, instructions?, answerSchema? }`.
18
+ An `answerSchema` makes the answer one JSON value a scorer can compare; it accepts `type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`, `title` and `description`, and any other keyword is rejected before the run starts.
19
+
20
+ ```json
21
+ [
22
+ "Which pull requests did this session open?",
23
+ { "id": "merged", "question": "Which pull requests were merged?", "answerSchema": { "type": "array", "items": { "type": "integer" } } }
24
+ ]
25
+ ```
26
+
27
+ ## What the run guarantees
28
+
29
+ - Every question receives the [facts](facts.md) sheet before its first model call.
30
+ - Every `trace://<trace>/span/<span>` citation is resolved; an unresolvable one fails that question. Findings pass the same evidence gate as the built-in kinds, and refusals are counted by reason.
31
+ - A failed question never stops the others. Ctrl-C keeps the answers already bought.
32
+ - `answers.json` and `report.md` (under `--out`) are written before the exit code is decided. Some questions unanswered: exit 2 with `code: partial`.
33
+ - Each cost carries its provenance (`observed`, `estimated` or `uncaptured`); an uncaptured cost stays null.
34
+ - The ledger reserves each call's maximum before it runs, so a budget below one call is refused before any spend.
35
+
36
+ ## Setup and refusals
37
+
38
+ `ask` needs `TANGLE_API_KEY` (or `OPENAI_API_KEY` with `OPENAI_BASE_URL`) and the DSPy bridge: with `uv` installed it runs `uvx --from agent-eval-rpc[dspy]==<version>`, or `TRACES_PYTHON` names a Python that has it. A missing key or bridge exits 2 with `code: setup` before anything is read or spent.
39
+
40
+ | Refusal from the endpoint | What `ask` does |
41
+ |---|---|
42
+ | 408, 425, 429, 5xx, no connection | retries the call: 2 s doubling to 60 s, 10 attempts, inside the question's deadline |
43
+ | 401, 402, 403 | stops every question; exit 2, `code: auth` |
44
+ | model not allowed, not found or unpriced | stops every question; exit 2, `code: model` |
45
+ | anything else | fails that question alone |
46
+
47
+ ## SDK
48
+
49
+ ```ts
50
+ import { analysisEngineFromEnv, runTraceQuestions } from '@tangle-network/traces'
51
+
52
+ const result = await runTraceQuestions({
53
+ questions: [{ id: 'first-failure', question: 'Which command failed first?' }],
54
+ spans,
55
+ engine: analysisEngineFromEnv({ model: 'gpt-5.6-luna', maxCostUsd: 1 }),
56
+ budgetUsd: 2,
57
+ })
58
+ ```
59
+
60
+ ## `traces ask --help`
61
+
62
+ ```text
63
+ Usage: traces ask <ref…> | --current | --last N | --since T --question TEXT [flags]
64
+ A question facts cannot answer, with checked trace:// citations: the only command that calls a model.
65
+ Each question spends at most min(--budget, $2) within 1800 s; 4 run at once. Some unanswered: exit 2.
66
+ Needs TANGLE_API_KEY (or OPENAI_API_KEY) and uv, or TRACES_PYTHON naming a Python with agent-eval-rpc[dspy].
67
+
68
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
69
+ --last N the N newest sessions by file mtime, never the caller's own
70
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
71
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
72
+ --harness ID read only this harness; with a file ref, force its reader
73
+ --latest-turn only the latest task turn of a resumed Codex or Claude Code session
74
+ --question TEXT a question (repeatable, at most 705 characters)
75
+ --questions FILE a JSON array of questions: strings or {id?, question, answerSchema?}
76
+ --budget USD required: the most the run may spend, shared by every question
77
+ --model ID the analyst model (default gpt-5.6-luna)
78
+ --format text|json text (default): the report; json: each answer, its citations, failure and cost
79
+ --out DIR write answers.json and report.md under DIR; stdout gets one summary line
80
+
81
+ jq paths (--format json):
82
+ .questions[] | {id, status, answer, failure} each answer, or why it has none
83
+
84
+ Examples:
85
+ traces ask --current --question "Which command failed first?" --budget 1
86
+ traces ask 019a3c7e --questions questions.json --budget 5 --out answers
87
+ traces ask --last 3 --question "Did it verify the fix?" --budget 2 --format json | jq '.questions[0].answer'
88
+ ```
package/docs/bundle.md ADDED
@@ -0,0 +1,40 @@
1
+ # Session bundle
2
+
3
+ [traces](../README.md#docs) › Session bundle
4
+
5
+ `traces bundle` keeps one session's evidence beyond log rotation, for any reader that must cite the session after the live stores rotate. It never calls a model.
6
+
7
+ ```bash
8
+ traces bundle 019a3c7e --out bundle-dir
9
+ traces bundle verify bundle-dir # VERIFIED, or FAILED with each difference (exit 1)
10
+ ```
11
+
12
+ The bundle directory holds:
13
+
14
+ - `session/`: the transcript byte for byte, plus every other source file its spans cite (the subagents directory when one exists);
15
+ - `spans.otlp.jsonl`: the session's spans, as every command reads them;
16
+ - `facts.json`: the same as `traces facts <ref> --format json`;
17
+ - `git-log.txt`: commits in the session window, from the cwd the session recorded;
18
+ - `manifest.json`: SHA-256 and size per file, provenance (session id, harness, cwd, transcript hash, window), and every absent artifact with its reason.
19
+
20
+ A missing transcript fails the assembly. An optional input that is absent (no subagents, no git repo) is recorded in `manifest.absent` with the path it probed.
21
+ A bundle directory is a ref: `traces facts bundle-dir` reads the session it keeps, and `traces facts bundle-dir --no-content` gives its evidence without its words.
22
+
23
+ `bundle verify` checks every listed file's size and SHA-256, and reports a missing file, an unlisted file, a path that climbs out of the directory, and any symbolic link. It proves the bytes are the ones `traces` wrote, not who wrote them.
24
+
25
+ ## `traces bundle --help`
26
+
27
+ ```text
28
+ Usage: traces bundle <ref> | --current --out DIR (then: traces bundle verify DIR)
29
+ Keep one session's evidence beyond log rotation: session/, spans, facts, git log, sha256 manifest.
30
+ A bundle directory is a ref: traces facts DIR reads it.
31
+
32
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
33
+ --harness ID force the reader of a file ref
34
+ --out DIR a new or empty directory for the bundle
35
+
36
+ Examples:
37
+ traces bundle 019a3c7e --out evidence/019a3c7e
38
+ traces bundle --current --out evidence/now
39
+ traces bundle verify evidence/019a3c7e
40
+ ```
package/docs/check.md ADDED
@@ -0,0 +1,77 @@
1
+ # Check: the one gate
2
+
3
+ [traces](../README.md#docs) › Check
4
+
5
+ `traces check` is the one gate, and it needs one: `--contract FILE`, the built-in `--contract conformance`, or `--share-safe`. With none it exits 2 and names them, so a CI step whose contract variable expanded to nothing never passes.
6
+
7
+ ## Trace contracts
8
+
9
+ Two runs can give the same answer while one calls a forbidden tool. An output check passes both; a trace contract fails the wrong one.
10
+
11
+ ```json
12
+ {
13
+ "name": "refund-desk",
14
+ "run": { "requireCompleted": true, "maxDurationMs": 300000 },
15
+ "tools": { "required": ["Read"], "forbidden": ["Bash"], "allowed": ["Read", "Grep"], "maxCalls": 10, "enforced": true },
16
+ "retries": { "reads": ["Read", "Grep"] }
17
+ }
18
+ ```
19
+
20
+ ```bash
21
+ claude -p "$TASK" --tools Read,Grep --strict-mcp-config --output-format stream-json --verbose > run.jsonl
22
+ traces check run.jsonl --contract refund-desk.json --format junit > contract.xml
23
+ traces check --contract refund-desk.json # what each rule checks; reads no trace
24
+ ```
25
+
26
+ `tools.enforced` fails when the harness offered a tool the contract does not allow. A saved interactive session records no run status, so give a contract for a session file no `run` key.
27
+ Every rule prints `pass`, `fail`, `error` or `skipped`. The keys and semantics are in [agent-eval's trace-contract guide](https://github.com/tangle-network/agent-eval/blob/main/docs/trace-contracts.md).
28
+
29
+ ## Conformance: integrate your own system
30
+
31
+ Emit the span contract and pass the file as the ref; no adapter or plugin.
32
+
33
+ ```ts
34
+ import { llmSpan, loopSpan, steeredBy, toolSpan } from '@tangle-network/agent-trace-contract'
35
+ ```
36
+
37
+ `parent_span_id` is containment and `links` is causality (`steeredBy`), so a retry is linked to the verdict that caused it, not nested under it.
38
+ `traces check spans.otlp.jsonl --contract conformance` says what a trace can answer: each capability (`token-accounting`, `cost-attribution`, `tool-usage`, `loop-convergence`, `tree-comparison`, `steering-chain`, `latency-analysis`) available or not, with the reason. It fails only when not one entry reads as a span. It reads contract OTLP-JSONL, standard OTLP/JSON and every other ref.
39
+ Nothing is repaired on the way in, and a re-export never validates cleaner than its source: `traces convert` keeps what the producer wrote and carries `traces.source.unreadable_rows`.
40
+
41
+ ## Share safety
42
+
43
+ `traces check <path> --share-safe` reads each path as bytes, never as a trace, so a handoff, an issue draft and a span export are all just files. SAFE and SAFE_WITH_WARNINGS pass, UNSAFE fails, UNKNOWN cannot decide. `--known-secret-env NAME` also fails on that variable's value, in any encoding. Findings name the file and JSON Pointer, never the value.
44
+
45
+ ## Exit codes and output
46
+
47
+ Gates combine: 1 if any fails, else 2 if any could not decide, else 0. Exit 2 means no gate, a bad contract, a rule that could not run, an UNKNOWN share verdict, or an unreadable or ambiguous ref.
48
+ The verdict goes to stdout in `--format`: `text`, `json` (`.gates[]`), `junit` or `github` (`::error` lines). Evidence for each gate that did not pass goes under `--out DIR`; without it, `check` writes nothing.
49
+
50
+ ## `traces check --help`
51
+
52
+ ```text
53
+ Usage: traces check <ref…> | --current | --last N | --since T --contract FILE|conformance | --share-safe [flags]
54
+ The one gate: does this run pass a trace contract or conform, and is this file safe to share?
55
+ A gate is required. --share-safe reads each path as bytes, never as a trace. --contract F with no ref explains F.
56
+ Exit 0 pass, 1 any gate fails, 2 a gate could not decide: a bad contract, a rule or verdict that could not run, an unreadable ref.
57
+
58
+ --contract FILE|conformance gate on a trace contract file, or on conformance (repeatable)
59
+ --share-safe gate on whether each path is safe to share, read as bytes: SAFE passes, UNSAFE fails, UNKNOWN cannot decide
60
+ --known-secret-env NAME with --share-safe: an environment variable whose value must appear nowhere (repeatable)
61
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
62
+ --last N the N newest sessions by file mtime, never the caller's own
63
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
64
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
65
+ --harness ID force the reader of a file ref (claude-code, codex, …)
66
+ --format text|json|junit|github text (default), json, junit or github: the verdict, on stdout
67
+ --out DIR write evidence for each gate that did not pass under DIR
68
+
69
+ jq paths (--format json):
70
+ .gates[] | {gate, status} each gate and what it decided
71
+ .gates[].report.traces[]?.verdict.violations each rule a run broke, with span ids
72
+
73
+ Examples:
74
+ traces check run.otlp.jsonl --contract contract.json --format junit > contract.xml
75
+ traces check spans.otlp.jsonl --contract conformance
76
+ traces check handoff.md --share-safe --known-secret-env TANGLE_API_KEY
77
+ ```
@@ -0,0 +1,44 @@
1
+ # Convert to OTLP
2
+
3
+ [traces](../README.md#docs) › Convert
4
+
5
+ `traces convert` hands a session's spans to another tool as contract OTLP-JSONL, the OpenInference span shape every traces command reads.
6
+
7
+ ```bash
8
+ traces convert --current --out spans.otlp.jsonl
9
+ traces convert sandbox-events.json --out spans.otlp.jsonl
10
+ traces convert raw.otlp.jsonl --no-content --out metadata.otlp.jsonl --format json
11
+ ```
12
+
13
+ Any ref converts, and these inputs are sniffed: sandbox-events recordings (a tool call is one span, keyed by its message part id, however many events report on it), OpenInference JSONL, and Sandbox live streams (`"backend":"codex"`, read as `codex-exec`; `--harness codex-exec` forces it).
14
+ Without `--out` the spans go to stdout. With `--out` stdout gets one summary line, and with `--format json` it is `{out, spans, droppedAttributes}`.
15
+
16
+ `--no-content` applies the one metadata-only rule (`src/metadata-only.ts`) that `facts` and `upload` share: prompts, replies, tool input and output, error text, working directories, branch names and briefs are dropped, and `droppedAttributes` lists the keys.
17
+ `convert` does not redact: redaction belongs where data leaves the machine (`traces upload`, and `traces check <path> --share-safe` before sharing).
18
+ A re-export keeps what the producer wrote and carries the rows it could not read, so it never looks cleaner than its source.
19
+
20
+ ## `traces convert --help`
21
+
22
+ ```text
23
+ Usage: traces convert <ref…> | --current | --last N | --since T [--out FILE] [--no-content]
24
+ Hand spans to another tool: contract OTLP-JSONL, to --out or stdout.
25
+ Sandbox events, Codex live streams and OpenInference files are sniffed; --harness forces a reader.
26
+
27
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
28
+ --last N the N newest sessions by file mtime, never the caller's own
29
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
30
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
31
+ --harness ID read only this harness; with a file ref, force its reader
32
+ --latest-turn only the latest task turn of a resumed Codex or Claude Code session
33
+ --no-content metadata only: drop prompts, replies, tool input and output, error text, paths and briefs
34
+ --out PATH write the spans to PATH; stdout gets one summary line
35
+ --format text|json the --out summary line: text (default) or json {out, spans, droppedAttributes}
36
+
37
+ jq paths (--format json):
38
+ .droppedAttributes with --out and --no-content: the attribute keys the metadata-only rule removed
39
+
40
+ Examples:
41
+ traces convert --current --out spans.otlp.jsonl
42
+ traces convert raw.otlp.jsonl --no-content --out metadata.otlp.jsonl --format json
43
+ traces convert live-stream.jsonl --harness codex-exec > spans.otlp.jsonl
44
+ ```
@@ -0,0 +1,32 @@
1
+ # Develop and release
2
+
3
+ [traces](../README.md#docs) › Develop and release
4
+
5
+ ## Develop
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm build # dist/index.js (SDK), dist/cli.js (bin) and .d.ts; tests spawn dist/cli.js
10
+ pnpm test
11
+ pnpm typecheck
12
+ pnpm dev facts --current # run from source through tsx
13
+ ```
14
+
15
+ Tests assert through public boundaries: they spawn `dist/cli.js` with argv (`tests/run-cli.ts`) and assert stdout, stderr and exit code, or import SDK exports. Only process boundaries are faked: a fixture HOME and `XDG_STATE_HOME`, the clock, a stub `npm`, and the Python bridge as a stub child process.
16
+
17
+ ## Release
18
+
19
+ `main` is protected, and nothing publishes until a `v*` tag:
20
+
21
+ 1. Change `package.json` (and `pnpm-lock.yaml`) to the version, and its notes to `docs/release-<version>.md`, through a pull request.
22
+ 2. Tag the merge commit `vX.Y.Z` and push the tag.
23
+ 3. The Publish workflow checks the tag is on `main` and matches `package.json`, builds, publishes to npm with provenance, and creates the GitHub release from `docs/release-<version>.md`.
24
+
25
+ `pnpm check:package` proves the tarball ships the `traces` binary and the skill. To retry a failed publish, run the Publish workflow with the existing tag.
26
+
27
+ ## How quality is measured
28
+
29
+ [`bench/audit/`](../bench/audit) asks 19 questions about three synthetic sessions and scores answers by exact match, with no model judging anything.
30
+ Three arms answer it: `facts` (`traces facts <ref> --format json` mapped by fixed rules, $0; `pnpm test` holds its score to `bench/audit/facts-score.json`, which may only rise), `raw-files` (one `claude -p` agent per question over the raw files) and `traces` (the same agent with `traces` on PATH and the skill installed in its own HOME).
31
+ `pnpm bench:audit large` runs any arm over one synthetic session of about 50,000 spans.
32
+ The release rule: the traces arm answers at least as many questions as the raw-files arm, at lower cost. [`bench/audit/README.md`](../bench/audit/README.md) has the commands and the rule, and [`bench/parity/`](../bench/parity) scores each parser traces retires against the command that replaces it ([parsers](parsers.md)).
package/docs/diff.md ADDED
@@ -0,0 +1,38 @@
1
+ # Diff two runs
2
+
3
+ [traces](../README.md#docs) › Diff two runs
4
+
5
+ `traces diff` names the first tool call where two runs of the same task split, and prints both calls.
6
+
7
+ ```bash
8
+ traces diff 0199a1b2 0199c3d4 # two sessions
9
+ traces diff run.otlp.jsonl#branch=arm-a run.otlp.jsonl#branch=arm-b # two arms of one run
10
+ ```
11
+
12
+ A run's steps are its tool calls in containment order; model calls are not steps, since two runs never word a reply alike.
13
+ Each call compares by tool, status and argument: the command a shell call ran, the file a file tool touched, or the call's input.
14
+ First each run's own places become placeholders (its cwd and worktree, temp dirs, and hex ids), so two runs of one play in two worktrees agree wherever they did the same thing.
15
+ The first divergence is `changed`, `replaced`, `only-in-a`, `only-in-b` or `reordered`; `--format json` holds every step, and `.diff.firstDivergence.index` is 0-based.
16
+ Exit 0 agree, 1 diverge, 2 could not compare (a side unreadable, or no call records an outcome); a gate treats 2 as unknown, never as a pass.
17
+
18
+ ## `traces diff --help`
19
+
20
+ ```text
21
+ Usage: traces diff <a> <b> [--format text|json]
22
+ Where two runs of one task split: the first tool call that differs, with both calls.
23
+ A side is a session id or 8+ char prefix or suffix, a session file, OTLP, file#<trace id> or file#branch=<id>.
24
+ Calls compare by tool, status and argument (the command, the file, or the input), after each run's
25
+ cwd, worktree, temp dirs and hex ids become <cwd>, <worktree>, <tmp> and <hex>. Model calls are not compared.
26
+ Exit 0 agree, 1 diverge, 2 could not compare: a side cannot be read, or neither records an outcome.
27
+
28
+ --format text|json text (default) or json
29
+
30
+ jq paths (--format json):
31
+ .diff.firstDivergence where and how the runs first split (index is 0-based)
32
+ .a.steps[.diff.firstDivergence.a].text A's call at that step, normalized
33
+
34
+ Examples:
35
+ traces diff 0199a1b2 0199c3d4
36
+ traces diff passing.otlp.jsonl failing.otlp.jsonl
37
+ traces diff run.otlp.jsonl#branch=a run.otlp.jsonl#branch=b --format json
38
+ ```
package/docs/facts.md ADDED
@@ -0,0 +1,114 @@
1
+ # Session facts
2
+
3
+ [traces](../README.md#docs) › Session facts
4
+
5
+ `traces facts` states what happened in a session, exactly, for $0: who acted, the turns, tools, commands, pull requests, first failure, tokens, cost and the time lost.
6
+ It calls no model, and the same spans always give the same sheet.
7
+
8
+ ```bash
9
+ traces facts --current # the text sheet: at most 25 lines a session
10
+ traces facts 019a3c7e --format json # the whole sheet, with span ids
11
+ traces facts --since 2026-10-14 --until 2026-10-15 # one day, across every harness
12
+ traces facts --since 1d --format jsonl # one row per typed turn
13
+ traces facts --since 30d --match '^gh pr merge' # each matching turn, agent message or command
14
+ ```
15
+
16
+ ## The text sheet
17
+
18
+ Text is the default; pass `--format json` to parse. Each session takes at most 25 lines, times in UTC:
19
+
20
+ ```
21
+ 99d75d4b claude-code · agent-dev-container (feat/gap-closure) · 10-15 09:12→12:40 UTC · active 3.1 h
22
+ who dotfiles sha256:5843…ca56 · claude-opus-5-5 (from session-identity)
23
+ human turns 14 (excluded: injected 6, fleet 2, relay 1) · interrupts 2
24
+ tools 412 (Bash 220 (31 failed), Edit 88, Read 61, …) · subagents 6 (lint, test, docs, …)
25
+ commands 236: pnpm test 41 (19 failed), git status 30, rg 22, gh pr 9 (1 failed), git push 6, …
26
+ tokens in 2.1M · out 310k · cache read 88M · $38.20 list price (Router rates)
27
+ waste 52 min: `pnpm test` failed 14× in a row (31 min) · 2 stalls ≥ 10 min (21 min) · reverts 1
28
+ PRs opened #11021 #11022 · merged #11006 · changed 23 files
29
+ first failure 09:40 Bash: exit 1 (blame agent, span 6be1…)
30
+ final "Merged #11006; …"
31
+ ```
32
+
33
+ A fact the spans cannot support reads `unknown` with the reason. A selection of several sessions starts with one line naming the count, the window and the sessions it could not read.
34
+
35
+ ## What it states
36
+
37
+ Three rules hold for every field. Every fact is about this session's own agent: a subagent's work is named in `subagents` and counted in `subagentTools` (by tool name, with failures), never added to the parent's totals. Every fact names its `spanIds`. A fact the spans cannot support is `null` with its reason, never a guess and never a silent zero; `partial` marks a value known to be incomplete.
38
+
39
+ | Fact | What it is |
40
+ |---|---|
41
+ | `toolCalls`, `toolCallsByName` | tool calls this session's agent made, by name. A Codex command that checks work is `exec_command.verify` |
42
+ | `subagents` | every subagent-spawning call, with its task name and `agentType` (Claude Code's `subagent_type`); a refused Codex spawn is `ERROR`. On the sheet, `subagent tools` counts their calls |
43
+ | `commands` | the programs the session's commands ran (`labctl run`, `git push`, `pnpm test`), most runs first: `runs`; `failed`, runs that were their call's only command (a `cd` aside) and ended in error; and `inScripts`, runs inside a Codex code-mode script, which records no exit code of its own; `nonZeroExits`, each run `failed` counts with the `code` its output printed (`null` when it printed none), `inScript`, `at` and `spanId`. `--match` gives each run's span. A code-mode `tools.exec_command` call inside `for (const x of [...])` counts once per element, `${x}` or `{cmd: x}` taking each value; inside any other loop, once as written. On the sheet, a tool's `(31 failed)` counts its calls that ended in error |
44
+ | `pullRequests` | pull requests created and merged, by number or head branch, with when the command ran and the span that named it |
45
+ | `humanTurns` | turns a person typed, each with `author`, `answered`, `reply`, `interrupted` and `signals` (`interrupt`, `rejected-tool`, `after-claim`) |
46
+ | `excludedTurns`, `turnsByActor` | every left-out turn, by reason and author, so the filter is checkable |
47
+ | `tokens`, `cost` | from one owner on every harness; `input` is the whole prompt, cache included; `cost` is agent-runtime's `priceTokens`, labelled `list price (Router rates)` |
48
+ | `measurements.waste` | failing streaks (3 or more identical failures), stalls of 10 minutes or more that no turn ended, `git revert` and `reset --hard`, repeated commands |
49
+ | `measurements.activeMinutes`, `.skills`, `.modelCalls`, `.guardRefusals`, `.limitStop` | minutes worked (gaps capped at 10); skills used, by the classifier `traces skills` uses; the context each of the session's own model calls read (input + cache read + cache write): `contextTokens.mean`, and `.p50` from 2 calls, and `latencyByContext`, tool result to next reply in 100k-token bands (`0-100k` … `900k+`), `p50Ms`/`p90Ms` from 2 replies, leaving out waits of 10 minutes or more; the `toolCalls` a hook refused (`hook error: <guard>`), `byGuard`; and the usage or weekly limit notice the session stopped on, its run's error (Codex: "You’ve hit your usage limit") or else its last reply (Claude Code: "You've hit your weekly limit"), else `null` |
50
+ | `repo`, `cwd`, `changedFiles`, `finalMessages`, `recordFile`, `record` | the repository and recorded cwd, files the session changed, and the last message of the agent and each subagent. With `--format json`, `recordFile` is the session file, and `record` sits on each `humanTurns` entry, `firstFailure` (the failing call's result, else the call), `commands` row (its latest run's result) and each of its `nonZeroExits`, and `changedFiles` entry (the call that first changed it), the record it came from: `{line}` in `recordFile`, `{file, line}` in a subagent's file, `null` when no record names it. Cite `recordFile:line` instead of searching the files. Absent under `--no-content` |
51
+ | `firstFailure` | the first failure among the session's own records, with its blame; `unknown` when no status was recorded, `ambiguous` when two ended at once |
52
+ | `agent` | the profile, model, harness and directive that acted, from the root span, else the session-identity record |
53
+
54
+ ## Turns
55
+
56
+ A turn is one a person typed into this session. History a fork or a compaction replays, harness-injected blocks (instructions, environment context, notifications, skill expansions) and a second record of the same turn are left out, each listed in `excludedTurns`. A turn the reader called human is checked once more by org convention: a fleet or session message, or a first turn over 1,500 characters, is `fleet`; a long markdown-heavy paste is `relay`; a harness command (`/model`) is left out.
57
+ For Codex, the harness's own labels (`content_item_kinds`) decide: a message is the person's when every item is a `user.` kind. An answer to Codex's question tool (`<send_user_message_question_reply>`) is a turn, and so is a turn with an attached image, whose text is the report Codex wrote of it, without the `<image>` wrapper.
58
+
59
+ `--format jsonl` prints one row per typed turn: `host`, `harness`, `session`, `cwd`, `at`, `author`, `text`, `answered`, `reply`, `interrupted`, `signals`, `spanId`, `recordFile` and `record` (native `{file, line}`, or `null` when unavailable). Window JSONL reads only the session's own records and uses the same turn classifier and reply fold as the full sheet; it does not compute costs, commands or other unused measurements.
60
+ `--match RE` prints one row per matching human turn, agent message or shell command (`sessionId`, `kind`, `at`, `spanId`, `source`, text clipped to 300 characters). A command row of a named session also has `record` and `lastLine`, the last non-blank line of the command's whole output, which the row's text never shows. A command row's `failed` follows the `commands` rule; it is `null`, `?` in text, when its call's status is not its own. Commands match one simple command at a time, so `^traces ` matches `cd x && traces facts …` and never `tangle traces list`. Over a window it reads the turn index under `$XDG_STATE_HOME/traces/turn-index`, which reads only bytes appended since the last search.
61
+
62
+ ## Pull requests
63
+
64
+ `pullRequests` scans each command the way a shell would, so a `gh pr create` inside a heredoc body never counts.
65
+ A pull request is named by its number when the command or an output that joins to it shows one, else by its head branch.
66
+ A create whose call returned while it still ran takes the number a later poll of that process printed.
67
+ In a Codex code-mode script, each tool result the script printed is decoded (`exit_code` or `session_id`, and the command's output), and a later script's `write_stdin` poll of the session counts as that poll.
68
+ A create counts only where an output shows `gh` answered: a script that completed says nothing about its commands.
69
+
70
+ ## `--no-content`, windows and exit status
71
+
72
+ `--no-content` withholds conversation, commands, paths and assignments and keeps the measurements; it is a local view, not a share-safety verdict (`traces check <path> --share-safe` is).
73
+ With `--since`/`--until`, the turn facts keep only turns inside `[since, until)`; every other fact covers the whole session. `selection` names the window, the sessions outside it and every unreadable session.
74
+ A named session that cannot be read exits 2; in a window it is skipped, named, and the run exits 0.
75
+
76
+ ## SDK
77
+
78
+ ```ts
79
+ import { buildSessionFactsReport, computeSessionFacts, renderSessionFacts } from '@tangle-network/traces'
80
+
81
+ const [facts] = computeSessionFacts(spans)
82
+ console.log(facts.toolCalls.value, facts.toolCalls.spanIds)
83
+ console.log(renderSessionFacts(buildSessionFactsReport(spans)))
84
+ ```
85
+
86
+ ## `traces facts --help`
87
+
88
+ ```text
89
+ Usage: traces facts <ref…> | --current | --last N | --since T [flags]
90
+ What happened, exactly, for $0: who acted, turns, tools, commands, PRs, first failure, tokens, cost, time lost.
91
+
92
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
93
+ --last N the N newest sessions by file mtime, never the caller's own
94
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
95
+ --until T close the --since window (exclusive); same forms
96
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
97
+ --harness ID read only this harness; with a file ref, force its reader
98
+ --latest-turn only the latest task turn of a resumed Codex or Claude Code session
99
+ --match RE one row per human turn, agent message or shell command matching RE (a command: ok, failed or ?)
100
+ --no-content omit conversation, commands, paths and assignments
101
+ --format text|json|jsonl text (default, 25 lines a session), json (the sheet), or jsonl: one row per typed turn
102
+ --out PATH write the sheet to PATH; stdout gets one summary line
103
+
104
+ jq paths (--format json):
105
+ .sessions[0].humanTurns.value[] | {at, text, spanId} each typed turn, its time and span
106
+ .sessions[0].commands.value[] programs run: runs, failed, inScripts
107
+ .sessions[0].measurements.tools[] | {name, calls, failed: .outcomes.error} tool calls by name, and failures
108
+ .sessions[0] | {firstFailure, tokens, cost, agent: .agent.value} first failure, tokens, cost, who acted
109
+
110
+ Examples:
111
+ traces facts --current
112
+ traces facts 019a3c7e --format json | jq '.sessions[0].measurements.waste'
113
+ traces facts --since 30d --match '^gh pr merge'
114
+ ```
package/docs/mcp.md ADDED
@@ -0,0 +1,36 @@
1
+ # MCP server
2
+
3
+ [traces](../README.md#docs) › MCP server
4
+
5
+ `traces mcp` serves agent-eval's read-only span tools over stdio, for an MCP client with no shell. An agent with a shell runs the CLI instead.
6
+
7
+ ```bash
8
+ claude mcp add traces -- traces mcp spans.otlp.jsonl
9
+ claude mcp add traces -- traces mcp --last 5
10
+ ```
11
+
12
+ It serves `getDatasetOverview`, `queryTraces`, `countTraces`, `viewTrace`, `viewSpans`, `searchTrace` and `searchSpan` over the spans selected at start. Each declares `readOnlyHint` and `idempotentHint`.
13
+
14
+ - Spans are redacted before the store is built, and each result again at the boundary. The server refuses to start when the redacted spans are still UNSAFE or UNKNOWN.
15
+ - Each result carries an untrusted-text notice, and a result above 512 KiB is refused rather than cut.
16
+ - `readSpanSource` is not served: a secret split across two windows matches no redaction rule.
17
+
18
+ ## `traces mcp --help`
19
+
20
+ ```text
21
+ Usage: traces mcp <ref…> | --current | --last N | --since T
22
+ Serve agent-eval's read-only span tools over stdio, for an MCP client with no shell.
23
+ Spans and results are redacted, and it refuses to start if redacted spans are UNSAFE or UNKNOWN.
24
+ Each result carries an untrusted-text notice and is capped at 512 KiB.
25
+
26
+ --current the caller's own session: CODEX_THREAD_ID, else CLAUDE_CODE_SESSION_ID
27
+ --last N the N newest sessions by file mtime, never the caller's own
28
+ --since T every session with a record since T: 30m, 2h, 7d or an ISO time
29
+ --cwd DIR sessions recorded in DIR or under it, or in a worktree of its repo
30
+ --harness ID read only this harness; with a file ref, force its reader
31
+
32
+ Examples:
33
+ traces mcp spans.otlp.jsonl
34
+ traces mcp --last 5
35
+ claude mcp add traces -- traces mcp spans.otlp.jsonl
36
+ ```