Telemetry

Overview#

The only data identical across hosts is the server's own bytes. The agent-connector serve proxy intercepts every tools/call at the server boundary and tokenizes input and output locally.

Input = params.arguments, output = result.content[] + structuredContent. With measureToolDefs (default on) it also tokenizes the tools/list schemas once → the fixed "cost of merely defining my tools" per-turn overhead. This measures the server your connector declares and wraps; to see per-CLI token totals without authoring a connector, use the connector-free usage track instead.

wrapped MCP entry
bash
# a wrapped MCP entry runs the real server behind the telemetry proxy:
agent-connector serve --connector acme-db -- npx -y @acme/acme-db-mcp

TelemetryConfig#

FieldTypeDefaultNotes
enabledbooleantrueAGENT_CONNECTOR_TELEMETRY=0 also kills it.
modelFamilyHint"auto" | "openai" | "anthropic" | "generic""auto"Tokenizer family selection; auto infers from client/host.
measureToolDefsbooleantrueTokenize tools/list once → fixed per-turn tool-definition overhead.
calibration{ anthropicCountTokens?: boolean }falseOpt-in network calibration (sends content off-box).
hostNativeUsagebooleanfalseOpt-in host-native turn capture. Also forced via AGENT_CONNECTOR_HOST_NATIVE=1.
store"ndjson" | "sqlite""ndjson"NDJSON needs no native deps; sqlite is a drop-in upgrade.
agent-connector.config.mjs
ts
telemetry: {
  enabled: true,            // AGENT_CONNECTOR_TELEMETRY=0 kills it
  modelFamilyHint: "auto",  // auto | openai | anthropic | generic
  measureToolDefs: true,    // tokenize tools/list once → per-turn overhead
  hostNativeUsage: false,   // opt-in; AGENT_CONNECTOR_HOST_NATIVE=1
  store: "ndjson",          // or "sqlite"
}

Tokenizer#

Default gpt-tokenizer (pure-JS, no native build → Windows/single-binary safe): o200k_base for every family — exact for OpenAI/Codex-family, and a documented approximation for Anthropic-family (no offline Claude tokenizer ships). Family is auto-selected from initialize.clientInfo or modelFamilyHint. Fallback is a plain chars/4 heuristic — explicitly labeled so it's never mistaken for exact. Separately, binary content blocks (image/audio/resource) are never base64-tokenized — each gets a flat per-modality token estimate (~85 each).

Confidence sources#

Every telemetry row carries one confidence source:

SourceMeaning
tokenizer-exactLocal gpt-tokenizer match for the host's family (o200k_base).
tokenizer-approxo200k_base used as a documented approximation for non-OpenAI families — Anthropic/Claude and the generic family (Gemini and any unrecognized host); no offline Claude tokenizer ships.
heuristicchars/4 fallback (no content-type awareness — when an encoder loads, binary content blocks get a flat per-modality estimate that is labeled tokenizer-approx, not heuristic, and base64 is never tokenized); explicitly labeled.
tokenizer-calibratedOpt-in Anthropic count_tokens sampler (sends content off-box) refines a row.
host-nativeReal host usage (e.g. Gemini usageMetadata.totalTokenCount) via the opt-in AfterModel hook.

Store#

Local, under the data-root, aggregate counts only — never raw args/results. MVP is an append-atomic NDJSON event log + derived rollups behind a TelemetryStore interface (store: "sqlite" is a drop-in upgrade). Rows are keyed roughly by connectorId, toolName, scope (call|tool_defs|model_turn|hook), hostPlatform, sessionId, projectKey, projectDir, inputTokens, outputTokens, confidenceSource, isError, ts.

Host usage layer#

A separate read-only subsystem (src/usage/) parses each agent CLI's native logs/DBs (JSONL / JSON / SQLite via pure-WASM sql.js / synced-cache artifacts) to report per-platform/project/session/model/day usage. Confidence is host-reported (real numbers) vs host-estimated (e.g. Kiro char/4, Crush cost-only). It never writes host config and never collides with the serve-proxy store. Some hosts (cursor / antigravity / antigravity-cli / trae / warp) need an external sync agent-connector does not perform → those rows are "requires sync, skipped" unless a local cache already exists.