Sample 10% of agent runs and you keep about 10% of your reverted payments. The sampler decided before the transaction was even sent.
Head or tail?
| Head sampling | Tail sampling | |
|---|---|---|
| Decides | When the trace starts | After its spans arrive |
| Knows about errors? | No | Yes |
| Runs in | The SDK | A stateful component, e.g. the Collector |
| Cost | Cheap | Memory for every pending trace |
The sampling docs list the catch with head sampling: it can’t decide based on data in the entire trace, so you can’t make sure traces with errors are kept.
What does a 10% head sampler drop?
1,000 agent runs, every tenth payment reverts, sampled with the usual ParentBased + TraceIdRatioBased pair:
import { SpanStatusCode } from '@opentelemetry/api';import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';import { ParentBasedSampler, SimpleSpanProcessor, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
new NodeTracerProvider({ sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.1) }), // keep 10% of runs spanProcessors: [new SimpleSpanProcessor(exporter)],}).register();
for (let run = 0; run < 1000; run++) { tracer.startActiveSpan('invoke_agent treasury-agent', (agent) => { const confirm = tracer.startSpan('confirm 8453'); if (run % 10 === 0) confirm.setStatus({ code: SpanStatusCode.ERROR }); // every 10th payment reverts confirm.end(); agent.end(); });}Three runs:
| Run | Confirm spans kept | Reverted payments kept |
|---|---|---|
| 1 | 108 of 1,000 | 11 of 100 |
| 2 | 100 of 1,000 | 15 of 100 |
| 3 | 88 of 1,000 | 9 of 100 |
The confirm span follows the root’s decision. Raising its own priority would only produce orphan spans whose parents were dropped.
Keep the traces that moved money
Make the decision at the tail. With the Collector’s tail sampling processor, a trace is kept if any policy says so:
processors: tail_sampling: decision_wait: 30s policies: - name: errors type: status_code status_code: { status_codes: [ERROR] } - name: moved-money type: string_attribute string_attribute: { key: blockchain.operation.name, values: [send, payment] } - name: ten-percent-of-the-rest type: probabilistic probabilistic: { sampling_percentage: 10 }Every failed span and every trace with a transaction stays. Plain chat runs are sampled at 10%. The SDK must then export everything to the Collector, so keep head sampling at 100%.