@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.
- package/README.md +61 -1078
- package/dist/analyst-model-call-JVUOYZ5V.js +20 -0
- package/dist/ask-2KF4KSPB.js +39 -0
- package/dist/check-YF6ZHPGG.js +322 -0
- package/dist/chunk-2XXABIRL.js +25 -0
- package/dist/chunk-4VKPN432.js +138 -0
- package/dist/chunk-6GOG5KIK.js +53 -0
- package/dist/chunk-7USMM4MA.js +5695 -0
- package/dist/chunk-CG5QHQO4.js +492 -0
- package/dist/chunk-CQ6UYZG6.js +1217 -0
- package/dist/chunk-DU534HAX.js +263 -0
- package/dist/chunk-FC4V25CG.js +503 -0
- package/dist/chunk-GLUDGUGJ.js +232 -0
- package/dist/chunk-GNRXKQEQ.js +120 -0
- package/dist/chunk-K2KH2HHD.js +228 -0
- package/dist/chunk-M3W6TZHQ.js +381 -0
- package/dist/chunk-O52XK7OB.js +19 -0
- package/dist/chunk-PYH3PL3N.js +62 -0
- package/dist/chunk-QLVP37YB.js +782 -0
- package/dist/chunk-QUHUMZ4W.js +759 -0
- package/dist/chunk-REO2JQJY.js +535 -0
- package/dist/chunk-RZL5GAWH.js +60 -0
- package/dist/chunk-TOCNGML5.js +273 -0
- package/dist/chunk-UQSAGXPH.js +208 -0
- package/dist/chunk-VFOLC4OX.js +407 -0
- package/dist/chunk-WF7PTDCZ.js +3921 -0
- package/dist/chunk-YAX72TST.js +259 -0
- package/dist/cli.js +2086 -2050
- package/dist/diff-J4RAIOXO.js +16 -0
- package/dist/improvement-LCCVGGUS.js +25 -0
- package/dist/index.d.ts +1235 -4357
- package/dist/index.js +53 -1204
- package/dist/live-XXL7VZWB.js +14 -0
- package/dist/mcp-EYIDKBEK.js +85 -0
- package/dist/report-E3G2T5TJ.js +338 -0
- package/dist/run-watch-RAOOAXOF.js +649 -0
- package/dist/supervisor-report-RMPLLBGZ.js +29 -0
- package/dist/supervisor-run-context-WO6VOTGV.js +30 -0
- package/dist/upload-3R7GRSKS.js +20 -0
- package/dist/verify-safe-C5ZXZOZ2.js +8 -0
- package/docs/analyze.md +76 -0
- package/docs/ask.md +88 -0
- package/docs/bundle.md +40 -0
- package/docs/check.md +77 -0
- package/docs/convert.md +44 -0
- package/docs/develop.md +32 -0
- package/docs/diff.md +38 -0
- package/docs/facts.md +114 -0
- package/docs/mcp.md +36 -0
- package/docs/migrate-1.0.md +33 -0
- package/docs/parsers.md +66 -0
- package/docs/release-1.0.0.md +147 -0
- package/docs/release-1.0.1.md +4 -0
- package/docs/sdk.md +77 -0
- package/docs/select.md +70 -0
- package/docs/skills.md +46 -0
- package/docs/upload.md +47 -0
- package/docs/watch-jsonl.v1.example.jsonl +5 -0
- package/docs/watch.md +66 -0
- package/examples/README.md +9 -0
- package/examples/custom-analyst.ts +65 -74
- package/install.sh +22 -2
- package/package.json +10 -9
- package/skills/inspect-agent-traces/SKILL.md +47 -110
- package/dist/chunk-7DVZCCZI.js +0 -20983
- package/docs/replay-verify.md +0 -123
- package/docs/stream-findings.svg +0 -39
- package/docs/trace-analysts.md +0 -643
- package/examples/custom-backend.ts +0 -21
- package/examples/external-engines.ts +0 -62
- package/examples/improvement-config.mjs +0 -63
- package/examples/observe-and-alert.ts +0 -19
- package/examples/register-harness.ts +0 -26
- package/skills/build-trace-analyst/SKILL.md +0 -76
- package/skills/build-trace-analyst/agents/openai.yaml +0 -4
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TRACE_ID_MINTED_ATTR,
|
|
3
|
+
toOpenInferenceSpan
|
|
4
|
+
} from "./chunk-CQ6UYZG6.js";
|
|
5
|
+
|
|
6
|
+
// src/conformance.ts
|
|
7
|
+
import {
|
|
8
|
+
validateTraceSpans
|
|
9
|
+
} from "@tangle-network/agent-trace-contract";
|
|
10
|
+
var SPAN_ID_SAMPLE = 5;
|
|
11
|
+
var ISSUE_SAMPLE = 10;
|
|
12
|
+
var SEVERITY_BADGE = {
|
|
13
|
+
error: "\u{1F534} ERROR",
|
|
14
|
+
warn: "\u{1F7E0} WARN",
|
|
15
|
+
info: "\u2139\uFE0F INFO"
|
|
16
|
+
};
|
|
17
|
+
var CAPABILITY_ANALYSES = {
|
|
18
|
+
"token-accounting": "execution facts \u2192 Direct model usage (input/output/reasoning/cache token totals)",
|
|
19
|
+
"cost-attribution": "execution facts \u2192 Cost coverage (observed vs estimated USD)",
|
|
20
|
+
"tool-usage": "tool-usage table + per-tool duplicate/retry/error rates + stuck-loop detection",
|
|
21
|
+
"loop-convergence": "round-over-round convergence (did round N+1 improve on N)",
|
|
22
|
+
"tree-comparison": "arm-vs-arm comparison: `traces diff <file>#branch=<a> <file>#branch=<b>` marks where two arms first diverge",
|
|
23
|
+
"steering-chain": "steering chain (which verdict caused which retry)",
|
|
24
|
+
"latency-analysis": "latency + critical-path timing distributions"
|
|
25
|
+
};
|
|
26
|
+
function summarizeSpanStructure(spans) {
|
|
27
|
+
const ids = new Set(spans.map((span) => JSON.stringify([span.trace_id, span.span_id])));
|
|
28
|
+
const rootsByTrace = /* @__PURE__ */ new Map();
|
|
29
|
+
let rootless = 0;
|
|
30
|
+
let orphans = 0;
|
|
31
|
+
let links = 0;
|
|
32
|
+
for (const span of spans) {
|
|
33
|
+
links += span.links?.length ?? 0;
|
|
34
|
+
if (!rootsByTrace.has(span.trace_id)) rootsByTrace.set(span.trace_id, 0);
|
|
35
|
+
if (span.parent_span_id === null) {
|
|
36
|
+
rootless += 1;
|
|
37
|
+
rootsByTrace.set(span.trace_id, (rootsByTrace.get(span.trace_id) ?? 0) + 1);
|
|
38
|
+
} else if (!ids.has(JSON.stringify([span.trace_id, span.parent_span_id]))) orphans += 1;
|
|
39
|
+
}
|
|
40
|
+
let extraRoots = 0;
|
|
41
|
+
let tracesWithoutRoot = 0;
|
|
42
|
+
for (const roots of rootsByTrace.values()) {
|
|
43
|
+
if (roots === 0) tracesWithoutRoot += 1;
|
|
44
|
+
else extraRoots += roots - 1;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
spans: spans.length,
|
|
48
|
+
traces: rootsByTrace.size,
|
|
49
|
+
rootless,
|
|
50
|
+
extraRoots,
|
|
51
|
+
tracesWithoutRoot,
|
|
52
|
+
orphans,
|
|
53
|
+
links
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function renderStructure(lines, structure) {
|
|
57
|
+
const share = structure.spans === 0 ? 0 : structure.rootless / structure.spans * 100;
|
|
58
|
+
lines.push(
|
|
59
|
+
`**Span structure:** ${structure.spans} spans across ${structure.traces} trace(s); ${structure.rootless}/${structure.spans} (${share.toFixed(1)}%) carry no parent edge (${structure.traces} of those are the trace roots); ${structure.orphans} name a parent absent from the export; ${structure.links} causal link(s).`
|
|
60
|
+
);
|
|
61
|
+
lines.push("");
|
|
62
|
+
if (structure.extraRoots === structure.spans - structure.traces && structure.extraRoots > 0) {
|
|
63
|
+
lines.push(
|
|
64
|
+
"> Every span is its own root. Nothing rolls up: there is no run, round or session to attribute cost, tokens or outcomes to, only a list of events."
|
|
65
|
+
);
|
|
66
|
+
lines.push("");
|
|
67
|
+
} else if (structure.extraRoots > 0) {
|
|
68
|
+
lines.push(
|
|
69
|
+
`> ${structure.extraRoots} span(s) attribute to nothing: beyond the one root each trace is entitled to, their model calls, tool calls and verdicts roll up to no enclosing unit of work, so only the ${structure.spans - structure.extraRoots} attached span(s) can be charged to the run that caused them.`
|
|
70
|
+
);
|
|
71
|
+
lines.push("");
|
|
72
|
+
}
|
|
73
|
+
if (structure.tracesWithoutRoot > 0) {
|
|
74
|
+
lines.push(
|
|
75
|
+
`> ${structure.tracesWithoutRoot} trace(s) have no root at all: every span names a parent, and the top of the tree is absent from the export, so nothing in them can be charged to a run.`
|
|
76
|
+
);
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
if (structure.links === 0) {
|
|
80
|
+
lines.push(
|
|
81
|
+
"> No causal links. Containment (`parent_span_id`) was recorded; causality (round N's verdict CAUSING round N+1) was not, so a steering or retry chain cannot be followed."
|
|
82
|
+
);
|
|
83
|
+
lines.push("");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function conformanceOfSpans(spans) {
|
|
87
|
+
return validateTraceSpans(spans.map(toOpenInferenceSpan));
|
|
88
|
+
}
|
|
89
|
+
function skippedAnalyses(validation) {
|
|
90
|
+
return validation.capabilities.filter((capability) => !capability.available).map((capability) => ({
|
|
91
|
+
capability: capability.name,
|
|
92
|
+
analysis: CAPABILITY_ANALYSES[capability.name] ?? "analyses keyed off this capability",
|
|
93
|
+
reason: capability.reason ?? "no reason recorded"
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
function tableCell(value) {
|
|
97
|
+
return value.replace(/\s+/g, " ").replaceAll("|", "\\|").trim();
|
|
98
|
+
}
|
|
99
|
+
function spanIdSample(finding) {
|
|
100
|
+
const ids = finding.spanIds ?? [];
|
|
101
|
+
if (ids.length === 0) return "";
|
|
102
|
+
const shown = ids.slice(0, SPAN_ID_SAMPLE).map((id) => `\`${tableCell(id)}\``).join(", ");
|
|
103
|
+
return ids.length > SPAN_ID_SAMPLE ? `${shown} +${ids.length - SPAN_ID_SAMPLE} more` : shown;
|
|
104
|
+
}
|
|
105
|
+
function capabilityRow(capability) {
|
|
106
|
+
const analysis = CAPABILITY_ANALYSES[capability.name] ?? "\u2014";
|
|
107
|
+
return capability.available ? `| \`${capability.name}\` | \u2705 available | ${tableCell(analysis)} | \u2014 |` : `| \`${capability.name}\` | \u274C unavailable | ${tableCell(analysis)} | ${tableCell(capability.reason ?? "no reason recorded")} |`;
|
|
108
|
+
}
|
|
109
|
+
function renderInheritedLoss(lines, unreadable) {
|
|
110
|
+
if (unreadable === void 0 || unreadable.inherited === 0) return;
|
|
111
|
+
lines.push(
|
|
112
|
+
`**${unreadable.inherited} row(s) of the ORIGINAL source are not in this file.** It is a re-export: ${unreadable.kinds.join(", ")} row(s) could not be represented as spans and were left behind by an earlier hop, so nothing below is a finding about them. The findings here describe what survived, and this file is NOT a substitute for validating the source.`
|
|
113
|
+
);
|
|
114
|
+
lines.push("");
|
|
115
|
+
}
|
|
116
|
+
function renderMintedFields(lines, spans) {
|
|
117
|
+
const minted = spans?.filter((span) => span.attributes[TRACE_ID_MINTED_ATTR] === true) ?? [];
|
|
118
|
+
if (minted.length === 0) return;
|
|
119
|
+
const ids = [...new Set(minted.map((span) => span.trace_id))];
|
|
120
|
+
lines.push(
|
|
121
|
+
`**${minted.length} span(s) carry a trace_id this tool minted** (${ids.slice(0, 3).map((id) => `\`${tableCell(id)}\``).join(", ")}${ids.length > 3 ? `, +${ids.length - 3} more` : ""}): their producer wrote none, and a file whose rows carry no trace id cannot be read back at all. So \`missing-trace-id\` is NOT reported below even though the source earns it \u2014 the minted id is recorded per span under \`traces.raw_attribute.trace_id\` / \`traces.substituted_fields\`, and this file is NOT a substitute for validating the source.`
|
|
122
|
+
);
|
|
123
|
+
lines.push("");
|
|
124
|
+
}
|
|
125
|
+
function renderIngest(lines, files, skipped, issues, withheld) {
|
|
126
|
+
if (files && files.length > 0) {
|
|
127
|
+
const rows = files.reduce((total, file) => total + file.rows, 0);
|
|
128
|
+
const spans = files.reduce((total, file) => total + file.spans, 0);
|
|
129
|
+
const repeats = files.reduce((total, file) => total + file.repeats, 0);
|
|
130
|
+
lines.push(
|
|
131
|
+
`**Ingested:** ${spans} span(s) from ${rows} row(s) across ${files.length} OTLP file(s)` + (repeats > 0 ? `; ${repeats} row(s) re-declared a span already read, identically.` : ".")
|
|
132
|
+
);
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push("| File | Rows | Spans | Identical repeats |");
|
|
135
|
+
lines.push("|---|---:|---:|---:|");
|
|
136
|
+
for (const file of files) {
|
|
137
|
+
lines.push(`| \`${tableCell(file.path)}\` | ${file.rows} | ${file.spans} | ${file.repeats} |`);
|
|
138
|
+
}
|
|
139
|
+
lines.push("");
|
|
140
|
+
if (repeats > 0) {
|
|
141
|
+
lines.push(
|
|
142
|
+
"> Identical repeats are not a defect. A multi-shot run writes one file per shot and each file re-declares the run and cell spans it hangs under, so every file is analysable on its own; reading them together collapses those back to one span. Only a repeat with DIFFERENT content is reported as a problem."
|
|
143
|
+
);
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (skipped && skipped.length > 0) {
|
|
148
|
+
lines.push(
|
|
149
|
+
`**${skipped.length} JSONL file(s) in this directory are not OTLP** and were not read. Their lines are not counted above and produce no findings: they were never claimed to be spans.`
|
|
150
|
+
);
|
|
151
|
+
lines.push("");
|
|
152
|
+
lines.push("| File | What it holds |");
|
|
153
|
+
lines.push("|---|---|");
|
|
154
|
+
for (const file of skipped.slice(0, ISSUE_SAMPLE)) {
|
|
155
|
+
lines.push(`| \`${tableCell(file.path)}\` | ${tableCell(file.reason)} |`);
|
|
156
|
+
}
|
|
157
|
+
if (skipped.length > ISSUE_SAMPLE) {
|
|
158
|
+
lines.push(`| ... | ${skipped.length - ISSUE_SAMPLE} more omitted |`);
|
|
159
|
+
}
|
|
160
|
+
lines.push("");
|
|
161
|
+
}
|
|
162
|
+
if (issues && issues.length > 0) {
|
|
163
|
+
lines.push(
|
|
164
|
+
`**${issues.length} row(s) could not be analysed.** They are excluded from every count above rather than repaired, because a synthesized timestamp or id would put invented work into the totals.`
|
|
165
|
+
);
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push("| Reason | File | Line | Detail |");
|
|
168
|
+
lines.push("|---|---|---:|---|");
|
|
169
|
+
for (const issue of issues.slice(0, ISSUE_SAMPLE)) {
|
|
170
|
+
lines.push(`| ${issue.kind} | \`${tableCell(issue.file)}\` | ${issue.line} | ${tableCell(issue.detail)} |`);
|
|
171
|
+
}
|
|
172
|
+
if (issues.length > ISSUE_SAMPLE) {
|
|
173
|
+
lines.push(`| ... | ${issues.length - ISSUE_SAMPLE} more omitted | - | - |`);
|
|
174
|
+
}
|
|
175
|
+
lines.push("");
|
|
176
|
+
}
|
|
177
|
+
if (withheld && withheld.length > 0) {
|
|
178
|
+
lines.push(
|
|
179
|
+
`**${withheld.length} span(s) were analysed with one field withheld.** They ARE counted above and in every table that does not read the named field: a span with an unusable timestamp still has a real identity, name, tool and status, and dropping it would delete real work from every total to avoid one wrong number. The field itself is withheld, never repaired.`
|
|
180
|
+
);
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push("| Reason | Field | File | Line | Detail |");
|
|
183
|
+
lines.push("|---|---|---|---:|---|");
|
|
184
|
+
for (const entry of withheld.slice(0, ISSUE_SAMPLE)) {
|
|
185
|
+
lines.push(
|
|
186
|
+
`| ${entry.kind} | \`${tableCell(entry.field)}\` | \`${tableCell(entry.file)}\` | ${entry.line} | ${tableCell(entry.detail)} |`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (withheld.length > ISSUE_SAMPLE) {
|
|
190
|
+
lines.push(`| ... | - | ${withheld.length - ISSUE_SAMPLE} more omitted | - | - |`);
|
|
191
|
+
}
|
|
192
|
+
lines.push("");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function conformanceBody(validation, opts) {
|
|
196
|
+
const heading = "#".repeat(opts.headingLevel ?? 2);
|
|
197
|
+
const lines = [];
|
|
198
|
+
const errors = validation.findings.filter((finding) => finding.severity === "error").length;
|
|
199
|
+
const warnings = validation.findings.filter((finding) => finding.severity === "warn").length;
|
|
200
|
+
const infos = validation.findings.filter((finding) => finding.severity === "info").length;
|
|
201
|
+
const unavailable = validation.capabilities.filter((capability) => !capability.available);
|
|
202
|
+
lines.push(
|
|
203
|
+
`${errors} error, ${warnings} warn, ${infos} info finding(s); ${validation.capabilities.length - unavailable.length}/${validation.capabilities.length} analysis capabilities available.`
|
|
204
|
+
);
|
|
205
|
+
lines.push("");
|
|
206
|
+
renderInheritedLoss(lines, opts.unreadable);
|
|
207
|
+
renderMintedFields(lines, opts.spans);
|
|
208
|
+
renderIngest(lines, opts.files, opts.skipped, opts.issues, opts.withheld);
|
|
209
|
+
if (opts.spans && opts.spans.length > 0) renderStructure(lines, summarizeSpanStructure(opts.spans));
|
|
210
|
+
if (validation.findings.length > 0) {
|
|
211
|
+
lines.push("| Severity | Code | Consequence | Blocks | Spans |");
|
|
212
|
+
lines.push("|---|---|---|---|---|");
|
|
213
|
+
for (const finding of validation.findings) {
|
|
214
|
+
const badge = SEVERITY_BADGE[finding.severity] ?? finding.severity;
|
|
215
|
+
const blocks = finding.blocks?.length ? finding.blocks.map((name) => `\`${name}\``).join(", ") : "\u2014";
|
|
216
|
+
lines.push(
|
|
217
|
+
`| ${badge} | \`${tableCell(finding.code)}\` | ${tableCell(finding.message)} | ${blocks} | ${spanIdSample(finding) || "\u2014"} |`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
lines.push("");
|
|
221
|
+
} else {
|
|
222
|
+
lines.push("No conformance findings: the spans carry every field this analysis reads.");
|
|
223
|
+
lines.push("");
|
|
224
|
+
}
|
|
225
|
+
lines.push("| Capability | State | Analysis it drives | Why not |");
|
|
226
|
+
lines.push("|---|---|---|---|");
|
|
227
|
+
for (const capability of validation.capabilities) lines.push(capabilityRow(capability));
|
|
228
|
+
lines.push("");
|
|
229
|
+
const skipped = skippedAnalyses(validation);
|
|
230
|
+
if (skipped.length > 0) {
|
|
231
|
+
lines.push(`${heading}# analyses skipped, and why`);
|
|
232
|
+
lines.push("");
|
|
233
|
+
lines.push(
|
|
234
|
+
"These reported nothing because the trace never carried what they read \u2014 not because the run was uneventful. Every section they feed is marked at the table, not only here."
|
|
235
|
+
);
|
|
236
|
+
lines.push("");
|
|
237
|
+
for (const entry of skipped) {
|
|
238
|
+
lines.push(`- **${entry.analysis}** \u2014 skipped: ${entry.reason} (\`${entry.capability}\`).`);
|
|
239
|
+
}
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
return lines.join("\n");
|
|
243
|
+
}
|
|
244
|
+
function renderValidation(validation, opts) {
|
|
245
|
+
const errors = validation.findings.filter((finding) => finding.severity === "error");
|
|
246
|
+
const available = validation.capabilities.filter((capability) => capability.available);
|
|
247
|
+
const verdict = !validation.ok ? `NOT A TRACE \u2014 ${errors.length} error finding(s): not one entry in this export could be read as a span, so there is nothing to analyse and every capability below is unavailable. An empty export and one whose every line was unreadable both land here. This is the only state that exits non-zero.` : available.length === 0 ? `ANALYSABLE, WITH NOTHING TO ANALYSE \u2014 spans WERE read, so this is a trace, but 0/${validation.capabilities.length} capabilities are available: every analysis this package runs reads a field none of these spans carry. The rows below name what each one is missing. Exits 0, because the export is readable \u2014 a caller deciding whether to spend a run should read the capability table, not the exit code.` : "CONFORMS \u2014 analysable. Every finding below is a degradation of a readable trace, not a bar to reading it; each one names the capability it costs.";
|
|
248
|
+
return `# trace conformance \u2014 ${opts.subject}
|
|
249
|
+
|
|
250
|
+
**${verdict}**
|
|
251
|
+
|
|
252
|
+
${conformanceBody(validation, { ...opts, headingLevel: 2 })}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export {
|
|
256
|
+
conformanceOfSpans,
|
|
257
|
+
skippedAnalyses,
|
|
258
|
+
renderValidation
|
|
259
|
+
};
|