# How the Telemetry Pipeline Tracks Agent Costs in Real Time Using OTLP Collector and UsageProvider

> Discover how the telemetry pipeline tracks agent costs in real time using OTLP collector and UsageProvider. Learn to decode spans, accumulate token counts, and expose usage efficiently.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: how-to-guide
- Published: 2026-08-29

---

**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)](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.

```typescript
// 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)](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.

```typescript
// 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)](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)](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.

```typescript
// 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)](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.

```typescript
// 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)](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.

```typescript
// 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 `TelemetryCollector` decodes JSON batches and normalizes span attributes to extract `input_tokens`, `output_tokens`, and `agent_id`.
- **Cost Ledger**: Token counts are converted to USD immediately using model-specific rates defined in [`realtimePricing.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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**: `useTelemetry` consumes 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/usage.ts), the method returns an **AgentUsageSample** object containing:
- `inputTokens`: Total input tokens consumed by the agent
- `outputTokens`: Total output tokens generated
- `costUsd`: Aggregated cost in US dollars based on real-time pricing
- `breaker`: 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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`.