Accurate per-model token accounting, Codex CLI and Gemini CLI as co-primary adapters, and the FORG-OS signal layer specification for cross-tool intelligence.
Every session now shows a Models Used table with per-model rows: tokens in, tokens out, cache reads, cost, and turn count. When a session switches models mid-session (e.g. claude-sonnet-4-5 for most turns, then claude-opus-4 for a complex task), both models appear with accurate attribution.
The data is built from individual telemetry events grouped by normalized model name on session end. A "+N" badge in the sessions list indicates multi-model sessions at a glance.
Old sessions recorded before v3.1.0 show a fallback row using the session's primary model and total tokens, with a "Pre-tracking data" label.
OpenAI Codex CLI is now a co-primary FORG adapter alongside Claude Code. Both have identical signal coverage: session start/end, per-turn token telemetry, model tracking, and MCP server registration.
Install: forg connect codex
The adapter writes a forg MCP server entry into ~/.codex/config.yaml under the mcpServers key. The entry is idempotent and uses a _forg_managed marker so Uninstall cleanly removes only the FORG footprint.
Codex sessions appear in the dashboard adapter filter. Model names from Codex (codex-mini-latest, o3, o4-mini) are normalized and tracked with correct per-model pricing.
Gemini CLI is now an official FORG adapter with full session telemetry. The adapter registers a forg MCP server in ~/.gemini/settings.json under the mcpServers key, following the same convention as Cursor and Warp. The file is created if absent.
Install: forg connect gemini-cli
Gemini sessions appear in the sessions list under the "Gemini CLI" adapter filter and are tracked with Gemini model pricing (gemini-2.5-pro, gemini-2.0-flash, etc.).
v3.1.0 defines the FORG-OS signal layer architecture:
L1 — Shell layer: command-name-only telemetry from shell hooks (zsh/bash). Records what CLI tools ran, not their arguments or output. Zero content capture.
L2 — Process layer: lightweight process metadata (name, PID, cgroup). Maps AI tool sessions to system processes for cross-tool aggregation. Shows "You used $X across 2 AI tools today."
L3 — OS layer: macOS Accessibility/Instruments or Linux perf metadata for timing and context switching. Deepest layer, opt-in, requires explicit permission grant.
L1 is the default. L2 and L3 are opt-in via settings. All layers are strictly metadata — no prompt or completion content is captured at any layer.
The Tokens Saved metric previously displayed a raw token count. v3.1.0 fixes this to show actual USD savings from cache reads.
Formula: savings_usd = cache_tokens_read × inputRate × 0.90
This reflects the real saving: cache reads cost 10% of the full input rate, so each cached token saves 90% of what a fresh input token would cost. The savings figure is computed per turn and summed per session.
Previous versions stored tokens_in = InputTokens + CacheReadInputTokens + CacheCreationInputTokens, causing double-counting since cache reads were also stored in a separate column.
v3.1.0 fixes the split: tokens_in = InputTokens + CacheCreationInputTokens (new input) cache_tokens_read = CacheReadInputTokens (cache hits) cache_creation_tokens = CacheCreationInputTokens (cache writes)
Cost calculation no longer subtracts anything from tokens_in — the numbers are clean from the start.
Previously, KV accumulators (per-session token counters) were deleted before the Supabase PATCH was confirmed. On a Supabase outage at session end, this caused permanent data loss.
v3.1.0 moves all RULE_STORE.delete() calls to after a confirmed successful PATCH response. If the PATCH fails, KV keys are preserved and the next heartbeat or retry will attempt the write again.
Claude Code's Stop hook can fire multiple times for the same transcript (e.g. on process restart). Each transcript line has a stable UUID. v3.1.0 sends this UUID as turn_uuid in the telemetry payload.
The engine uses ON CONFLICT(turn_uuid) DO NOTHING on INSERT, so duplicate turns are silently dropped. A partial UNIQUE INDEX on turn_uuid WHERE turn_uuid IS NOT NULL handles the case where legacy turns have no UUID.
Model IDs from Claude include a date suffix (e.g. claude-sonnet-4-5-20250929). These were stored as-is, causing GROUP BY model to produce separate rows for the same logical model on different dates.
v3.1.0 normalizes model names at INSERT time using a normalizeModel() function that strips the date suffix before storing. All existing historical data continues to work — normalization is applied to new turns only.
v3.1.0 release binaries were uploaded without the --remote flag, meaning they landed in the local wrangler simulation instead of the live R2 bucket. The worker's R2.get() returned null for every v3.1.0 key despite the object existing locally.
All 5 platform binaries (darwin-arm64, darwin-amd64, linux-amd64, linux-arm64, windows-amd64) are now confirmed present in the forg-dist R2 bucket. forg update resolves the correct stable version from D1, downloads the right binary, and replaces in-place.