supabase/functions/_shared/ai/, which is metadata-only against cloud Langfuse —
do not confuse the two.)
Three-layer ownership
L1 always runs. L2 is opt-in (
LANGFUSE_TRACING_ENABLED=1 + keys). L3 is on-demand.
L2 — self-hosted Langfuse
Stack:langfuse-web (host 127.0.0.1:3001) + worker + postgres + clickhouse +
redis + minio, all on the internal compose network (no port collision with the
Supabase 543xx stack). Distinct compose project langfuse.
LANGFUSE_INIT_* in .env): on first boot it
provisions the org/project and sets the project API keys deterministically, so no
UI click-through is needed. Log into the UI at http://localhost:3001 with
LANGFUSE_INIT_USER_EMAIL / LANGFUSE_INIT_USER_PASSWORD.
Tracing the automation AI calls
callAiGateway passes experimental_telemetry; automation/_platform/otel.ts
bootstraps the OTEL SDK with the LangfuseSpanProcessor when enabled. To trace a
run (e.g. the skills judge):
judge:ai.generateText with token usage in the Langfuse UI.
Gotcha: the SDK readsLANGFUSE_BASE_URL(defaultcloud.langfuse.com). The main checkout’s.env.localsets that to the cloud PF-111 instance; if yousourceit wholesale, the cloud value wins and exports your local keys to the wrong host (401 “Invalid credentials. Confirm the correct host”). SetLANGFUSE_BASE_URLexplicitly tohttp://localhost:3001, and only pullAI_GATEWAY_API_KEYout of.env.local.
L2 — datasets & experiments (C3 scorer)
npm run eval:experiment (evals/langfuse/run-experiment.ts) loads the two
reviewable spec fixtures as a Langfuse dataset (spec-workflow-fixtures), runs an
experiment whose task is the claude -p spec-review actor, and attaches an
LLM-as-judge evaluator graded through the AI Gateway. It prints a scorecard and
a dataset-run URL. Same env as above (plus the run needs the actor’s local Claude
Code auth). Each actor call is ~$0.35–0.50 / ~2 min.
Skill-regression evals (Tier B)
npm run eval:skills (evals/skills/run-skill-evals.ts) regression-tests Agent
Skills. For each skill that ships automation/skills/<name>/evals/evals.json, it
sends every case’s prompt to a fresh claude -p and grades (via the AI Gateway)
whether the skill’s behaviour was applied. Cases are tagged should_trigger: true|false so the gate catches BOTH under-triggering (skill should fire but
didn’t) and over-triggering (fires on unrelated prompts). Informational /
non-blocking; --strict exits non-zero on any failed case. CI: eval-skills.yml.
evals.json schema:
Finding (2026-06-04) — skills DO load in headlessclaude -p. A stream-json spike confirmedsystem:initregisters all skills and the response pulled body-only content (repo-specificnpm run deadcode/ Fallow / report paths that exist only in the SKILL.md body, not its ~100-token description). This reverses the earlier assumption (from the 2026-05-26 actor spike) that skill evals needed the Claude Agent SDK — they run on plainclaude -p, no SDK. First dogfood: 4/4 cases (code-review + dead-code-audit, trigger 0.90 / no-trigger 0.00).
Verified [2026-06]
Dogfooded against the self-hosted stack (Langfuse v3.178.0):- Spike B: a single
generateTextcall traced cleanly (spikeB:ai.generateText, model + token usage). - L2 wiring: the real
callAiGatewaypath emitted ajudge:ai.generateTexttrace AND the L1 JSONL line (cost computed) — both layers from one call. - C3:
eval:experimentproduced a dataset run with an average rubric score (0.80 across the two fixtures), viewable in the UI.
AI integration evals (synthetic harness)
The following four suites live underevals/datasets/ and extend the harness with
regression coverage for the AI integration expansion work
(design doc §4).
Suite inventory
Note:pf-ai-skill-drift.test.tsis a Vitest-native test (skipped unlessRUN_SKILL_DRIFT=1is set), NOT a suite registered inevals/run-scorecard.ts. The gated/promotion path below does not apply to it.
Promotion path
New suites startgated:false (advisory / report-only). To promote to gated:true
(CI-blocking):
- Run nightly via the DGX workflow (
.github/workflows/eval-ai-skills-regression.yml) for at least one week without flapping. - Confirm stable — pass rate at or above the suite’s declared
passThresholdacross nightly runs; no environment-dependent failures. - Flip
gated: truein the suite’s entry in theSUITESarray inevals/run-scorecard.ts. - Update this doc’s table above.
predict-denial-risk-contract starts gated:true because it tests a
deterministic function with no LLM calls. All LLM-dependent suites start
gated:false.
How to add a golden case
-
Read an exemplar — study
evals/datasets/gr-06-compliance-advisor.tsfor the dataset fixture pattern andevals/datasets/pf-ai-skill-contract.tsfor how to wire a scorer. -
Author a synthetic case — write a scenario using only synthetic data (no real
org UUIDs, no PHI). Add it to the relevant
*-contract.tsor*-injection.tsdataset file. -
PHI check — run
npm run eval:fixtures:phi:checkto confirm your addition is clean. -
Run locally — execute the suite directly:
On the GB10 box with the full AI stack, set
LOCAL_AI_BASE_URLto point at the local OpenRouter-compatible gateway: - Open a PR — include test output showing the new case passes; reviewer confirms it is synthetic and PHI-free.
METADATA-ONLY caveat and candidate sourcing
The PF-111 edge seam (supabase/functions/_shared/ai/) logs metadata only to
pf_ai_usage_logs — no prompt or response content is captured. This is by design for
PHI safety. As a result, you cannot mine production traces for golden-case content.
Instead, use the candidate finder to identify which skills have the highest failure
rates and latency, then hand-author synthetic golden cases that cover those failure
modes:
pf_ai_usage_logs for the past 30 days and prints a ranked table
(skillCode, failures, total, p95ms) so you can prioritize which skills need
new golden cases most urgently.
Real-trace content curation (capturing anonymized prompt/response pairs for eval
authoring) is the deferred content-capture item described in
design doc §4.
It requires an explicit PHI-review before enabling content capture on the PF-111 lane.
PHI guardrail
These tools are for synthetic, PHI-free fixtures only. Unlike the PF-111 in-product lane (metadata-only), this harness captures prompt/completion content in traces — which is fine for skill/spec markdown and synthetic fixtures, and is why Langfuse trace storage is self-hosted (traces stay on the box). Note this is storage residency only: the model traffic itself (theclaude -p actor and the
gateway LLM-judge) still leaves the host to the LLM provider, so the inputs must
remain synthetic and PHI-free regardless. Never pipe real specs containing PHI, real
org UUIDs, or patient data through these tools without a scrub (see
.claude/rules/security.md). If a PHI-bearing eval is ever needed, disable content
capture and treat it like the PF-111 metadata-only lane.