Context
feat/llm-usage-metering added per-user LLM cost metering: the proxy records a
content-free row (user, model, token counts, status, duration, ttft) into the
llm_usage table, and GET /api/usage_summary breaks spend down by user, costed
at read time from the hand-maintained rate table in backend/src/pricing.ts.
PostHog has an AI observability product
that overlaps: $ai_generation events carry provider/model/tokens/cost/latency, it
computes cost itself, and its dashboard answers "LLM costs by customer and model".
Privacy mode omits
$ai_input / $ai_output_choices, so no prompt or completion content is sent.
Decision: in addition, not instead
llm_usage stays the system of record. It must be, because:
- Consent. PostHog is the channel our consent levels gate (opt-out by default,
opt in at >= usage). Usage metering deliberately sits outside those levels —
it's operational billing data we keep even at level none. Putting the ledger in
PostHog would mean either capturing analytics for users who opted out, or having
no cost data for them.
- Durability. PostHog strips large
$ai_ properties after 30 days and events
age out under retention; a billing record needs to reconcile against an invoice
months later.
- Deletion. PostHog person deletion removes the events, so a departing user
would take their spend history with them — exactly what the deleted tombstone
in anonymizeUserUsage exists to prevent.
What a mirror would buy us
- An independently-maintained price table.
pricing.ts is hand-maintained and
will silently rot the day we switch models (unlisted models report costUsd: null
and appear in unpricedModels, so at least it fails loudly). PostHog's own cost
calculation is a free cross-check.
- Dashboards next to product analytics: which generation mode costs most per
suggestion, whether TTFT correlates with drop-off, cost per feature. Our JSON
summary endpoint will never be that.
Sketch
- Emit
$ai_generation from meter() in backend/src/openaiProxy.ts alongside
recordUsage() — we already have every number. Note our proxy is a raw fetch
passthrough with no OpenAI SDK, so none of PostHog's drop-in wrappers apply; the
event is hand-rolled via posthog-node.
- Only for users at consent >=
usage (the proxy currently resolves the user id
but not their consent level — small change to resolveUserId). PostHog's picture
is therefore deliberately incomplete; that's fine, it's the analytics view.
- Privacy mode on, and never send
$ai_input / $ai_output_choices.
- Add PostHog to the privacy policy's third-party list as a recipient of
content-free token/cost telemetry (today it's listed for product analytics and
error monitoring only).
Context
feat/llm-usage-meteringadded per-user LLM cost metering: the proxy records acontent-free row (user, model, token counts, status, duration, ttft) into the
llm_usagetable, andGET /api/usage_summarybreaks spend down by user, costedat read time from the hand-maintained rate table in
backend/src/pricing.ts.PostHog has an AI observability product
that overlaps:
$ai_generationevents carry provider/model/tokens/cost/latency, itcomputes cost itself, and its dashboard answers "LLM costs by customer and model".
Privacy mode omits
$ai_input/$ai_output_choices, so no prompt or completion content is sent.Decision: in addition, not instead
llm_usagestays the system of record. It must be, because:opt in at >=
usage). Usage metering deliberately sits outside those levels —it's operational billing data we keep even at level
none. Putting the ledger inPostHog would mean either capturing analytics for users who opted out, or having
no cost data for them.
$ai_properties after 30 days and eventsage out under retention; a billing record needs to reconcile against an invoice
months later.
would take their spend history with them — exactly what the
deletedtombstonein
anonymizeUserUsageexists to prevent.What a mirror would buy us
pricing.tsis hand-maintained andwill silently rot the day we switch models (unlisted models report
costUsd: nulland appear in
unpricedModels, so at least it fails loudly). PostHog's own costcalculation is a free cross-check.
suggestion, whether TTFT correlates with drop-off, cost per feature. Our JSON
summary endpoint will never be that.
Sketch
$ai_generationfrommeter()inbackend/src/openaiProxy.tsalongsiderecordUsage()— we already have every number. Note our proxy is a rawfetchpassthrough with no OpenAI SDK, so none of PostHog's drop-in wrappers apply; the
event is hand-rolled via
posthog-node.usage(the proxy currently resolves the user idbut not their consent level — small change to
resolveUserId). PostHog's pictureis therefore deliberately incomplete; that's fine, it's the analytics view.
$ai_input/$ai_output_choices.content-free token/cost telemetry (today it's listed for product analytics and
error monitoring only).