Verified Agent Telemetry#
Co-Vibe follows Observal's methodology, not its AGPL implementation code: observe the harness/proxy/provider boundary and parse structured telemetry server-side instead of trusting an agent to summarize its own spend.
Trusted Sources#
- Codex/OpenAI: Codex can emit JSON/OTel events whose usage fields include
usage.input_tokens, cached-input details, output tokens, reasoning output, andusage.total_tokens. Co-Vibe accepts OpenAI response payloads and Codex OTel span attributes, and the local companion submits Codex Desktopresponse.completedsplit counters from numeric fields only, then persists the parsed counters exactly. - Claude: Claude Code/SDK exposes result usage and observability metrics.
Co-Vibe accepts Claude SDK/API usage shapes, including
modelUsage,input_tokens,output_tokens,cache_creation_input_tokens, andcache_read_input_tokens. The project Claude Code hook reads transcript JSONL usage records and submits only those counters through the verified telemetry tool. - Cursor: The Co-Vibe Cursor hook and
covibe-local watchsweep counter-only usage from Cursor's agent transcripts and local state database (per-session token splits, bubble token counts, Cursor's own cost accounting) and submit it asdirect_counts. Seecursor-integration.md. - Proxy/harness: A Co-Vibe wrapper may submit
direct_countsonly when it captured counters from a structured harness/provider event.
Trust Rules#
covibe_ingest_agent_telemetryis the only usage-metric path. It parses model, token, cache, and total counters server-side, stores a SHA-256 hash of the raw payload, and marks the spanverified = 1.- Claude Code hooks may start and end agent sessions, but token/cost metrics
still enter only through
covibe_ingest_agent_telemetry. - Co-Vibe never stores raw prompts, model responses, or complete telemetry
payloads in
agent_spans. - Submitted cost is never trusted blindly. Co-Vibe recomputes cost when a known
model has a versioned server pricing entry, uses provider-estimated cost only
when pricing is unknown, and flags
cost_mismatch = 1when submitted cost disagrees with parsed usage. - Token and model attribution are deterministic for supported payloads. Dollar cost is an estimate unless the source is a provider billing/export system.
Supported Payload Kinds#
openai_responsecodex_otel_spananthropic_messageclaude_sdk_resultdirect_counts
Use telemetry_source to describe where the structured event came from:
harness_captured, proxy_captured, provider_reported, or otel_captured.
Codex export format#
Codex emits OTel-style spans. Co-Vibe accepts payload_kind codex_otel_span and
reads counters from the span attributes object (or, if the file is flat, from
the root object). Only these fields are read — derive nothing else:
- model:
gen_ai.response.model(falls back togen_ai.request.model) - input tokens:
gen_ai.usage.input_tokens(orcodex.turn.token_usage.input_tokens) - output tokens:
gen_ai.usage.output_tokens(orcodex.turn.token_usage.output_tokens) - cache-read tokens:
gen_ai.usage.cache_read.input_tokens(orcodex.turn.token_usage.cached_input_tokens) - total tokens:
codex.usage.total_tokens(orcodex.turn.token_usage.total_tokens); omitted totals are summed from the counters above - source event id:
codex.event.id, thengen_ai.response.id, thenspan_id/id
Provider is always normalized to openai. Export counters only — no prompts,
messages, responses, or transcript text. Write one JSON object (or an array of
them) per run to .covibe/telemetry/*.json:
{
"span_id": "codex-span-1",
"attributes": {
"gen_ai.response.model": "gpt-5-codex",
"gen_ai.usage.input_tokens": 1200,
"gen_ai.usage.cache_read.input_tokens": 800,
"gen_ai.usage.output_tokens": 350,
"codex.usage.total_tokens": 2350
}
}Flush the inbox (the --base-url value is the Co-Vibe origin only):
npm exec -- covibe-local telemetry \
--inbox .covibe/telemetry \
--base-url <origin> \
--payload-kind codex_otel_span \
--telemetry-source otel_captured \
--agent-type codex \
--agent-name "Codex"For the example above (gpt-5-codex, 400 uncached input + 800 cache-read + 350
output) the server prices the span itself: cost 0.0041, cost_source = server_pricing, verified = 1. No submitted cost is trusted.
Codex local response sync#
covibe-local watch also checks Codex Desktop's local SQLite databases when
present. This is an external telemetry source, not Co-Vibe app storage: the
companion reads the local Codex thread database only to map the current git repo
root to Codex thread ids, then extracts numeric response.completed counters
from the local Codex logs database. It submits split direct_counts only:
response.id, response.model, usage.input_tokens, usage.output_tokens,
usage.input_tokens_details.cached_tokens, and usage.total_tokens.
The first run primes ignored .covibe/codex-usage-state.json with existing
response ids and does not backfill historical responses. Later runs submit
events like:
{
"provider": "openai",
"model": "gpt-5.5",
"input_tokens": 194,
"output_tokens": 6,
"cache_read_tokens": 181,
"total_tokens": 200,
"source_event_id": "codex-response:resp-2"
}Run it once manually with:
npm exec -- covibe-local telemetry \
--base-url <origin> \
--codex \
--agent-type codex \
--agent-name "Codex Local"Or run continuous sync:
npm exec -- covibe-local watch --base-url <origin>The companion does not submit or store raw Codex logs, prompts, responses,
transcript text, or SSE bodies. If the split logs database or sqlite3 reader
is unavailable, no Codex usage event is submitted; watch continues with
snapshots, inbox flushes, and heartbeats.
Cursor export format#
Cursor installs wired by covibe-local setup capture usage automatically
(see cursor-integration.md); the manual format below also serves custom
wrappers. Cursor exports counter-only usage, so Co-Vibe accepts payload_kind
direct_counts and reads flat fields straight from the JSON object:
- provider:
provider(or the--agent/toolproviderfield) - model:
model(or the toolmodelfield) - input tokens:
input_tokens - output tokens:
output_tokens - cache-read tokens:
cache_read_tokens - cache-write tokens:
cache_write_tokens - total tokens:
total_tokens(omitted totals are summed from the counters above) - source event id:
source_event_id(orid)
Provider/model must be in the payload (or passed as flags) because Co-Vibe needs
them to price the span. Export counters only. Write one JSON object (or array)
per run to .covibe/telemetry/*.json:
{
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"input_tokens": 900,
"output_tokens": 300,
"cache_read_tokens": 200,
"cache_write_tokens": 150,
"total_tokens": 1550,
"source_event_id": "cursor-span-1"
}Flush the inbox:
npm exec -- covibe-local telemetry \
--inbox .covibe/telemetry \
--base-url <origin> \
--payload-kind direct_counts \
--telemetry-source harness_captured \
--agent-type cursor \
--agent-name "Cursor"For the example above (claude-sonnet-4-6) the server prices it cache-aware:
cost 0.007823, cost_source = server_pricing, verified = 1. When a model has
no server pricing entry, cost_source falls back to provider_estimate (if the
payload carried total_cost_usd/cost_usd) or unknown.
Local Import#
For Codex, OpenAI wrappers, Cursor wrappers, or CI harnesses that can export
usage JSON, developers can submit one file or flush the local telemetry inbox.
Use the install command shown in Agent Setup. Hosted builds serve the companion
from /downloads/co-vibe.tgz, so the default command looks like:
npm install --save-dev https://your-co-vibe-host/downloads/co-vibe.tgz \
&& printf "Co-Vibe MCP token: " \
&& read -rs COVIBE_MCP_TOKEN \
&& printf "\n" \
&& export COVIBE_MCP_TOKEN \
&& npm exec -- covibe-local setup --base-url https://your-co-vibe-host \
&& npm exec -- covibe-local doctor --base-url https://your-co-vibe-hostThen submit the usage file:
npm exec -- covibe-local telemetry \
--base-url https://your-co-vibe-host \
--file usage.json \
--payload-kind codex_otel_span \
--telemetry-source otel_captured \
--agent-type codex \
--agent-name "Codex" \
--latency-ms 450The setup, doctor, and telemetry --base-url values must be the Co-Vibe origin only;
credential-bearing URLs, callback URLs, query strings, hashes, and other paths
are rejected before local config is written. The setup block is chained so
telemetry import comes after a successful install, setup, and doctor check.
For recurring local capture, write one usage JSON file per run into
.covibe/telemetry and flush the inbox:
npm exec -- covibe-local telemetry \
--base-url https://your-co-vibe-host \
--inbox .covibe/telemetry \
--payload-kind direct_counts \
--telemetry-source harness_captured \
--agent-type cursor \
--agent-name "Cursor"The command accepts one JSON object or an array of usage events. It rejects files
that look like raw prompts, messages, transcripts, content, inputs, outputs,
responses, queries, or completions; export usage counters only. Use
--latency-ms when the harness has elapsed runtime metadata for the usage
event, and --cost-usd only for provider-estimated cost. Numeric telemetry
flags must be finite non-negative values and fail before submission when
invalid. Successfully submitted inbox files move to .covibe/telemetry/sent so
they are not double-counted; failed ingest leaves inbox files in place for retry.