Metrics

Contents

A metric is a named, governed answer to a business question: one definition, one owner, one source of truth. Instead of every AI session (and every teammate) deriving "MRR" or "activation rate" from scratch, you define it once in the semantic layer and everything downstream runs the same definition.

The semantic layer is in alpha

The semantic layer is in alpha, enabled per organization for a small group of customers. There's no dedicated UI yet – you work with it through MCP tools and SQL. Tool names and behavior may change between releases. Found a bug, or want access? Contact support and mention the semantic layer alpha.

Anatomy of a metric

FieldWhat it is
nameIdentifier-safe handle (mrr, weekly_active_teams), unique per project. Write-once and reserved forever, even after deletion.
display_nameHuman-friendly label. Mutable, unlike name.
descriptionWhat the metric means and how to interpret it. This is load-bearing text – agents read it to decide whether the metric answers a question.
unitUnit of the result, e.g. usd, percent, cents.
ownerThe human accountable for the metric.
definitionThe machine-readable query, or markdown calculation steps. Omit it for a name-and-description-only stub.
definition_kindQuery kind of the definition (HogQLQuery, TrendsQuery, ...), or null for a stub.
statusLifecycle state: proposed or approved.
is_driftedWhether the definition has drifted from its source insight. Computed at read time, never stored.
source_insight_short_idSet when the metric was created from an insight – the insight's query is snapshotted server-side. Mutually exclusive with definition.
created_source, ai_model, confidence, reasoningProvenance: whether a human (user) or an agent (ai_generated) authored it, and if an agent did, which model, how confident it was, and why.

Why names are reserved forever

Agents and integrations store metric names and call them later. If a deleted name could be reused, a stored reference to mrr could silently start meaning something else. So names are write-once: deleting a metric retires its name permanently. Treat naming a metric like naming an API route.

Two kinds of definition

Executable query. A HogQLQuery, TrendsQuery, FunnelsQuery, or a bare event node, executed by the same query runner that powers insights – running the metric gives identical results to running the query directly. This is the kind to prefer: deterministic, auditable, and overridable with date ranges at run time.

JSON
{
"kind": "HogQLQuery",
"query": "SELECT sum(amount) AS mrr FROM stripe_subscriptions WHERE status = 'active'"
}

Markdown definition. Numbered steps that an agent follows to produce the number, for calculations that can't be one query or that need judgment:

JSON
{
"kind": "MarkdownDefinition",
"markdown": "1. Sum MRR of customers active both this month and 12 months ago.\n2. Divide by those customers' MRR 12 months ago.\n3. Exclude internal and test accounts (email ends in @posthog.com)."
}

Running a markdown metric doesn't execute anything – the steps come back in the instructions field and the agent computes the number, still anchored to the one governed procedure.

Use an executable definition whenever the metric is deterministic. Reach for markdown only when the calculation genuinely can't be a single query.

Lifecycle: proposed → approved

Every metric lands as proposed – whether a human or an agent created it, and even when an existing name is refined (data-catalog-metric-create upserts on name). A human then approves it, which requires typing "confirm" and the data_catalog_approval scope.

Editing an approved metric's definition resets it to proposed. A metric is never silently redefined: any change to what the number means goes back through a human.

Drift

Drift is how the semantic layer notices when a definition goes stale behind your back.

When a metric is created from an insight (via source_insight_short_id), PostHog snapshots the insight's query. From then on, is_drifted is computed at read time by comparing the snapshot to the insight's current query. Nothing is stored or scanned in the background – drift is always evaluated against the live insight.

A worked example:

  1. You create a signups metric from your "Weekly signups" insight and approve it.
  2. A teammate edits the insight to exclude a country.
  3. The metric now reports is_drifted: true. The approved snapshot no longer matches the insight it came from.
  4. Runs still work, but results are labeled noncanonical, and re-approval is blocked until a human resolves the drift – either update the metric to re-snapshot the new query, or unlink it from the insight.
The canonical rule

A result is canonical only when status = 'approved' and is_drifted = false. Agents never present a proposed or drifted metric's result as canonical – they label it noncanonical instead.

Running a metric

Agents run metrics with the data-catalog-metric-run MCP tool. Every run returns a normalized envelope:

FieldWhat it is
statusLifecycle state of the metric that produced the results.
is_driftedWhether the definition has drifted. Check this and status before trusting the result.
unitUnit of the result.
kindQuery kind that was executed.
resultsThe query results, for an executable metric. Null for a markdown metric.
compiled_queryThe compiled HogQL, when available.
posthog_urlDeep link to open the exact query in PostHog – the audit trail for any number an agent hands you.
instructionsFor a markdown metric, the calculation steps to follow. Null for an executable metric.

Runs accept optional overrides: date_from (e.g. -7d), date_to, and interval reshape the query window without touching the definition. Overrides are rejected for HogQLQuery metrics, whose window is fixed in the SQL. A refresh parameter controls caching with the same semantics as insights (blocking, async, force_blocking, and friends) – by default a fresh cache hit is served and stale results are recalculated.

Naming metrics well

Names are an API contract. Choose accordingly:

  • Lowercase, terse, unambiguous: mrr, nrr, weekly_active_teams
  • No versions or qualifiers in the name – that's what description and the lifecycle are for
  • If two teams mean different things by "activation", make two metrics with honest names (activation_signup, activation_first_insight) rather than one contested one

The description does the heavy lifting for discovery: agents match questions against it, so write it the way a teammate would ask the question.

Community questions

Was this page useful?