-
Notifications
You must be signed in to change notification settings - Fork 88
feat(eval)!: add eval measurements and EM-001 trace_fitness scorer #6036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
06cb1ee
feat(eval): add eval measurements and EM-001 trace_fitness scorer
ascerra 16e82de
refactor(eval): keep measurements tool-agnostic and document ownership
ascerra 2add5e9
fix(eval): address review feedback on PR #6036
ascerra 71e411b
fix(eval): restore with: blocks and rename ledger extension
ascerra 930db1a
fix(eval): address review feedback and rebase onto main
ascerra 615dd69
fix(eval): move measure before upload and raise test coverage
ascerra c8438d8
fix(eval): address review findings on measurements path
ascerra 4ff9499
chore: merge origin/main into feat/eval-measurements
ascerra 3d5aefc
fix(eval): address second-round measurement review
ascerra 61b9bae
fix(eval): keep partial eval-measure rows on persist error
ascerra 5a21884
fix(eval): address third-round measurement review findings
ascerra d37d045
chore: merge origin/main into feat/eval-measurements
ascerra 1b87426
Merge remote-tracking branch 'origin/main' into feat/eval-measurements
ascerra 8029af1
fix(eval): address Wayne Aug-19 measurement review
ascerra e3731af
chore: merge origin/main into feat/eval-measurements
ascerra cffdfc7
chore: merge origin/main into feat/eval-measurements
ascerra 8596989
fix(eval): tighten output exclude and evening review follow-ups
ascerra cc5d4e4
chore: merge origin/main into feat/eval-measurements
ascerra b7a6591
chore: merge origin/main into feat/eval-measurements
ascerra 8f3564d
fix(eval)!: trust default-branch measurement manifests in CI
ascerra 903c0a4
fix(eval): resolve trusted measure path from fullsend-dir layout
ascerra 30564ef
chore: merge origin/main into feat/eval-measurements
ascerra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 151 additions & 0 deletions
151
docs/ADRs/0087-eval-measurements-online-trace-scoring.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| --- | ||
| title: "87. Eval measurements as online trace scoring with portable export" | ||
| status: Accepted | ||
| relates_to: | ||
| - operational-observability | ||
| - testing-agents | ||
| topics: | ||
| - observability | ||
| - evaluation | ||
| - opentelemetry | ||
| --- | ||
|
|
||
| # 87. Eval measurements as online trace scoring with portable export | ||
|
|
||
| Date: 2026-08-10 | ||
|
|
||
| ## Status | ||
|
|
||
| Accepted | ||
|
|
||
| ## Context | ||
|
|
||
| Agent runs already emit OpenTelemetry traces as `run-telemetry.jsonl`, with | ||
| optional live OTLP export when `OTEL_EXPORTER_OTLP_*` is set | ||
| ([ADR 0050](0050-distributed-tracing-instrumentation.md)). Separately, | ||
| [ADR 0051](0051-agent-eval-harness-for-test-infrastructure.md) owns the | ||
| **functional** eval harness: curated fixtures / scenarios in | ||
| `fullsend-ai/agents` `eval/<agent>/` that gate agent PRs. Those fixtures do | ||
| not score wild production runs. | ||
|
|
||
| Operators also need an **online / trend** layer on wild traces (completeness | ||
| first; quality signals later). Fullsend must stay **backend-agnostic**: orgs | ||
| already choose Phoenix, MLflow, Jaeger, or another OTLP collector for traces. | ||
| Baking a single product’s Assessments/Quality API into the core CLI or managed | ||
| workflows would force a tool decision on every install. | ||
|
|
||
| Adjacent telemetry work (not competing with this score path): | ||
|
|
||
| - **Level 3 content capture** ([ADR 0050](0050-distributed-tracing-instrumentation.md); | ||
| activation draft closed without merge in | ||
| [#5947](https://github.com/fullsend-ai/fullsend/pull/5947)): first ship | ||
| reads Level 1/2 metadata in `run-telemetry.jsonl` (fitness foundation). | ||
| Content-aware scorers on prompt/completion bodies are the intended next | ||
| layer once Level 3 is implemented. Measure CLI is host-side after sandbox | ||
| exit. | ||
| - **Span status from run outcome** | ||
| ([#5944](https://github.com/fullsend-ai/fullsend/pull/5944), merged): | ||
| OTLP Status (and `fullsend.transcript_error`) become the reliable | ||
| success/failure signal. EM-001 only checks that `exit_code` is **present** | ||
| (fitness). Outcome scorers must key on Status, not `exit_code == 0`. | ||
| - **Observer / lessons → fixtures** (draft closed without merge in | ||
| [#2423](https://github.com/fullsend-ai/fullsend/pull/2423)): narrative | ||
| analysis and golden-set promotion remain a sibling idea. This ADR is | ||
| same-job deterministic scoring on traces. | ||
| - **Harness snapshot / forge join keys** | ||
| ([#5524](https://github.com/fullsend-ai/fullsend/pull/5524), open): | ||
| sibling artifact for harness fingerprint and forge/CI pointers beside | ||
| telemetry. Complementary join/identity layer; primary run facts belong on | ||
| the OTEL trace (Level 1), while measurements stay a derived sibling file. | ||
|
|
||
| ## Options | ||
|
|
||
| 1. **Local JSONL only** — portable offline artifact; no remote scores from | ||
| fullsend itself. | ||
| 2. **Backend-native APIs in core** (e.g. one vendor’s Assessments API) — | ||
| couples every managed workflow to that product’s auth and schema. | ||
| 3. **Local JSONL + same OTLP path as agent traces for remote** — scores travel | ||
| with the endpoint/headers orgs already configure for ADR 0050; no second | ||
| vendor stack in core. | ||
|
|
||
| ## Decision | ||
|
|
||
| Introduce **eval measurements**: deterministic scorers that read | ||
| `run-telemetry.jsonl` after `fullsend run` in the **same** managed job | ||
| (`fullsend eval-measure` in `action.yml`), **fail-open**. Functional eval | ||
| scenarios remain ADR 0051 / `eval/<agent>/`; measurements never block | ||
| delivery. | ||
|
|
||
| In plain terms: eval measurements are the concept of scoring traces. | ||
| [OTEL primary facts](../glossary.md#otel-primary-facts) are what happened | ||
| on the run (the OTEL trace / `run-telemetry.jsonl`). | ||
| [OTEL derived products](../glossary.md#otel-derived-products) are scores | ||
| computed from that trace (`eval-measurements.jsonl`). Measurements never | ||
| rewrite primary facts, and they are [fail-open](../glossary.md#fail-open). | ||
|
|
||
| Scores land in a tool-agnostic `eval-measurements.jsonl` (plus a | ||
| small idempotency ledger) next to `run-telemetry.jsonl` whenever at least | ||
| one new measurement row is produced (including `label: skip`). Remote score export | ||
| will use the same `OTEL_EXPORTER_OTLP_*` configuration as ADR 0050 — no | ||
| vendor-specific score adapters in core. `fullsend` owns the parser, scorers, | ||
| CLI, and GHA step; `fullsend-ai/agents` owns per-agent measurement manifests | ||
| (`eval/measurements/<agent>.yaml`) that declare which scorers to enable. | ||
| Stock-agent defaults resolve from `agents@v0` at runtime; local files are for | ||
|
ascerra marked this conversation as resolved.
|
||
| override, opt-out, or custom agents only. Activation is **two-step**: merge | ||
| measurement manifests into `fullsend-ai/agents` **and** cut a `v0.x.y` release | ||
| that re-points the floating `v0` tag. Merging alone does not activate managed | ||
| jobs. Tracking: [#6384](https://github.com/fullsend-ai/fullsend/issues/6384). | ||
| Until that release lands, GHA/GitLab `eval-measure` wiring is provisional | ||
| (clean skip when the remote manifest is missing). Local `FULLSEND_DIR` | ||
| manifests are exercised in unit tests today. | ||
|
|
||
| The first scorer is `trace_fitness` (catalog id `em-001`) — span-tree and | ||
| attribute fitness so later scorers can trust the trace. EM-001 reads | ||
| OpenTelemetry GenAI attribute names (`gen_ai.*` constants in | ||
| `internal/evalmeasure`). `gen_ai.system` was renamed to `gen_ai.provider.name` | ||
| in semconv v1.37.0; `modelOK` accepts either so `em-001@1` survives the | ||
| emitter migration. Other upstream renames remain an `em-001` version bump. | ||
| Pre-script-skipped runs, runs with no `agent` span (never reached an | ||
| iteration), and runs where agent spans flushed but the root `run` span never | ||
| ended (hard kill / timeout) record `label: skip` and are excluded from | ||
| pass/(pass+fail). | ||
|
|
||
| ### Versioning (per measurement, not platform “v1”) | ||
|
|
||
| There is no product-wide “eval measurements v1” switch. “First ship” just | ||
| means only one scorer is enabled yet. Each manifest entry carries: | ||
|
|
||
| | Field | Meaning | | ||
| |---|---| | ||
| | `id` | Stable catalog id (`em-001`). New measurement concept → new id. | | ||
| | `scorer` | Go dispatch name (`trace_fitness`). | | ||
| | `version` | Integer **contract** version of that measurement’s checks / pass rule. | | ||
|
|
||
| Scores and the idempotency ledger key on `id@version` (e.g. `em-001@1`). | ||
| Bump `version` when pass/fail semantics change so trends do not mix eras. | ||
| Add a check that does not change the pass definition → same version is fine. | ||
| Entirely new signal → new `em-NNN` (and usually a new `scorer` string). | ||
|
|
||
| ## Consequences | ||
|
ascerra marked this conversation as resolved.
|
||
|
|
||
| - Every measured run produces a reviewable, backend-agnostic score file beside | ||
| telemetry; missing manifests skip cleanly and measure failure never fails | ||
| the agent job. GitHub Actions is the first-ship managed path (uploads | ||
| `output/`). GitLab CI calls the same fail-open `eval-measure` CLI under | ||
| `$CI_PROJECT_DIR/output` with `artifacts: when: always`. Stock manifests | ||
| fetch from public `agents@v0` even without `GH_TOKEN` (rate-limited); a | ||
| token is recommended on shared runners. | ||
| - Core stays tool-agnostic: no product-specific score env vars in managed | ||
| workflows; remote scores follow OTEL when that path lands. | ||
| - Functional scenarios (gate) and eval measurements (trend) stay separate; | ||
| retro can recommend either a manifest scorer or a scenario fixture. | ||
| - Level 1/2 metadata scorers (EM-001) are the foundation; Level 3 content | ||
| capture expands what scorers *can* assert (quality / LLM-judge style) once | ||
| implemented — it does not replace this same-job path. | ||
| - Per-measurement versioning (`id@version`) lets pass/fail semantics evolve | ||
| without mixing trend eras. | ||
| - Pre-script skipped runs (`fullsend.prescript.skipped=true` on the root span), | ||
| runs with no `agent` span (never reached an iteration), and runs where agent | ||
| spans flushed but the root `run` span never ended (hard kill / timeout) are | ||
| excluded from EM-001: the scorer writes `label: skip` instead of failing a run | ||
| that never produced a full telemetry contract. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.