Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .agents/skills/writing-evals/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ async def eval_my_thing(ctx: EvalContext) -> None:

- One suite = one Braintrust experiment. Bundle related cases into one suite; split only when the scorecard would be heterogeneous (self-skipping scorers stretch a shared scorer list across mildly different cases, but a scorecard where half the scorers skip half the cases is a sign to split).
- `experiment_name` is the Braintrust history key — renaming it resets cross-run comparison. Existing sandboxed suites end in `-cli` because the MCP server serves the `cli` surface.
- `SandboxedPublicEval` sets `no_send_logs=False` and reports results to Braintrust and PostHog, including available input, output, and expected values. `SandboxedPrivateEval` sets `no_send_logs=True` and uploads results to neither service; local logs are still written. PostHog result uploads follow `no_send_logs` independently of `OPT_OUT_CAPTURE`, which still applies to ordinary SDK and trace clients. See [evaluation result reporting](../../../docs/internal/ai-offline-evaluation-reporting.md).
- `SandboxedPublicEval` sets `no_send_logs=False` and reports results to Braintrust, plus PostHog when `offline_suite` is configured. `SandboxedPrivateEval` sets `no_send_logs=True` and uploads to neither service by default; local logs are still written. Set `send_to_posthog=True` with `offline_suite` to publish only to PostHog, or `send_to_posthog=False` to disable that destination independently. These options also apply to one-shot suites. Ordinary SDK and trace clients still honor `OPT_OUT_CAPTURE`. See [evaluation result reporting](../../../docs/internal/ai-offline-evaluation-reporting.md).
- To enroll a suite in PostHog reporting, register its scorer kinds and immutable version UUIDs in the owning `offline_config.py` catalog, then pass `offline_suite=offline_suite("<domain>/<module>::<function>", metrics=[...])` to the runner. Use the PostHog AI catalog for built-in suites and the Slack app catalog for Slack suites. List every explicit scorer's `_name()` in `metrics`; the catalog adds `exit_code_zero` only for sandboxed suites. For one-shot suites, pass `suite_kind=SUITE_KIND`. The enrollment test discovers catalog-using modules and checks every suite function against its scorer list.
- Uploading and tagging require `POSTHOG_OFFLINE_EVAL_API_KEY` to be a personal key with `offline_evaluation_ingestion:write` and `evaluation:write`. Ingestion-only personal or project secret keys can upload without tags. Upload success is recorded before the separate, bounded tag update, so a tagging failure or interruption keeps the results link.

## One-shot suites

Expand Down Expand Up @@ -135,7 +137,7 @@ class MyCheck(Scorer):

Rules that apply to both:

- The harness adds `ExitCodeZero` to every experiment. Do not include it in a suite's `scorers`; an explicit duplicate is rejected.
- The sandboxed harness adds `ExitCodeZero` to every experiment. Do not include it in a suite's `scorers`; an explicit duplicate is rejected. One-shot suites do not receive it.
- Reuse the other generic scorers first: `NoToolCall`, `RequiredToolCall`, and `LastToolCallNot` (all in `products.posthog_ai.eval_harness.scorers`).
- Always parse logs via `LogParser.cached(...)` so a case's log is parsed once, not once per scorer.
- Count only successful tool calls (`is_error=False`) — the agent is free to attempt and fail.
Expand Down
54 changes: 38 additions & 16 deletions docs/internal/ai-offline-evaluation-reporting.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Offline evaluation result reporting

The evaluation harness in `products/posthog_ai/eval_harness/` reports public-suite scorer results to Braintrust.
The SQL suite also publishes those results to PostHog's offline experiment API when configured.
Public suites under `products/posthog_ai/evals/` and `products/slack_app/evals/` also publish those results to PostHog's offline experiment API when configured.
The Signals pytest suites reuse the same publisher for their completed results.
Agent and judge trace capture remains available.
Reporting does not run the agent or scorers again.
Use `hogli evals` to run PostHog AI 2.0 suites. See the [eval harness guide](../../products/posthog_ai/eval_harness/README.md) for suite discovery and execution.

## Capture settings

Evaluation result uploads to Braintrust and PostHog share the `no_send_logs` setting.
`no_send_logs` controls Braintrust uploads and, by default, PostHog result uploads.
`SandboxedPublicEval` sets `no_send_logs=False` and uploads to Braintrust, plus PostHog when the suite has offline publishing configured.
`SandboxedPrivateEval` sets `no_send_logs=True` and uploads to neither service; local logs are still written.
`SandboxedPrivateEval` sets `no_send_logs=True` and uploads to neither service by default; local logs are still written.
Set `send_to_posthog=True` alongside `offline_suite` to publish results to PostHog while keeping Braintrust uploads disabled.
Set `send_to_posthog=False` to disable PostHog result uploads independently. The same options apply to one-shot suites.

Offline API uploads run through the Python SDK independently of `TEST` and `OPT_OUT_CAPTURE`.
The shared trace client retains its existing `TEST` and `OPT_OUT_CAPTURE` guards for `$ai_generation`, `$ai_span`, and `$ai_trace` events.
Expand All @@ -33,25 +35,27 @@ The scoring phase includes time waiting for the experiment results, rather than
A failed final metadata upload produces a warning and keeps the completed results available locally.
A scorer failure marks the case as an error. Statistics still include scores from other successful scorers.

## SQL offline experiment pilot
## Configured offline experiment suites

The `sql/eval_sql::eval_sql` suite (`sandboxed-sql-cli`) opts into the offline experiment publisher.
Other public suites continue reporting scores to Braintrust until they opt into offline publishing.
Configured public PostHog AI and Slack app suites opt into the offline experiment publisher, including both sandboxed and one-shot suites.
The 11 experiment-skill suites retain `no_send_logs=True` and explicitly set `send_to_posthog=True`, publishing results only to PostHog.
After scoring, the harness prepares the completed results in memory and queues delivery through the Python SDK's `AsyncOfflineEvaluations` client. It does not execute another agent run or judge call.
`no_send_logs=True` suppresses this upload too.
Without an explicit `send_to_posthog` override, `no_send_logs=True` suppresses this upload too.

The [SQL suite definition](../../products/posthog_ai/evals/sql/eval_sql.py) pins destination project `2` and the seven immutable scorer version UUIDs.
The [PostHog AI catalog](../../products/posthog_ai/evals/offline_config.py) and [Slack app catalog](../../products/slack_app/evals/offline_config.py) pin destination project `2`, scorer kinds, immutable version UUIDs, and component tags.
Suites select their own metrics from the catalog; sandboxed suites also include the harness-provided `exit_code_zero` metric.
The SQL suite retains its seven existing scorer versions.
Provide the API key through the environment before running the suite:

| Variable | Purpose |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `POSTHOG_OFFLINE_EVAL_API_KEY` | Personal or project secret API key with `offline_evaluation_ingestion:write`. A public project token cannot authenticate uploads. |
| `POSTHOG_OFFLINE_EVAL_HOST` | App origin; defaults to `https://us.posthog.com`. |
| Variable | Purpose |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POSTHOG_OFFLINE_EVAL_API_KEY` | Personal API key with `offline_evaluation_ingestion:write` and `evaluation:write` to upload and tag results. Ingestion-only personal or project secret keys can upload without tags. A public project token cannot authenticate uploads. |
| `POSTHOG_OFFLINE_EVAL_HOST` | App origin; defaults to `https://us.posthog.com`. |

The publisher normalizes the cloud ingestion hosts (`us.i.posthog.com`, `eu.i.posthog.com`) and the legacy `app.posthog.com` host to their app origins.
Uploads, result links, and trace-destination checks use that same origin.

The suite uses these existing scorers:
The SQL suite uses these existing scorers:

| Metric name | Scorer kind |
| ------------------------------------ | -------------------------------- |
Expand All @@ -72,15 +76,32 @@ export POSTHOG_OFFLINE_EVAL_API_KEY='your-secret-key'
hogli evals sql/eval_sql::eval_sql
```

Without an API key, the SQL run reports that offline uploads are disabled.
Without an API key, each enrolled suite reports that offline uploads are disabled.
Keys containing whitespace are rejected before evaluation starts; remove any spaces, tabs, or pasted trailing newlines.
Invalid configuration produces a warning and skips this destination; Braintrust and local reporting continue.
An upload failure also warns without changing the evaluation's score gate.
The final summary shows a PostHog results link only after a successful offline upload; otherwise it shows the upload status.

### Scorer and experiment tags

Score definitions and experiment runs use the same team and component tags:

| Suites | Tags |
| -------------------- | ------------------------------------------------------------------------------------------- |
| PostHog AI | `team:surfaces`, `component:posthog-ai/<domain>` (for example `sql` or `product-analytics`) |
| Slack app | `team:surfaces`, `component:slack-app/<area>` (for example `followup-classifier`) |
| Signals grouping | `team:self-driving`, `component:signals/grouping` |
| Signals scout safety | `team:self-driving`, `component:signals/scout-safety` |

`OfflineEvalSuite.tags` supplies the experiment tags. Delivery records the successful upload and results link once the experiment has completed, then calls the publisher's `tag()` method to update tags through the Python SDK's `experiment.set_tags(...)`. Tagging has its own five-second deadline outside the upload deadline. Tags are mutable metadata, separate from the immutable creation declaration and scorer versions.

To upload **and tag** experiments, `POSTHOG_OFFLINE_EVAL_API_KEY` must be a personal API key with both `offline_evaluation_ingestion:write` and `evaluation:write`. An ingestion-only personal or project secret key can still upload results, but the tag update will warn. Tagging failures, timeouts, and shutdown interruptions retain the successful upload status and results link and do not change the evaluation score gate.

Each prepared experiment's tag update is attempted once per publisher, including when delivery pauses and resumes. A failed or interrupted replacement is not repeated automatically, because it may already have succeeded or someone may have edited the tags afterward. Inspect the experiment's tags before manually retrying a replacement.

### CI configuration

CI jobs running this suite need `POSTHOG_OFFLINE_EVAL_API_KEY` and `BRAINTRUST_API_KEY` in the environment.
CI jobs running enrolled suites need `POSTHOG_OFFLINE_EVAL_API_KEY` and `BRAINTRUST_API_KEY` in the environment.
Map the dedicated `ANTHROPIC_OFFLINE_EVALS_API_KEY` and `OPENAI_OFFLINE_EVALS_API_KEY` Actions secrets to `LLM_GATEWAY_ANTHROPIC_API_KEY` and `OPENAI_API_KEY` for the harness and judges.
Publishing failures remain warnings, so check the final harness upload status even when a CI job is green.
The harness sets the SDK's `run_source` to `ci` when `CI` is set, including manual Actions runs, and to `local` otherwise.
Expand Down Expand Up @@ -124,6 +145,7 @@ Existing local logs, enabled case exports, and Braintrust results remain availab
Both `SandboxedEval` and `OneShotEval` accept `offline_suite=OfflineEvalSuite(...)`.
Each suite declares its destination `project_id`, selects at least one metric in `scorer_kinds`, and pins the corresponding immutable version UUIDs in `scorer_versions`.
These fields belong to `OfflineEvalSuite` in code. Scorer version UUIDs must be distinct for the suite's enrolled metrics.
The AI and Slack catalog helpers add `exit_code_zero` for sandboxed suites. One-shot suites pass `suite_kind=SUITE_KIND` to enroll only their declared metrics.
Numeric scores can use the domain of their configured scorer version. The API validates bounds and steps; the harness does not restrict every numeric metric to `[0, 1]`.

`OfflineEvalSuite.experiment_options` accepts the SDK's `ExperimentOptions` for external dataset identifiers, `prompt_version`, model/application versions, and `run_source`.
Expand Down Expand Up @@ -185,7 +207,7 @@ Runs appear in the offline experiments UI.
## Postgres experiment ingestion

The project API accepts offline experiment results behind the `ai-observability-offline-evaluations` feature flag.
The configured SQL pilot calls this API to publish completed scorer results.
The configured public suites call this API to publish completed scorer results.

Experiments and their items, results, and payloads belong to the exact project/environment in the request path.
Scorers and hosted datasets must belong to that same environment.
Expand Down
15 changes: 9 additions & 6 deletions products/posthog_ai/eval_harness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,22 +387,25 @@ The summary includes the trial ID, the agent trace ID when available, phase dura
Case names and `--eval` filters stay the same.

`SandboxedPublicEval` sets `no_send_logs=False` and reports results to Braintrust, plus PostHog when the suite has offline publishing configured.
`SandboxedPrivateEval` sets `no_send_logs=True` and uploads results to neither service; local logs are still written.
PostHog result uploads follow `no_send_logs` independently of `OPT_OUT_CAPTURE`, which still applies to ordinary SDK and trace clients.
`SandboxedPrivateEval` sets `no_send_logs=True` and uploads results to neither service by default; local logs are still written.
Both sandboxed and one-shot suites can set `send_to_posthog=True` with `offline_suite` to publish only to PostHog while Braintrust uploads remain disabled, or `send_to_posthog=False` to disable PostHog uploads independently.
The 11 experiment-skill suites use this PostHog-only mode.
PostHog result uploads default to following `no_send_logs`, independently of `OPT_OUT_CAPTURE`, which still applies to ordinary SDK and trace clients.
See [evaluation result reporting](../../../docs/internal/ai-offline-evaluation-reporting.md) for capture settings and scope.

### SQL offline experiment pilot

The `sql/eval_sql::eval_sql` suite can also send its seven scorer results to PostHog's offline experiment API.
It publishes the same completed case results through the Python SDK and keeps sending to Braintrust.
Other public suites report scores to Braintrust until they opt into offline publishing.
Agent and judge trace capture remains available, and `no_send_logs=True` suppresses offline result uploads.
PostHog AI and Slack app public suites also opt into offline publishing with their own tagged scorer versions.
Agent and judge trace capture remains available. `no_send_logs=True` suppresses offline result uploads unless `send_to_posthog=True` is explicitly set.

Set `POSTHOG_OFFLINE_EVAL_API_KEY` to a personal or project secret key with `offline_evaluation_ingestion:write` and access to project `2`.
Set `POSTHOG_OFFLINE_EVAL_API_KEY` to a personal key with `offline_evaluation_ingestion:write`, `evaluation:write`, and access to project `2` to upload and tag results. An ingestion-only personal or project secret key can upload results, but tagging will warn and leave the run untagged.
The [SQL suite definition](../evals/sql/eval_sql.py) pins that project ID and all seven immutable scorer version UUIDs in code.
`POSTHOG_OFFLINE_EVAL_HOST` defaults to `https://us.posthog.com`.
The [pilot configuration](../../../docs/internal/ai-offline-evaluation-reporting.md#sql-offline-experiment-pilot) lists the metrics and explains local and CI key configuration.
The [suite configuration](../../../docs/internal/ai-offline-evaluation-reporting.md#configured-offline-experiment-suites) lists the metrics and explains local and CI key configuration.
Delivery runs in a bounded background queue after local results are saved. Prepared uploads stay in memory, with up to two uploads running concurrently, at most 32 queued or active uploads, and a 60-second deadline per active upload.
A completed upload records its results link before tagging begins. Tagging has a separate five-second deadline; failures, timeouts, and shutdown interruptions only warn and preserve the successful upload status.
Unfinished uploads pause before infrastructure teardown. After teardown, the harness retries the same prepared objects and identities with fresh deadlines, waiting up to the full 60-second upload deadline for delivery to finish.
Requests use a 5-second timeout and up to two retries within that overall deadline. Terminal API failures trigger a best-effort attempt to mark the experiment failed, limited to 5 seconds; pauses and interruptions leave it resumable.
Both sandboxed and one-shot suites can opt in through `offline_suite`. Rich scorer recording runs only when offline publishing is configured and uploads are allowed.
Expand Down
Loading
Loading