@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
package/docs/trace-analysts.md
DELETED
|
@@ -1,643 +0,0 @@
|
|
|
1
|
-
# Trace analysts
|
|
2
|
-
|
|
3
|
-
Use `traces` to answer three different questions.
|
|
4
|
-
Keeping them separate prevents an exploratory model output from being reported as a confirmed defect.
|
|
5
|
-
|
|
6
|
-
| Question | Tool | Output |
|
|
7
|
-
| --- | --- | --- |
|
|
8
|
-
| What happened? | Built-in local checks | Findings from explicit trace facts |
|
|
9
|
-
| Why might it have happened? | `--llm`, HALO, or a custom analyst | Findings or a diagnosis report with cited spans |
|
|
10
|
-
| What behavior should we inspect? | Hodoscope | Samples marked `needs_review` |
|
|
11
|
-
| What exactly does this session say about X? | `traces ask` | An answer per question, with checked `trace://` citations |
|
|
12
|
-
| What does this session state, exactly and for free? | `traces facts` | A deterministic facts sheet, no model call |
|
|
13
|
-
|
|
14
|
-
## Start here
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
# Free local checks over the active Codex coordinator and connected workers.
|
|
18
|
-
traces analyze --harness codex --current --latest-turn --workflow
|
|
19
|
-
|
|
20
|
-
# Limit a resumed Claude Code session to its latest task and subagents.
|
|
21
|
-
traces analyze --harness claude-code --session <path> --latest-turn
|
|
22
|
-
|
|
23
|
-
# Write reusable findings, evidence, report, and OpenInference spans.
|
|
24
|
-
traces improve --harness codex --last 5 --dir .traces/improvement
|
|
25
|
-
|
|
26
|
-
# Add the built-in model-assisted analysts with a spending limit.
|
|
27
|
-
traces analyze --harness codex --last 5 --llm --budget 0.50
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Without `--workflow`, each selected Codex file is analyzed independently.
|
|
31
|
-
With it, `traces` follows stable parent and child session IDs, up to 100 files by default.
|
|
32
|
-
`--latest-turn` keeps a long-lived resumed Codex or Claude Code session scoped to its most recent task and the workers spawned from that task.
|
|
33
|
-
For Codex, when the selected session is a child, `traces` finds the last parent task that structurally spawned or targeted that stable child ID and reads only that task.
|
|
34
|
-
The Codex parent log must carry both the child ID and a stable turn ID on the relevant spawn, send, follow-up, or lifecycle event.
|
|
35
|
-
Without those fields, the result is explicitly incomplete; names, nicknames, and timestamps are never used as substitutes.
|
|
36
|
-
It reports missing files, duplicate IDs, contradictory parents, and cycles instead of guessing.
|
|
37
|
-
Use `--max-workflow-sessions <n>` to set a different bound.
|
|
38
|
-
Claude Code already folds nested subagent files into its parent trace.
|
|
39
|
-
The adapter preserves Claude source UUIDs in `traces.claude.source_*` attributes and derives valid OTLP IDs for the exported graph.
|
|
40
|
-
It emits tool spans from recorded tool calls, but does not invent loop iterations or causal links absent from the transcript.
|
|
41
|
-
Ordinary Claude subagents use their parent tool call ID.
|
|
42
|
-
Claude Workflow subagents use the run ID and transcript directory returned by the parent `Workflow` call.
|
|
43
|
-
If the same Workflow run is resumed, each child attaches to the latest matching call that started before it.
|
|
44
|
-
The selected task includes only children attached to its calls.
|
|
45
|
-
Returned directories from another resumed Claude session are parsed and included in source hashes.
|
|
46
|
-
|
|
47
|
-
`traces improve` writes:
|
|
48
|
-
|
|
49
|
-
```text
|
|
50
|
-
.traces/improvement/
|
|
51
|
-
evidence.jsonl
|
|
52
|
-
findings.json
|
|
53
|
-
report.md
|
|
54
|
-
result.json
|
|
55
|
-
traces.otlp.jsonl
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
The OpenInference file is the shared input for external engines.
|
|
59
|
-
The original trace and exact cited span remain available for review.
|
|
60
|
-
|
|
61
|
-
## Ask free-form questions
|
|
62
|
-
|
|
63
|
-
The built-in kinds ask fixed questions.
|
|
64
|
-
`traces ask` asks your own.
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
traces ask --harness codex --session <id> \
|
|
68
|
-
--question "Which shell commands exited non-zero?" \
|
|
69
|
-
--question "Which pull requests did the session open, and were they merged?" \
|
|
70
|
-
--dir .traces/ask
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Each question runs through agent-eval's `runTraceAnalyst` directly, not through the analyst registry.
|
|
74
|
-
That matters for three reasons.
|
|
75
|
-
|
|
76
|
-
- The registry keeps only findings, so it discards the engine's prose answer. `ask` keeps the answer text verbatim.
|
|
77
|
-
- The registry runs analysts one at a time. `ask` runs questions concurrently, so wall time falls toward the slowest question instead of the sum.
|
|
78
|
-
- One shared `CostLedger` bounds the whole run, so `--budget` means the same thing whatever the number of questions.
|
|
79
|
-
|
|
80
|
-
### Questions
|
|
81
|
-
|
|
82
|
-
A question stays short on purpose.
|
|
83
|
-
The engine shows the model a preview of each long input: it keeps the first 500 and last 500 characters and drops the middle.
|
|
84
|
-
A question inside the limit therefore reaches the model whole, and the answer rules sit in the first 500 characters of the instructions, which the preview always keeps.
|
|
85
|
-
`ask` rejects an over-long question before it starts a model call and names the limit.
|
|
86
|
-
Put the detail in the entry's `instructions` field instead, which the model reads after the rules.
|
|
87
|
-
|
|
88
|
-
Read many questions from a file:
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"questions": [
|
|
93
|
-
"What was the last thing the human asked for?",
|
|
94
|
-
{
|
|
95
|
-
"id": "failed-commands",
|
|
96
|
-
"question": "Which shell commands exited non-zero?",
|
|
97
|
-
"instructions": "Report the command line and the exit code. Ignore commands the agent only proposed.",
|
|
98
|
-
"answerSchema": {
|
|
99
|
-
"type": "array",
|
|
100
|
-
"items": {
|
|
101
|
-
"type": "object",
|
|
102
|
-
"properties": { "command": { "type": "string" }, "exit_code": { "type": "integer" } },
|
|
103
|
-
"required": ["command", "exit_code"]
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
An `answerSchema` makes the answer one JSON value a scorer can compare field by field.
|
|
112
|
-
The supported keywords are `type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`, `title`, and `description`.
|
|
113
|
-
A keyword that constrains one JSON type must declare it: `required`, `properties`, and `additionalProperties` need `"type": "object"`, and `items` needs `"type": "array"`, or the constraint would be skipped for an answer of another shape.
|
|
114
|
-
Any other keyword is rejected when the run starts.
|
|
115
|
-
This package carries no JSON Schema library, and a constraint that is quietly ignored would let a wrong answer pass as checked.
|
|
116
|
-
|
|
117
|
-
### What the run guarantees
|
|
118
|
-
|
|
119
|
-
- Every question receives the deterministic [session-facts sheet](#session-facts) as prepared context, before its first model call. It costs nothing and answers the counting questions the bounded trace tools cannot.
|
|
120
|
-
- Every `trace://<trace_id>/span/<span_id>` URI in an answer is resolved against the store. An unresolvable citation fails that question.
|
|
121
|
-
A citation the model wrapped in Markdown emphasis (`**...**`, `_..._`, `~~...~~`) or ended a sentence with resolves like a bare one: the delimiters are prose, not part of the span ID.
|
|
122
|
-
- Findings the answer submits still pass the same evidence gate as the built-in kinds. Refused findings are counted by reason in both artifacts.
|
|
123
|
-
- A failed question never stops the others. Its failure is recorded on its own answer, and the remaining answers are written.
|
|
124
|
-
- Ctrl-C keeps what the run already bought. The signal reaches the engine, not the checks that follow it: an answer that came back is kept with its citations resolved, and each question the run never reached is recorded as `aborted`.
|
|
125
|
-
- The artifacts are written before the exit code is decided. `ask` exits 1 when any question failed, returned no answer, broke its schema, or cited a missing span.
|
|
126
|
-
- `totals.wallTimeMs` covers the whole run, including writing and indexing the trace file; `totals.setupTimeMs` names that part. `result.effectiveConcurrency` is the number of workers the run created, `min(--concurrency, questions)`, and `totals.peakConcurrency` is how many actually overlapped.
|
|
127
|
-
- `--question-timeout <seconds>` is the wall-clock deadline for one question's investigation (default 600). A question over many large sessions can need longer; the timeout fails that question alone.
|
|
128
|
-
|
|
129
|
-
### Budget under concurrency
|
|
130
|
-
|
|
131
|
-
`--budget` is the ceiling shared by every question; `--question-budget` is the provider ceiling for one question.
|
|
132
|
-
The ledger reserves each model call's maximum charge before the call runs, and releases the unused part when the call settles.
|
|
133
|
-
Two consequences follow.
|
|
134
|
-
|
|
135
|
-
- A budget below one call's reservation refuses the run before any model call, rather than failing every question.
|
|
136
|
-
- A budget that admits fewer concurrent reservations than `--concurrency` still runs, and the report carries a warning naming how many concurrent calls it covers.
|
|
137
|
-
|
|
138
|
-
A question the ledger refuses is reported as `budget-refused`, and the answers already produced are kept.
|
|
139
|
-
That kind is decided from the accounting, not only from the error text.
|
|
140
|
-
The refusal happens inside the model proxy, behind the DSPy bridge, whose HTTP error handling can replace the Node error with its own message.
|
|
141
|
-
So a failed question is reported as `budget-refused` whenever the shared ledger's settled spend left less than one model call's reservation at the time it failed and nothing else in the failure names its cause.
|
|
142
|
-
A failure whose own text names its cause keeps that cause: a bridge version mismatch stays `error`, so the reinstall hint still prints, and an empty answer from the bridge stays `no-answer`.
|
|
143
|
-
The message itself is kept verbatim on the answer.
|
|
144
|
-
|
|
145
|
-
### SDK
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
import { analysisEngineFromEnv, runTraceQuestions, writeTraceQuestionsArtifacts } from '@tangle-network/traces'
|
|
149
|
-
|
|
150
|
-
const result = await runTraceQuestions({
|
|
151
|
-
questions: [
|
|
152
|
-
{ id: 'failed-commands', question: 'Which shell commands exited non-zero?' },
|
|
153
|
-
{ id: 'last-ask', question: 'What was the last thing the human asked for?' },
|
|
154
|
-
],
|
|
155
|
-
spans,
|
|
156
|
-
engine: analysisEngineFromEnv({ model: 'gpt-5.6-luna', maxCostUsd: 1 }),
|
|
157
|
-
concurrency: 4,
|
|
158
|
-
budgetUsd: 2,
|
|
159
|
-
})
|
|
160
|
-
await writeTraceQuestionsArtifacts(result, '.traces/ask')
|
|
161
|
-
if (!result.ok) process.exitCode = 1
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
`result.questions[i].answer` is the engine's prose, unedited.
|
|
165
|
-
`result.totals` carries the wall time, the summed question time, the peak concurrency, and the cost with its provenance.
|
|
166
|
-
|
|
167
|
-
## Session facts
|
|
168
|
-
|
|
169
|
-
`traces facts` computes a fixed set of session facts straight from the spans.
|
|
170
|
-
It runs no model, opens no engine, and spends nothing.
|
|
171
|
-
The same spans always produce the same sheet.
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
traces facts --harness codex --session <id> # JSON on stdout
|
|
175
|
-
traces facts --harness codex --last 5 --format text # the short readable form
|
|
176
|
-
traces facts --otlp spans.otlp.jsonl --out facts.json
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Why it exists
|
|
180
|
-
|
|
181
|
-
The trace tools are bounded, and above their bounds they answer a different question than the one asked.
|
|
182
|
-
`viewTrace` returns a `≤20`-entry span-name histogram once a trace exceeds `perCallByteCeiling` (150,000 bytes).
|
|
183
|
-
`countTraces` counts traces, not spans.
|
|
184
|
-
`viewSpans` needs span ids the reader does not have yet.
|
|
185
|
-
`searchTrace` stops at 500 hits.
|
|
186
|
-
|
|
187
|
-
A model asked "how many tool calls ran?" therefore adds up a capped histogram and decides by eye which names belong.
|
|
188
|
-
Measured over twelve private audit sessions, the model-backed analyst arm scored a deterministic mean of **0.389**.
|
|
189
|
-
Extracting the same answers mechanically from the OTLP spans those runs already wrote scored **0.858** — the facts were present and exact the whole time.
|
|
190
|
-
The sheet is that extraction, made part of the tool.
|
|
191
|
-
|
|
192
|
-
### What it states
|
|
193
|
-
|
|
194
|
-
| Field | Value |
|
|
195
|
-
| --- | --- |
|
|
196
|
-
| `toolCalls` | TOOL spans **this session's agent** invoked. Synthesized lifecycle spans are excluded and counted in `synthesizedToolSpans`; calls made by a spawned subagent are excluded and counted in `subagentToolSpans` |
|
|
197
|
-
| `toolCallsByName` | the same calls by tool name |
|
|
198
|
-
| `subagentToolSpans` | TOOL spans excluded because a subagent, not this agent, made the call |
|
|
199
|
-
| `subagents` | every subagent-spawning call, with the task name the call recorded |
|
|
200
|
-
| `pullRequests` | pull requests the commands created and merged, each named by number or head branch, with the command span and the join evidence |
|
|
201
|
-
| `cwd` | the working directory the harness recorded for the session, or `null` with the reason |
|
|
202
|
-
| `humanTurns` | `user.prompt` turns a person typed into this session, in order, with timestamps, each with the agent message it `answered` |
|
|
203
|
-
| `excludedTurns` | every `user.prompt` turn `humanTurns` left out, grouped by the reason, with the span ids |
|
|
204
|
-
| `turnsByActor` | every `user.prompt` turn by actor, so the human filter is checkable |
|
|
205
|
-
| `finalMessages` | the last message of the session's own agent, and of each subagent task, separately |
|
|
206
|
-
| `changedFiles` | paths the harness recorded for its own file changes, plus paths from `*** Add/Update/Delete/Move to File:` patch headers and from file-editing tool arguments |
|
|
207
|
-
| `firstRecordAt`, `lastRecordAt` | the earliest span start and latest span end among this session's own records |
|
|
208
|
-
| `unreadRecords` | records the session reader could not parse |
|
|
209
|
-
| `tokenTotal` | the harness's cumulative total, when a span carries `traces.session.total_tokens` |
|
|
210
|
-
|
|
211
|
-
Three rules hold for every field.
|
|
212
|
-
|
|
213
|
-
- **Every fact is about this session's own agent.** Some harnesses fold a spawned agent's transcript into the parent's trace — Claude Code writes one file per subagent under the session directory and the adapter reads them all — so a trace can carry both what this agent did and what a child it spawned did. The child's work is named in `subagents` and counted in `subagentToolSpans`, never added to the parent's totals. On a trace with no subagent records every one of those counts is zero.
|
|
214
|
-
- **Every fact names the span ids it came from.** A reader can open those spans and check the number. The sheet is not a span and cannot be cited.
|
|
215
|
-
- **A fact the spans cannot support is `null` with a stated reason.** It is never guessed and never a silent zero. `partial` marks a measured value known to be incomplete.
|
|
216
|
-
|
|
217
|
-
`facts` exits non-zero when a selected session produced no record spans, rather than printing a sheet of zeros for a session it could not read.
|
|
218
|
-
|
|
219
|
-
#### Pull requests
|
|
220
|
-
|
|
221
|
-
The command spans carry the script, the exit code and the output.
|
|
222
|
-
`pullRequests` scans each script the way a shell would, so a `gh pr create` inside a heredoc body or a commit message is not counted as a command that ran.
|
|
223
|
-
A pull request is named by its number when the command or an output that joins to it shows one, and by its head branch when neither does.
|
|
224
|
-
A create whose stdout was redirected away takes the number a later output states for the same branch on the same line, and names the span that stated it.
|
|
225
|
-
A failed `git push && gh pr create` counts only when the output shows `gh` itself answering; otherwise the shell never reached it.
|
|
226
|
-
A trace whose spans carry no executed command returns `null` with that reason, because "no pull requests" and "the spans cannot say" are different answers.
|
|
227
|
-
|
|
228
|
-
#### Human turns
|
|
229
|
-
|
|
230
|
-
A user message is one the human typed into this session.
|
|
231
|
-
Three filters run in order, and each excluded span is listed in `excludedTurns` with its reason, never silently dropped:
|
|
232
|
-
|
|
233
|
-
- history the session carries but did not receive — the prefix a fork copies from its parent, and the turns a compaction replays;
|
|
234
|
-
- turns whose actor is not a person — an instruction file, an environment-context block, a system reminder, a subagent notification, a turn-aborted marker, or a skill or slash-command expansion;
|
|
235
|
-
- a second record of the turn before it, meaning the same text at the same instant with no model call, tool call or assistant message between them.
|
|
236
|
-
|
|
237
|
-
For Codex the actor comes from the harness's own per-item labelling (`internal_chat_message_metadata_passthrough.content_item_kinds`) whenever the record carries it: a message is the person's exactly when every item in it is a `user.` kind.
|
|
238
|
-
|
|
239
|
-
### Session facts as prepared context
|
|
240
|
-
|
|
241
|
-
The same sheet is supplied to the model-backed analysts before their first model call, through `TraceAnalystDefinition.prepareContext`.
|
|
242
|
-
It reaches the built-in kinds run by `analyze --llm`, `investigate`, and `improve`, and it reaches every `traces ask` question inside `PREPARED CONTEXT:`.
|
|
243
|
-
|
|
244
|
-
- The sheet is bounded at `PREPARED_CONTEXT_BYTE_CEILING` (30,000 bytes), a fifth of the `perCallByteCeiling` of 150,000 the trace tools work to, so the rest of the budget stays available for the tool calls the model still makes.
|
|
245
|
-
- When the sheet does not fit, fields are shed from the largest downward and the shed is listed in `omitted_fields`. The tool-call counts are the last facts to go.
|
|
246
|
-
- Receiving the sheet changes an analyst's behavior, so its version carries `SESSION_FACTS_VERSION_SUFFIX` (`+session-facts.` and the sheet's `measurementVersion`). `createTraceAnalyst` records `prepare_context` in the exact-run identity, and a changed prepared context must not hide behind an unchanged version.
|
|
247
|
-
- The sheet is not evidence. Citations still resolve against the raw spans, which is why every fact names its span ids rather than asking the model to trust the sheet.
|
|
248
|
-
|
|
249
|
-
Pass `sessionFactsContext: false` to `analyzeSpans` to run an analyst without it.
|
|
250
|
-
|
|
251
|
-
```ts
|
|
252
|
-
import { buildSessionFactsReport, computeSessionFacts, renderSessionFacts } from '@tangle-network/traces'
|
|
253
|
-
|
|
254
|
-
const [facts] = computeSessionFacts(spans)
|
|
255
|
-
console.log(facts.toolCalls.value, facts.toolCalls.spanIds)
|
|
256
|
-
console.log(renderSessionFacts(buildSessionFactsReport(spans)))
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Evidence-gate rejections
|
|
260
|
-
|
|
261
|
-
A model-backed analyst can submit a finding the evidence gate then refuses.
|
|
262
|
-
Without the reason, a report showing "0 findings" reads as "the model found nothing" when the truth may be "the gate refused everything it found".
|
|
263
|
-
|
|
264
|
-
`analyze`, `investigate`, `improve`, and `ask` now carry those refusals:
|
|
265
|
-
|
|
266
|
-
- the CLI log adds whatever the event carries beyond its own text to each `finding rejected` line: the cause (`reason` for the gate's rejections and the bridge-row rejection, `issues` for a schema failure), the offending URI, the citation counts, and the subject;
|
|
267
|
-
- the analyst table's Detail cell names the reasons and their counts;
|
|
268
|
-
- `result.findingRejections` (investigation and improvement) and `answers.json` (`ask`) hold the counts per analyst and reason.
|
|
269
|
-
|
|
270
|
-
The common reasons are an excerpt the cited span does not contain, a span the trace does not hold, and too few distinct citations for the kind.
|
|
271
|
-
|
|
272
|
-
## External analyzer failures exit non-zero
|
|
273
|
-
|
|
274
|
-
`--analyzer halo|hodoscope|prime|<command>` promises that engine's output.
|
|
275
|
-
An analyzer that fails now writes its error into the report as before, and then the command exits 1 naming every analyzer that failed.
|
|
276
|
-
Scripts that treated exit 0 as "the analyzer ran" were reading a report that said otherwise.
|
|
277
|
-
|
|
278
|
-
The check runs on `analyze`, `investigate`, and `improve`, and it covers every analyzer the run requested, not only the ones named on the command line.
|
|
279
|
-
`investigate` and `improve` load the default traces config file, so an analyzer declared in `externalAnalyzers` there is a requested analyzer too, and a flaky one now turns those two commands red.
|
|
280
|
-
`analyze` does not load a default config, so only its own `--analyzer` flags reach the check.
|
|
281
|
-
|
|
282
|
-
## Codex tool outcomes
|
|
283
|
-
|
|
284
|
-
Both function and custom tool outputs use the same status parser.
|
|
285
|
-
Explicit exit codes, error flags, and runner headers determine tool status.
|
|
286
|
-
Explicit failure takes precedence when structured fields conflict.
|
|
287
|
-
Text such as `error:`, `ENOENT`, or `command failed` inside stdout does not determine status.
|
|
288
|
-
An unmatched call, running process, or output without trustworthy status remains `UNSET`.
|
|
289
|
-
A recorded `wait_agent` timeout is a completed poll, not proof that the agent finished.
|
|
290
|
-
Tool output remains in `output.value`, with the existing size limit and truncation receipt.
|
|
291
|
-
|
|
292
|
-
Error and retry counts use explicit failures.
|
|
293
|
-
Successful follow-up counts require explicit success; unknown follow-up outcomes remain `null` in the detailed report.
|
|
294
|
-
The runtime span projection leaves its optional status absent for `UNSET` spans.
|
|
295
|
-
Use the execution report for terminal run outcomes.
|
|
296
|
-
The older runtime store's required run status cannot represent unknown and is not completion evidence.
|
|
297
|
-
|
|
298
|
-
## Model-assisted citations
|
|
299
|
-
|
|
300
|
-
agent-eval's evidence gate accepts a model finding only when each cited span exists and each excerpt appears in that span.
|
|
301
|
-
Before the gate runs, `traces` normalizes the citations of the built-in `--llm` analysts against the analyzed spans.
|
|
302
|
-
|
|
303
|
-
- An excerpt copied from a `searchTrace` hit carries JSON escapes such as `\"` and `\n`.
|
|
304
|
-
It is replaced by its decoded form only when the cited span contains that decoded text.
|
|
305
|
-
- A citation of a readable harness ID, such as `tool:<call_id>` or the Codex session ID, is replaced by the hex OTLP span ID.
|
|
306
|
-
The readable ID must name exactly one span through its `traces.<harness>.source_*` attributes.
|
|
307
|
-
- A subject from another analyst's vocabulary is removed, because the subject is optional.
|
|
308
|
-
The claim and its citations still go through the gate.
|
|
309
|
-
|
|
310
|
-
The gate itself is unchanged and checks every rewritten finding.
|
|
311
|
-
A fabricated excerpt, an unknown ID, or an ambiguous ID remains rejected.
|
|
312
|
-
Normalization never adds a citation, so a failure-mode finding still needs two distinct spans.
|
|
313
|
-
A subject outside the subject grammar is rejected before normalization runs.
|
|
314
|
-
Wrapped analysts report a version ending in `+traces-citations-1`.
|
|
315
|
-
|
|
316
|
-
A custom analyst built with `createTraceAnalyst` gets the same behavior through `normalizeAnalystCitations(definitions, spans)`.
|
|
317
|
-
Pass the spans that the analyst's trace store was written from.
|
|
318
|
-
|
|
319
|
-
## External engines
|
|
320
|
-
|
|
321
|
-
External engines are optional tools that you install separately.
|
|
322
|
-
One engine failing does not discard the other results.
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
|
-
# Recursive diagnosis over the same OpenInference trace.
|
|
326
|
-
traces analyze --last 1 --analyzer halo --analyzer-prompt "find unsupported completion claims"
|
|
327
|
-
|
|
328
|
-
# Sample distinct actions across a larger set of sessions.
|
|
329
|
-
traces analyze --all --last 20 --analyzer hodoscope
|
|
330
|
-
|
|
331
|
-
# Run any installed command that accepts an OpenInference file path.
|
|
332
|
-
traces analyze --last 1 --analyzer my-trace-tool
|
|
333
|
-
|
|
334
|
-
# One-shot prime-RLM analysis through a local OpenAI-compatible bridge.
|
|
335
|
-
traces analyze --last 1 --analyzer prime --analyzer-prompt "find unsupported completion claims"
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
`--model` is forwarded to the built-in model-assisted analysts, HALO, Hodoscope, and prime.
|
|
339
|
-
HALO and Hodoscope use their own provider clients and credentials.
|
|
340
|
-
The Hodoscope adapter pins version `0.2.4` and uses Python 3.11 through `uvx`.
|
|
341
|
-
|
|
342
|
-
External results have one of three explicit kinds:
|
|
343
|
-
|
|
344
|
-
```ts
|
|
345
|
-
type ExternalAnalysisResult =
|
|
346
|
-
| { kind: 'report'; output: string }
|
|
347
|
-
| { kind: 'findings'; findings: AnalystFinding[] }
|
|
348
|
-
| { kind: 'discovery'; candidates: ExternalDiscoveryCandidate[] }
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
Raw JSON is still a `report`.
|
|
352
|
-
Only an adapter that validates the full finding shape may return `findings`.
|
|
353
|
-
Hodoscope always returns `discovery`, and each candidate has `status: 'needs_review'` plus its source trace and span.
|
|
354
|
-
|
|
355
|
-
### Prime engine
|
|
356
|
-
|
|
357
|
-
`--analyzer prime` runs a one-shot analyst over the emitted OTLP artifact through an OpenAI-compatible bridge, such as cli-bridge's prime backend.
|
|
358
|
-
Unlike the `--llm` analysts, which drill into the trace with paged tools, prime is a recursive language model with its own REPL, so the trajectory reaches it one of two ways:
|
|
359
|
-
|
|
360
|
-
- **Inline (small traces):** the full span projection is inlined into a single prompt as JSON. Oversized projections are re-rendered with a per-attribute character cap.
|
|
361
|
-
- **File (large traces):** when even the capped projection exceeds the inline budget, the artifact is copied into a per-run directory, that directory is sent as `cwd` on the chat-completions request (cli-bridge starts prime there), and the prompt tells prime to load `trajectory.otlp.jsonl` itself and fan out with `rlm()` — so the trajectory travels as a readable file, not prompt bytes, and no trace is refused for size. The bridge must be able to read that directory: host-mode bridges do by construction; a jailed or remote bridge needs the directory registered or uploaded on the bridge side.
|
|
362
|
-
|
|
363
|
-
Span-id grounding is identical in both modes: every cited id is validated against the artifact, and the delivery decision is recorded in the result output.
|
|
364
|
-
|
|
365
|
-
Prerequisites:
|
|
366
|
-
|
|
367
|
-
- A running bridge that accepts `POST /v1/chat/completions` and routes the configured model to the prime backend.
|
|
368
|
-
- No key handling here: the bridge owns provider credentials.
|
|
369
|
-
|
|
370
|
-
Configuration (flags first, then environment, then defaults):
|
|
371
|
-
|
|
372
|
-
| Setting | Source | Default |
|
|
373
|
-
| --- | --- | --- |
|
|
374
|
-
| Bridge root URL | `TRACES_PRIME_BRIDGE_URL` | `http://localhost:4181` |
|
|
375
|
-
| Model | `--model`, then `TRACES_PRIME_MODEL` | `prime/zai/glm-5.2` |
|
|
376
|
-
| Per-call deadline | `TRACES_PRIME_TIMEOUT_MS` | `1200000` (20 min) |
|
|
377
|
-
| Question | `--analyzer-prompt` | a general diagnosis question |
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
traces analyze --last 1 --analyzer prime
|
|
381
|
-
TRACES_PRIME_BRIDGE_URL=http://localhost:4181 traces analyze --last 1 --analyzer prime --analyzer-prompt "find unsupported completion claims"
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
Output expectations:
|
|
385
|
-
|
|
386
|
-
- The reply contract is one fenced JSON block of short strings citing span ids verbatim; a structurally malformed reply gets exactly one bounded repair turn that carries the malformed reply and the contract, never the trajectory.
|
|
387
|
-
- A still-malformed reply after repair is a failed result (`ok: false`) with the raw reply preserved for inspection; one failed engine never discards the other results.
|
|
388
|
-
- Valid rows become full `findings` with `trace://` span evidence, validated against the artifact like every other findings-kind engine; rows citing unknown or ambiguous span ids are rejected with a recorded reason.
|
|
389
|
-
- Zero findings from a well-formed reply is an honest null, not a failure.
|
|
390
|
-
- Bridge-reported token usage and call counts are recorded in the result output; cost stays uncaptured because this adapter has no pricing table.
|
|
391
|
-
|
|
392
|
-
The scored prime-vs-dspy comparison — same trajectories, same scoring — lives in `@tangle-network/agent-eval`'s analyst benchmark (`runAnalystBenchmark`); this engine is the capture-side entry point, not the scoreboard.
|
|
393
|
-
|
|
394
|
-
### Verifying against a real prime
|
|
395
|
-
|
|
396
|
-
The suite above runs against a scripted bridge over real HTTP. A full real run — real
|
|
397
|
-
prime-agent, real model, real REPL — is a manual, credentials-on-the-host affair:
|
|
398
|
-
|
|
399
|
-
1. **Install prime-agent the way cli-bridge pins it.** It is a hard fork of pi that reuses
|
|
400
|
-
pi's npm name, so `npm i` gives you the WRONG agent: clone
|
|
401
|
-
`PrimeIntellect-ai/prime-agent`, `git checkout <the commit src/backends/prime.ts names>`
|
|
402
|
-
(be9e2fa0 at the time of writing), build, and expose the bundle as `prime-agent`
|
|
403
|
-
(cli-bridge's `pnpm install:harness prime` does exactly this).
|
|
404
|
-
2. **Start cli-bridge with the prime backend**: `BRIDGE_BACKENDS=prime
|
|
405
|
-
PRIME_MODELS_JSON=<operator models.json> BRIDGE_PORT=4181 pnpm start`. The
|
|
406
|
-
models.json names an `apiKey` as an exported ENV VAR (not a literal — prime
|
|
407
|
-
resolves env-var-first, so an unforwarded name becomes the literal key), and the
|
|
408
|
-
bridge forwards exactly the names the file names into prime's neutral child env.
|
|
409
|
-
Mind which endpoint a key is funded on: a coding-plan GLM key only works on the
|
|
410
|
-
coding endpoint, and the same key 402s on the general one.
|
|
411
|
-
3. **Give the kernel a persistent interpreter.** Prime's Python kernel bootstraps a
|
|
412
|
-
venv under its HOME, and the bridge gives every run an isolated one — a
|
|
413
|
-
uv-managed interpreter installed inside an ephemeral HOME dies with it. Pre-provision
|
|
414
|
-
a venv (ipykernel + the default rlm extras + an editable
|
|
415
|
-
`prime-agent-runtime`) from a system Python and export
|
|
416
|
-
`PRIME_AGENT_KERNEL_PYTHON=<that venv's python>` to the bridge; prime skips
|
|
417
|
-
auto-bootstrap when the named interpreter already passes its checks.
|
|
418
|
-
4. Run with `TRACES_PRIME_BRIDGE_URL=http://localhost:4181 TRACES_PRIME_MODEL=prime/<provider>/<model>`.
|
|
419
|
-
The opt-in suite (`TRACES_PRIME_REAL_E2E=1` in `tests/analyst-engine-prime.test.ts`)
|
|
420
|
-
exercises the same path from vitest.
|
|
421
|
-
|
|
422
|
-
What real runs have shown (GLM-5.3 via a coding-plan endpoint, fork at the pinned commit):
|
|
423
|
-
|
|
424
|
-
- **Inline, small session:** clean single-call success — one fenced JSON block, five findings,
|
|
425
|
-
all grounded, exit 0.
|
|
426
|
-
- **File, ~121 spans / 788 KB artifact:** the RLM loads and pages the file (14 calls, ~156k input
|
|
427
|
-
tokens); its first reply gets cut by the endpoint's output limit mid-JSON, and the
|
|
428
|
-
truncation-aware repair turn (re-emit at most 3 findings, terse strings) recovers it — ok:true,
|
|
429
|
-
findings grounded, exit 0.
|
|
430
|
-
- **File, ~794 spans / 2.1 MB artifact:** the reply channel alone never survived the endpoint's
|
|
431
|
-
output cap. The findings.json payload channel finishes it — the analyzer writes the result with
|
|
432
|
-
its tools before replying — measured live at **8 findings, 0 rejected, exit 0**, 53 model calls,
|
|
433
|
-
~3.1M input tokens of REPL inspection.
|
|
434
|
-
|
|
435
|
-
## Write one analyst
|
|
436
|
-
|
|
437
|
-
An analyst receives a paged trace store and returns typed findings.
|
|
438
|
-
Start with deterministic logic when the trace contains enough facts.
|
|
439
|
-
|
|
440
|
-
```ts
|
|
441
|
-
import {
|
|
442
|
-
buildDefaultAnalystRegistry,
|
|
443
|
-
makeFinding,
|
|
444
|
-
} from '@tangle-network/traces'
|
|
445
|
-
import { defineTraceAnalyst } from '@tangle-network/agent-eval/analyst'
|
|
446
|
-
|
|
447
|
-
const failedTools = defineTraceAnalyst({
|
|
448
|
-
id: 'failed-tools',
|
|
449
|
-
description: 'Reports repeated tool failure signatures.',
|
|
450
|
-
async analyze(store) {
|
|
451
|
-
const overview = await store.getOverview({ has_errors: true })
|
|
452
|
-
return overview.error_clusters.map((cluster) => makeFinding({
|
|
453
|
-
analyst_id: 'failed-tools',
|
|
454
|
-
area: 'tool-use',
|
|
455
|
-
subject: cluster.signature,
|
|
456
|
-
claim: `${cluster.span_count} spans share this failure`,
|
|
457
|
-
severity: cluster.span_count >= 3 ? 'high' : 'medium',
|
|
458
|
-
evidence_refs: [{
|
|
459
|
-
kind: 'span',
|
|
460
|
-
uri: `trace://${cluster.exemplar_trace_ids[0]}/span/${cluster.exemplar_span_ids[0]}`,
|
|
461
|
-
}],
|
|
462
|
-
recommended_action: 'Fix the operation or change its retry policy.',
|
|
463
|
-
validation_plan: 'Rerun the task and confirm the signature is absent.',
|
|
464
|
-
confidence: 1,
|
|
465
|
-
id_basis: cluster.signature,
|
|
466
|
-
}))
|
|
467
|
-
},
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
const registry = buildDefaultAnalystRegistry()
|
|
471
|
-
registry.register(failedTools)
|
|
472
|
-
|
|
473
|
-
export default { registry }
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
Run it without another wrapper:
|
|
477
|
-
|
|
478
|
-
```bash
|
|
479
|
-
traces improve --last 5 --config traces.config.mjs --dir .traces/improvement
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
The full runnable example is [`examples/custom-analyst.ts`](../examples/custom-analyst.ts).
|
|
483
|
-
|
|
484
|
-
Run its fixed input to inspect the complete JSON:
|
|
485
|
-
|
|
486
|
-
```bash
|
|
487
|
-
pnpm tsx examples/custom-analyst.ts --fixture
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
The current fixture returns one finding.
|
|
491
|
-
Its stable fields are:
|
|
492
|
-
|
|
493
|
-
```json
|
|
494
|
-
{
|
|
495
|
-
"analyst_id": "failed-tool-clusters",
|
|
496
|
-
"area": "tool-use",
|
|
497
|
-
"claim": "3 failed span(s) share the error: Command failed with exit code 127 on attempt 1",
|
|
498
|
-
"severity": "high",
|
|
499
|
-
"evidence_refs": [
|
|
500
|
-
{
|
|
501
|
-
"kind": "span",
|
|
502
|
-
"uri": "trace://fixture-three-failures/span/failed-1"
|
|
503
|
-
}
|
|
504
|
-
],
|
|
505
|
-
"recommended_action": "Fix or change the retry policy for exec.",
|
|
506
|
-
"validation_plan": "Rerun the same task and confirm this error signature is absent.",
|
|
507
|
-
"confidence": 1
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
`pnpm tsx examples/custom-analyst.ts --good-fixture` returns `[]`.
|
|
512
|
-
|
|
513
|
-
## Test an analyst
|
|
514
|
-
|
|
515
|
-
A useful analyst must beat a trivial no-findings baseline on labeled trajectories.
|
|
516
|
-
It must also avoid inventing issues on clean trajectories.
|
|
517
|
-
|
|
518
|
-
`agent-eval` provides adapters for two public datasets:
|
|
519
|
-
|
|
520
|
-
| Dataset | Labels used |
|
|
521
|
-
| --- | --- |
|
|
522
|
-
| AgentRx | Failure category, failed step, and root-cause step |
|
|
523
|
-
| CodeTraceBench | Incorrect and unhelpful action steps; solved label-empty rows are clean controls |
|
|
524
|
-
|
|
525
|
-
Use `runAnalystBenchmark` to report recall, precision, F1, root-step accuracy, citation coverage, citation validity, clean-case false positives, repeat agreement, latency, calls, tokens, and known cost.
|
|
526
|
-
Use `compareAnalystRunners` for paired baseline and candidate comparisons.
|
|
527
|
-
Failed label-empty CodeTraceBench rows are unlabeled, not clean controls.
|
|
528
|
-
|
|
529
|
-
```ts
|
|
530
|
-
import {
|
|
531
|
-
codeTraceBenchCase,
|
|
532
|
-
registryBenchmarkRunner,
|
|
533
|
-
renderAnalystBenchmarkMarkdown,
|
|
534
|
-
runAnalystBenchmark,
|
|
535
|
-
} from '@tangle-network/agent-eval/analyst'
|
|
536
|
-
import { otlpTextToTraceAnalysisStore } from '@tangle-network/agent-eval/traces'
|
|
537
|
-
import { chatTrajectoryToSpans, serializeSpans } from '@tangle-network/traces'
|
|
538
|
-
|
|
539
|
-
const cases = rows.map(({ label, trajectory }) => {
|
|
540
|
-
const spans = chatTrajectoryToSpans(trajectory, { traceId: label.traj_id })
|
|
541
|
-
const traceStore = otlpTextToTraceAnalysisStore(serializeSpans(spans))
|
|
542
|
-
return codeTraceBenchCase(label, { traceStore })
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
const result = await runAnalystBenchmark({
|
|
546
|
-
cases,
|
|
547
|
-
runners: [registryBenchmarkRunner({ id: 'candidate', registry })],
|
|
548
|
-
repetitions: 3,
|
|
549
|
-
maxConcurrency: 4,
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
console.log(renderAnalystBenchmarkMarkdown(result))
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
Public labels test the measurement code, not the quality of every built-in analyst automatically.
|
|
556
|
-
A real quality claim requires running the analyst over the corresponding trajectories, retaining all rows, and comparing it with named alternatives at equal model and request limits.
|
|
557
|
-
|
|
558
|
-
## Verified findings (executed replay)
|
|
559
|
-
|
|
560
|
-
An analyst finding is a cited claim until something executes it.
|
|
561
|
-
`traces analyze --verify-findings` (and the standalone `traces verify-findings`) replays each finding's trajectory prefix in a real sandbox, re-runs the accused step, and annotates every finding with an executed verdict:
|
|
562
|
-
|
|
563
|
-
| Verdict | Meaning |
|
|
564
|
-
|---|---|
|
|
565
|
-
| `reproduced` | the recorded failure signature (returncode + stable output substring) reproduced when the accused step re-ran |
|
|
566
|
-
| `fix-flipped` | reproduced, and a supplied corrected command made the failure vanish in a fresh replay |
|
|
567
|
-
| `divergent` | the step executed but the recorded failure did not reproduce — evidence against the finding, or against replay fidelity (the receipt carries prefix divergences so you can tell which) |
|
|
568
|
-
| `not-replayable` | the finding could not be executed; the receipt names the precise reason (no step subject, unknown trajectory, no docker image, submit step, …) |
|
|
569
|
-
|
|
570
|
-
```bash
|
|
571
|
-
# Verify the findings an analyze run produced (marks each finding in the report):
|
|
572
|
-
traces analyze --last 1 --llm --verify-findings \
|
|
573
|
-
--replay-corpus holdout=labels.json::prepared/ --verify-out ./receipts
|
|
574
|
-
|
|
575
|
-
# Verify findings recorded earlier (e.g. extracted from an eval result.json):
|
|
576
|
-
traces verify-findings --findings findings.json --out ./receipts \
|
|
577
|
-
--steps normalized/<traj>/steps.json --image <replay-ready-image> --cwd /app
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
Findings are matched by the shape analysts emit: subject `incorrect-step-<n>` (or the wire form `incorrect-steps-<f>-<l>-…`), `metadata.block_first_step`, and `trace://<trajectory>/…` evidence refs.
|
|
581
|
-
Findings accusing the same step share one executed proof; each finding still gets its own receipt directory (`receipt.json` plus, when executed, `replay-verdict.json` and `report.md` with real stdout/stderr).
|
|
582
|
-
Verification is execution, not generation: no LLM is involved unless you pass `--fix-command`.
|
|
583
|
-
A missing sandbox is an error when any finding is replayable — verification never silently skips.
|
|
584
|
-
Sandbox setup, execution semantics, and honest limits are in [Replay verification](./replay-verify.md).
|
|
585
|
-
|
|
586
|
-
## Turn findings into improvement
|
|
587
|
-
|
|
588
|
-
Do not train or rewrite policy from an analyst's own prose alone.
|
|
589
|
-
Attach independent feedback or a measured task outcome to the trace first.
|
|
590
|
-
Then promote the reviewed failure into an eval case, make one targeted change, and compare before and after on fresh cases.
|
|
591
|
-
|
|
592
|
-
The required review path is:
|
|
593
|
-
|
|
594
|
-
```text
|
|
595
|
-
production trace -> finding -> reviewed feedback -> eval case -> candidate change -> comparison
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
## Read retained source fields
|
|
599
|
-
|
|
600
|
-
Normalized conversation text and tool values remain capped.
|
|
601
|
-
Create a full bundle before the harness rotates its original files.
|
|
602
|
-
Explicitly authorize that bundle when analysis needs omitted text:
|
|
603
|
-
|
|
604
|
-
```bash
|
|
605
|
-
traces bundle --session <session-id> --out ./session-bundle
|
|
606
|
-
traces analyze --source-bundle ./session-bundle --llm
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
`investigate` and `improve` accept the same flag.
|
|
610
|
-
The SDK accepts `sourceBundle: { path, maxRecordBytes? }` in analysis and investigation options.
|
|
611
|
-
This authorization exposes `traces.readSpanSource` to local analysts through the existing trace store.
|
|
612
|
-
External analyzers receive normalized OTLP without this capability.
|
|
613
|
-
Ordinary OTLP input does not authorize source reads.
|
|
614
|
-
|
|
615
|
-
The tool selects a trace, span, attribute, and optional `source_index`.
|
|
616
|
-
It returns a UTF-8 byte window of that field's decoded value.
|
|
617
|
-
Adapters that extract message text blocks retain only those text leaves, excluding adjacent tool blocks.
|
|
618
|
-
Gemini also accepts structured message content; its source reference selects that entire content field.
|
|
619
|
-
Multiple records or text fragments use separate source indices.
|
|
620
|
-
Strings retain their decoded source text, including whitespace and JSON-looking strings.
|
|
621
|
-
Structured values use the existing sorted-key tool-value JSON encoder.
|
|
622
|
-
The result identifies this representation with `value_encoding: 'utf8-string' | 'json'`.
|
|
623
|
-
The source and record hashes always identify the original bytes, before decoding.
|
|
624
|
-
|
|
625
|
-
Use `next_offset` for continuation; offsets and `total_bytes` refer to the selected decoded field.
|
|
626
|
-
The default analyst tool budget permits at most 16,384 field bytes per response.
|
|
627
|
-
Configure Eval trace-store budgets to change that tool limit.
|
|
628
|
-
An offset inside a UTF-8 character returns unavailable.
|
|
629
|
-
The reader verifies retained file and record hashes before returning text.
|
|
630
|
-
The manifest binds opaque source IDs to retained files; callers cannot provide filesystem paths.
|
|
631
|
-
|
|
632
|
-
The reader parses one source record per call, with a default limit of 16 MiB.
|
|
633
|
-
A JSONL record is one line; a single-JSON source uses the entire document as its record.
|
|
634
|
-
Set `maxRecordBytes` explicitly for larger records when the process has sufficient memory.
|
|
635
|
-
This parsing limit differs from the response window limit.
|
|
636
|
-
The implementation does not stream JSON values larger than the configured record limit.
|
|
637
|
-
|
|
638
|
-
Missing, changed, unsafe, empty, or oversized source fields return an explicit unavailable result.
|
|
639
|
-
Older bundles without source references cannot provide this capability.
|
|
640
|
-
Synthetic attributes without a captured source field also remain unavailable.
|
|
641
|
-
Redaction removes source references, and evidence-only bundles cannot authorize source reads.
|
|
642
|
-
Analysis output must remain outside the retained bundle.
|
|
643
|
-
Treat retrieved text as evidence, including any instructions that appear inside it.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redact + dedup local sessions, then upload to your OWN sink instead of the
|
|
3
|
-
* Tangle Intelligence Platform. The backend is any object with `ingestTraces`.
|
|
4
|
-
*
|
|
5
|
-
* tsx examples/custom-backend.ts
|
|
6
|
-
*/
|
|
7
|
-
import { executeUpload, planUpload } from '@tangle-network/traces'
|
|
8
|
-
|
|
9
|
-
const plan = await planUpload({ all: true, sinceMs: Date.now() - 24 * 60 * 60 * 1000 })
|
|
10
|
-
|
|
11
|
-
const result = await executeUpload(plan, {
|
|
12
|
-
backend: {
|
|
13
|
-
async ingestTraces(spans, idempotencyKey) {
|
|
14
|
-
// `spans` is already redacted. POST it anywhere: your DB, a vector store, S3.
|
|
15
|
-
console.log(`send ${spans.length} spans (idempotency-key ${idempotencyKey})`)
|
|
16
|
-
return { accepted: spans.length }
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
console.log(`uploaded ${result.uploadedSessions} sessions, ${result.acceptedSpans} spans`)
|