How the Telemetry Pipeline Tracks Agent Costs in Real Time Using OTLP Collector and UsageProvider
The telemetry pipeline tracks agent costs by binding an embedded OTLP collector to a loopback HTTP endpoint, decoding OpenTelemetry spans to accumulate token counts in a real-time cost ledger, and exposing the aggregated usage through the UsageProvider’s getAgentUsage method.
The chaitanyagiri/munder-difflin repository implements a privacy-first telemetry system that translates OpenTelemetry (OTEL) spans into precise dollar costs without transmitting data to external services. By combining an embedded OTLP collector with a UsageProvider interface, the system provides real-time visibility into per-agent token consumption and associated costs directly within the main process.
Architecture Overview: From Agent Spans to Cost Ledger
The pipeline operates as a closed loop inside the Electron main process. Agents emit standard OTEL spans, the TelemetryCollector ingests them via HTTP, and a cost ledger converts token deltas into USD values immediately.
Configuring the OTLP Endpoint in the Hive
The Hive class in [src/main/hive.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/hive.ts) instantiates the telemetry infrastructure when it spins up agent processes. It generates a loopback URL and injects the necessary environment variables so that the OTEL SDK inside each agent pushes spans to the internal collector rather than to the internet.
// src/main/hive.ts
process.env.OTEL_EXPORTER_OTLP_PROTOCOL = 'http/json';
process.env.OTEL_EXPORTER_OTLP_ENDPOINT = this._otelEndpoint; // loopback URL
These variables ensure that every span emitted by an agent is serialized as OTLP/JSON and POSTed to the embedded collector.
Receiving and Decoding OTLP Batches
The TelemetryCollector defined in [src/main/telemetry.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/telemetry.ts) runs an HTTP server that accepts OTLP JSON payloads. When a batch arrives, the collector decodes the spans, normalizes attribute key-value pairs, and routes the extracted metrics to the cost ledger. The core decoding logic is marked by the comment // ─── OTLP decode → normalize → accumulate ────────────────────────────────── near line 296.
// src/main/telemetry.ts
handleRequest(req: IncomingMessage, res: ServerResponse) {
const spans = otlpDecode(jsonBody); // Line 296 region
for (const span of spans) {
const data = normalizeSpan(span); // Flatten attributes
this._costLedger.record(data.agent_id, data.input_tokens, data.output_tokens);
}
res.writeHead(200).end();
}
Real-Time Cost Accumulation
After normalization, token counts are passed to the cost ledger implemented in [src/main/realtimePricing.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/realtimePricing.ts). This module applies model-specific pricing (e.g., per-token rates for Claude-2 or Claude-3) to compute a conservative USD upper bound. The ledger persists a running total per agent_id and periodically flushes state to cost-ledger.jsonl for crash recovery.
Querying Costs via the UsageProvider
Once token data is aggregated, the UsageProvider exposes a synchronous API for the rest of the application to retrieve live cost and breaker status.
The getAgentUsage Method
Located in [src/main/usage.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/usage.ts), the UsageProvider class maintains a map of per-agent ledgers. Its primary method, getAgentUsage(agentId), returns an AgentUsageSample containing input tokens, output tokens, calculated cost, and circuit-breaker state.
// src/main/usage.ts (lines 58-86 region)
export class UsageProvider {
getAgentUsage(agentId: string): AgentUsageSample | null {
const ledger = this._costLedger[agentId];
if (!ledger) return null;
return {
inputTokens: ledger.input,
outputTokens: ledger.output,
costUsd: ledger.usd,
breaker: ledger.breaker, // tripped if budget exceeded
};
}
}
IPC Bridge for Renderer Access
To make this data available to the React frontend, the main process registers the provider with the IPC bridge in [src/preload/index.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts). This exposes a secure channel telemetry:usage that the renderer can invoke without direct Node.js access.
// src/preload/index.ts (lines 1010-1015 region)
ipcMain.handle('telemetry:usage', async (_event, agentId: string) => {
return usageProvider.getAgentUsage(agentId);
});
Frontend Integration
The renderer consumes these IPC channels through React hooks to update the UI in real time.
React Hook for Live Telemetry
The [src/renderer/src/hooks/useTelemetry.ts](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useTelemetry.ts) hook subscribes to the telemetry:event stream and polls the UsageProvider whenever a new span is processed. This provides the cost HUD and fleet grid with instantaneous feedback on agent spending.
// src/renderer/src/hooks/useTelemetry.ts
export function useTelemetry(agentId: string) {
const [usage, setUsage] = useState<AgentUsageSample | null>(null);
useEffect(() => {
const unsubscribe = window.cth.onTelemetryEvent(() => {
window.cth.telemetryUsage(agentId).then(setUsage);
});
return unsubscribe;
}, [agentId]);
return usage; // { inputTokens, outputTokens, costUsd, breaker }
}
Summary
- Embedded OTLP Collector: The main process runs an HTTP server on loopback to receive OTEL spans from agents, ensuring no data leaves the local machine.
- OTLP Decoding: The
TelemetryCollectordecodes JSON batches and normalizes span attributes to extractinput_tokens,output_tokens, andagent_id. - Cost Ledger: Token counts are converted to USD immediately using model-specific rates defined in
realtimePricing.ts. - UsageProvider: Aggregates per-agent data via
getAgentUsage, returning a structured sample that includes cost and breaker status. - IPC Exposure: The preload script bridges the main process ledger to the renderer, enabling secure, real-time UI updates.
- React Hook:
useTelemetryconsumes the IPC channel to display live cost metrics in the frontend.
Frequently Asked Questions
How does the OTLP collector ensure data privacy?
The collector binds exclusively to a loopback address (localhost) and sets the OTLP endpoint environment variable to this internal URL. Because the TelemetryCollector never forwards spans to external endpoints, all prompt tokens and cost calculations remain on the local machine, satisfying PII-free telemetry requirements.
What data does getAgentUsage return?
According to the implementation in src/main/usage.ts, the method returns an AgentUsageSample object containing:
inputTokens: Total input tokens consumed by the agentoutputTokens: Total output tokens generatedcostUsd: Aggregated cost in US dollars based on real-time pricingbreaker: Boolean flag indicating whether the cost circuit breaker has tripped due to budget limits
How is the token-to-USD conversion calculated?
The conversion logic resides in src/main/realtimePricing.ts. It maps each model attribute (e.g., "claude-2", "claude-3-opus") to a specific per-token rate, multiplies the input and output tokens by their respective rates, and sums the results to produce the costUsd value stored in the ledger.
Can the pipeline handle multiple concurrent agents?
Yes. The UsageProvider maintains a dictionary of ledgers keyed by agent_id. Each incoming span is routed to the appropriate ledger based on its agent_id attribute, allowing the system to track costs independently for dozens of simultaneous agents while exposing a unified query interface via getAgentUsage.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →