Part of epic #8286 (Phase 3 — AI observability). Follow-on to #10218 (content capture) and #10226 ($ai_metric).
Why Hog, and why not the other two
PostHog offers three evaluation types. Only one fits this deployment:
| Type |
Cost |
Verdict |
| Code-based (Hog) |
Free — no LLM, runs in milliseconds |
Use this |
| Sentiment |
Free — local ML model on PostHog's side |
Skip — no value here |
| LLM-as-a-judge |
One LLM API call per evaluation, billed to a pinned cloud key |
Skip — see below |
- LLM-judge is structurally wrong for this stack. Its provider enum is
openai | anthropic | gemini | openrouter | fireworks | azure_openai | together_ai | minimax | zeabur — there is no ollama or self-hosted option. Enabling it means a new cloud bill on a deployment that is deliberately 100% self-hosted, and 7,434 of the last 13,997 AI events are free local ollama embeddings, so a paid cloud judge would mostly be grading calls that cost nothing.
- Sentiment classifies user-message sentiment. The "user messages" here are PR diffs and review prompts. There is no human emotion in a diff to measure.
Hog is free, deterministic, needs no model, and checks the things that actually matter for a review engine.
Event-budget constraint (this is the real cost)
Evaluations are free to run, but each one emits an $ai_evaluation event, and PostHog's LLM-analytics free tier is 100K events/month. Current run rate is ~2,300 AI events/day (~70K/month). Evaluating all 6,563 generations at 100% rollout would add ~33K/month and push the project over the free tier.
Mitigations, both first-class in the API:
conditions[].rollout_percentage (0–100) samples matching events.
target: "trace" evaluates once per trace instead of once per generation. With trace grouping now live (~7 generations/trace), this is roughly a 7× reduction on its own.
Start sampled and trace-targeted; widen only if the signal proves useful.
What Hog can read — verified, not assumed
Probed live with llma-evaluation-test-hog (3 recent generations, target: generation):
source: return not properties.$ai_is_error
→ 3/3 samples returned true, no errors
So generation properties ($ai_is_error, $ai_model, $ai_provider, $ai_latency, $ai_total_cost_usd, repo, pullNumber) are readable today.
The same probe returned empty input_preview / output_preview, confirming content is not yet flowing — #10218 is merged but has not reached the ORB image. Any check that inspects the prompt or completion is blocked until it does.
Candidate checks
Available now (properties only):
- A review generation that succeeded but reported zero cost and zero latency — a shape that suggests a degenerate or short-circuited call rather than a real one.
- A
claude-code generation whose latency exceeds the configured effort tier's timeout, which should be impossible and indicates the ladder is misconfigured.
Blocked on content capture reaching the ORB (#10218):
- The review returned parseable JSON matching the reviewer contract (
assessment, blockers, nits, suggestions, confidence). A malformed envelope is the single highest-value deterministic check — it is exactly what extractCliText / the JSON-mode fallback exist to survive, and today a malformed response degrades silently.
- The output is non-empty and not truncated — catches
claude_code_empty_output and clipped completions as a quality signal rather than only as an error.
- A blocker was asserted with supporting evidence (a file path or line reference present in the blocker text). A blocker with no locus is the shape that produces a bad auto-close.
Pick the JSON-contract check first; it is the one with a real failure mode already documented in the codebase.
Deliverables
Blocked by
The content-inspecting checks need #10218 live on the ORB. The property-only checks can ship immediately.
Note on $ai_evaluation
$ai_evaluation is PostHog's output, not ours — the docs are explicit that evaluations "run on new $ai_generation events; results are $ai_evaluation events". There is no SDK helper because clients are not meant to emit it. This is why #10226 deliberately excluded it and used $ai_metric for LoopOver-native quality signal instead. The two are complementary: $ai_metric carries what the engine already computes (reviewer votes, judgment agreement, gate/rule precision); $ai_evaluation carries what PostHog derives from the generation itself.
Part of epic #8286 (Phase 3 — AI observability). Follow-on to #10218 (content capture) and #10226 (
$ai_metric).Why Hog, and why not the other two
PostHog offers three evaluation types. Only one fits this deployment:
openai | anthropic | gemini | openrouter | fireworks | azure_openai | together_ai | minimax | zeabur— there is no ollama or self-hosted option. Enabling it means a new cloud bill on a deployment that is deliberately 100% self-hosted, and 7,434 of the last 13,997 AI events are free local ollama embeddings, so a paid cloud judge would mostly be grading calls that cost nothing.Hog is free, deterministic, needs no model, and checks the things that actually matter for a review engine.
Event-budget constraint (this is the real cost)
Evaluations are free to run, but each one emits an
$ai_evaluationevent, and PostHog's LLM-analytics free tier is 100K events/month. Current run rate is ~2,300 AI events/day (~70K/month). Evaluating all 6,563 generations at 100% rollout would add ~33K/month and push the project over the free tier.Mitigations, both first-class in the API:
conditions[].rollout_percentage(0–100) samples matching events.target: "trace"evaluates once per trace instead of once per generation. With trace grouping now live (~7 generations/trace), this is roughly a 7× reduction on its own.Start sampled and trace-targeted; widen only if the signal proves useful.
What Hog can read — verified, not assumed
Probed live with
llma-evaluation-test-hog(3 recent generations,target: generation):So generation properties (
$ai_is_error,$ai_model,$ai_provider,$ai_latency,$ai_total_cost_usd,repo,pullNumber) are readable today.The same probe returned empty
input_preview/output_preview, confirming content is not yet flowing — #10218 is merged but has not reached the ORB image. Any check that inspects the prompt or completion is blocked until it does.Candidate checks
Available now (properties only):
claude-codegeneration whose latency exceeds the configured effort tier's timeout, which should be impossible and indicates the ladder is misconfigured.Blocked on content capture reaching the ORB (#10218):
assessment,blockers,nits,suggestions,confidence). A malformed envelope is the single highest-value deterministic check — it is exactly whatextractCliText/ the JSON-mode fallback exist to survive, and today a malformed response degrades silently.claude_code_empty_outputand clipped completions as a quality signal rather than only as an error.Pick the JSON-contract check first; it is the one with a real failure mode already documented in the codebase.
Deliverables
llma-evaluation-test-hog(sample_countup to 10) before creating anything — the tool exists precisely so the code is proven against real samples first.hogevaluation viallma-evaluation-createwithevaluation_type: "hog",output_type: "boolean", andoutput_config.allows_na: trueso non-review generations (embeddings, vision, advisory) return N/A instead of a fabricated fail.conditions[].propertiesso it only runs on review generations, not on the free ollama embedding traffic.target: "trace"and a startingrollout_percentagewell under 100, then re-check the monthly$ai_evaluationvolume against the 100K free tier before widening.enabled: falsefirst, confirm the sampled results read correctly in the Traces view, then enable.Blocked by
The content-inspecting checks need #10218 live on the ORB. The property-only checks can ship immediately.
Note on
$ai_evaluation$ai_evaluationis PostHog's output, not ours — the docs are explicit that evaluations "run on new$ai_generationevents; results are$ai_evaluationevents". There is no SDK helper because clients are not meant to emit it. This is why #10226 deliberately excluded it and used$ai_metricfor LoopOver-native quality signal instead. The two are complementary:$ai_metriccarries what the engine already computes (reviewer votes, judgment agreement, gate/rule precision);$ai_evaluationcarries what PostHog derives from the generation itself.