Skip to content

Three Operations on Auto-Collected Data: The Recorder Pattern That Saves You $15/1M Tokens

April 2026 · Sanjay Krishna Anbalagan


A user asks: “Why was my loan rejected?”

Your agent has the data. Credit score, DTI ratio, employment history — all computed across 8 pipeline stages. The LLM needs to answer.

Without recorder operations: The LLM retrieves scattered logs. Makes 4 tool calls. Spends 4 reasoning steps connecting disconnected data points. Total: ~2,500 tokens, requires an expensive reasoning model ($15/1M tokens).

With recorder operations: The recorder already has the causal chain. One read. The LLM sees the connected trace. Total: ~200 tokens, any lightweight model works ($0.25/1M tokens).

That’s 60x cost reduction per question. At 10,000 questions/day, that’s $2,190/day in savings.


Every recorder in footprintjs collects data during the single DFS traversal — no post-processing, never stale. The consumer chooses how to read that data at query time:

1. Translate — “What happened at this step?”

Section titled “1. Translate — “What happened at this step?””

Per-step raw value. O(1) lookup by runtimeStageId.

import { QualityRecorder } from 'footprintjs/trace';
import { flowChart, FlowChartExecutor } from 'footprintjs';
const chart = flowChart<{ systemPrompt: string; response: string }>(
'Seed', (scope) => { scope.systemPrompt = 'Answer using the supplied facts'; }, 'seed',
)
.addFunction('CallLLM', (scope) => {
// A local response fixture; replace with your model call in an application.
scope.response = `${scope.systemPrompt}: unsupported claim`;
}, 'call-llm')
.build();
const quality = new QualityRecorder((_id, context) =>
context.keysWritten.includes('response')
? { score: 0.3, factors: ['demo quality check failed'] }
: { score: 1 },
);
const executor = new FlowChartExecutor(chart);
executor.attachScopeRecorder(quality);
await executor.run();
// What was the quality score at the response-writing step?
const lowest = quality.getLowest();
if (lowest) {
const entry = quality.getByKey(lowest.runtimeStageId);
console.log(entry?.score); // 0.3
}

Use case: Time-travel debugger. Click a stage in the UI → see its data. The explainable-ui slider calls getByKey() at each position.

ROI: Developer debugging time drops from “read 500 log lines” to “click the stage.” At $150/hr senior eng rate, even 10 minutes saved per debug session × 5 sessions/day = $125/day.

2. Accumulate — “What happened up to this point?”

Section titled “2. Accumulate — “What happened up to this point?””

Progressive running total filtered by a set of visible keys. For time-travel sliders where you scrub forward/backward.

// Quality score up to the slider position
const visibleKeys = new Set(['seed#0', 'classify#1', 'call-llm#2']);
const scoreUpTo = quality.accumulate(
(sum, entry) => sum + entry.score,
0,
visibleKeys,
);
// → 2.1 (sum of scores for first 3 steps)

Use case: Progressive metrics in the UI. As the slider moves, the dashboard updates: “Duration so far: 120ms, Reads: 5, Writes: 3, Quality: 0.7.”

ROI: Users understand performance characteristics without reading code. Support tickets drop because the UI shows what happened instead of requiring a human to explain it.

3. Aggregate — “What’s the grand total?”

Section titled “3. Aggregate — “What’s the grand total?””

Reduce all entries to a single value. For dashboards, SLA checks, billing.

// Overall pipeline quality
const overallScore = quality.aggregate(
(sum, entry) => sum + entry.score,
0,
) / quality.size;
// → 0.65
// Total cost across all LLM calls
const totalCost = costRecorder.aggregate(
(sum, entry) => sum + entry.cost,
0,
);
// → $0.0042

Use case: SLA monitoring. “Average quality this hour: 0.82. Cost per request: $0.004.” These numbers go to Grafana/Datadog dashboards.

ROI: You catch quality degradation before users report it. One prevented outage saves more than a year of infrastructure cost.


These three operations aren’t just a nice API. They’re the foundation for the backward causal chain — footprintjs’s answer to “why did quality drop?”

When qualityTrace() backtracks from a low-scoring step:

  • It uses Translate to read the quality at each step
  • It uses Accumulate to compute progressive quality up to the drop point
  • It uses Aggregate to compute the overall pipeline score

The KeyedStore<T> composition primitive provides all three operations. Own one as a field on your recorder — “one purpose per recorder” — and the built-ins (MetricRecorder, QualityRecorder) expose them too. Your custom recorders get them for free.

import { KeyedStore } from 'footprintjs/trace';
import type { EmitEvent, EmitRecorder } from 'footprintjs';
// A single-purpose recorder that owns a KeyedStore for storage:
class TokenRecorder implements EmitRecorder {
readonly id = 'tokens';
private readonly store = new KeyedStore<{ input: number; output: number }>();
onEmit(e: EmitEvent) {
const usage = e.payload;
if (e.name !== 'tokens' || typeof usage !== 'object' || usage === null) return;
if (!('input' in usage) || typeof usage.input !== 'number') return;
if (!('output' in usage) || typeof usage.output !== 'number') return;
this.store.set(e.runtimeStageId, { input: usage.input, output: usage.output });
}
// All three operations on auto-collected data:
getForStep(id: string) { return this.store.get(id); } // Translate
getTokensUpTo(keys: Set<string>) { return this.store.accumulate((s, e) => s + e.input + e.output, 0, keys); } // Accumulate
getTotal() { return this.store.aggregate((s, e) => s + e.input + e.output, 0); } // Aggregate
}

One interface. Three operations. Collected during traversal. Never stale.

In the next post, we’ll show how these operations power the backward causal chain — an algorithm that answers “why did quality drop at step 5?” by walking the dependency graph backward.