Skip to content

feat(eval): add eval measurements and EM-001 trace_fitness scorer - #6036

Open
ascerra wants to merge 10 commits into
mainfrom
feat/eval-measurements
Open

feat(eval): add eval measurements and EM-001 trace_fitness scorer#6036
ascerra wants to merge 10 commits into
mainfrom
feat/eval-measurements

Conversation

@ascerra

@ascerra ascerra commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduce eval measurements: fail-open same-job scoring of wild agent OTEL traces (fullsend eval-measure), always writing portable eval-measurements.jsonl beside telemetry. First scorer is trace_fitness (EM-001). Decision recorded in ADR 0087.

Companion default-policy PR: fullsend-ai/agents#722 (manifests under eval/measurements/). Until those land on agents@v0, the measure step skips cleanly.

Ownership (please read)

Concern Repo
Parser, scorer implementations, CLI, GHA post-step this PR (internal/evalmeasure/)
Default manifests for stock agents agents#722
Org overrides / BYOA manifests Consumer FULLSEND_DIR
  • Stock-agent defaults are fetched from fullsend-ai/agents@v0 when no local file exists — installs do not copy manifests to score stock agents.
  • Local ${FULLSEND_DIR}/eval/measurements/${AGENT}.yaml is override / opt-out / custom-agent only.
  • Executable logic stays in fullsend because eval-measure is the released binary that reads run-telemetry.jsonl (which fullsend writes). Agents is content/policy, not that binary.
  • EM-001 is a platform fitness check on the telemetry contract; stock agents enable it via agents manifests.
  • Change guide: new Go scorer or (future) declarative assert: → fullsend PR; new id / enable / thresholds on an existing scorer for a stock agent → agents-only; org-specific policy → local override.
  • Planned (not in this PR): declarative logic-as-config in manifests so most agent-specific policy is YAML-only.

Tool-agnostic export

Core does not pick an observability product. Scores always land in local eval-measurements.jsonl. Remote score export (when implemented) reuses the same OTEL_EXPORTER_OTLP_* path as ADR 0050. No vendor Assessments adapters or MLFLOW_* (or similar) wiring in managed workflows.

Related Issue

N/A (architecture + first scorer). Adjacent: #5947, #5944, #2423.

Changes

  • ADR 0087 + guide (ownership, resolution, declarative sketch), glossary, architecture, tracing cross-links
  • internal/evalmeasure parser + trace_fitness + local JSONL/ledger
  • fullsend eval-measure CLI; fail-open post-step in action.yml
  • Manifest resolution: local FULLSEND_DIR then agents@v0 fetch

Testing

  • go test ./internal/evalmeasure/ (+ focused CLI eval-measure tests)
  • pre-commit hooks on commit
  • Companion agents manifests on v0 (agents#722)

Tested on local MLflow instance. Testing the case where a team chooses to use MLflow as their system to send the eval measurements scorer results too. See this example of the MLflow UI showing that trace_fitness ran and failed one time
image
Then see here the as part of the trace on the UI we can see a new assessment (what MLflow calls scorers) showing a passing results and what the trace_fitness eval measurementlooks for
image

Checklist

  • PR title follows Conventional Commits
  • Commits are signed off (DCO)
  • I wrote this contribution myself and can explain all changes in it

Notes for reviewers

  • Portable OTLP score export is the ADR remote contract but not wired yet (local JSONL today).
  • CLI flag remains --registry (path to the YAML); rename to “manifest” is follow-up.
  • Explainer HTML kept local / out of this PR.
  • MLflow Assessments adapter and MLFLOW_* workflow wiring removed; local JSONL only until portable OTLP score export lands.

@ascerra
ascerra requested a review from a team as a code owner August 10, 2026 11:44
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:45 AM UTC · Completed 12:02 PM UTC

Commit: fdf5632 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Add eval measurements CLI and EM-001 trace_fitness scoring

✨ Enhancement 📝 Documentation 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add fail-open, same-job trace scoring that writes eval-measurements.jsonl beside telemetry.
• Introduce EM-001 trace_fitness scorer driven by per-agent measurement manifests.
• Document architecture/ownership via ADR 0087 and new operator guide.
Diagram

graph TD
  A["GitHub Action job"] --> B["fullsend run"] --> C[("run-telemetry.jsonl")] --> G{{"Resolve manifest"}} --> H["registry.yaml"] --> D["fullsend eval-measure"] --> E[("eval-measurements.jsonl")]
  E --> F["upload-artifact"]
  G --> I["skip scoring"]
  subgraph Legend
    direction LR
    _proc["Process"] ~~~ _file[("Artifact")] ~~~ _dec{{"Decision"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Embed stock manifests into the fullsend binary
  • ➕ No network fetch from agents@v0; works offline in CI
  • ➕ Eliminates runtime dependency on a pinned agents ref
  • ➖ Couples policy/content updates to fullsend releases
  • ➖ Harder for agents repo to evolve defaults independently
  • ➖ Increases binary churn and complicates ownership boundaries
2. Export measurements to OTLP immediately (instead of JSONL-first)
  • ➕ Immediate backend dashboards without additional ingestion work
  • ➕ Single ingestion path for traces and scores
  • ➖ Forces OTLP exporter configuration correctness for scoring to be useful
  • ➖ Increases surface area/risk for first ship (auth, batching, schema)
  • ➖ Conflicts with the stated tool-agnostic requirement if not done carefully
3. Make manifests fully declarative now (YAML asserts, no Go scorers)
  • ➕ Faster iteration for per-agent policy without code changes
  • ➕ Reduces need for many tiny scorer implementations
  • ➖ Requires designing a stable DSL/runtime early
  • ➖ Harder to express complex multi-span logic safely at first
  • ➖ Higher risk for correctness and backwards compatibility

Recommendation: Proceed with the PR’s approach: JSONL-first portable outputs + fail-open same-job scoring + agent-owned enablement manifests. This cleanly separates engine (fullsend) from policy (agents/local overrides), avoids prematurely committing to an OTLP score export schema, and provides an always-available artifact (eval-measurements.jsonl) for any downstream system.

Files changed (31) +1831 / -4

Enhancement (8) +818 / -0
evalmeasure.goAdd 'fullsend eval-measure' Cobra command +71/-0

Add 'fullsend eval-measure' Cobra command

• Implements the eval-measure CLI command, validates required flags, calls the evalmeasure engine, and prints pass/warn results without gating (exit 0 on failed scores).

internal/cli/evalmeasure.go

root.goRegister eval-measure subcommand on root CLI +1/-0

Register eval-measure subcommand on root CLI

• Wires the new eval-measure command into the CLI root so it is available in the released binary.

internal/cli/root.go

export_local.goImplement local JSONL export and idempotency ledger +83/-0

Implement local JSONL export and idempotency ledger

• Adds portable 'eval-measurements.jsonl' append logic and a simple ledger file used to ensure per-trace per-measurement idempotency.

internal/evalmeasure/export_local.go

fitness.goAdd EM-001 trace_fitness scorer implementation +190/-0

Add EM-001 trace_fitness scorer implementation

• Implements a trace fitness score over expected span tree and key attributes (identity, work item, model, usage, cost/tools/turns, exit presence). Produces a pass only when all checks pass; otherwise emits a detailed explanation.

internal/evalmeasure/fitness.go

parse.goParse run-telemetry.jsonl OTLP JSON lines into trace/span model +187/-0

Parse run-telemetry.jsonl OTLP JSON lines into trace/span model

• Adds a minimal OTLP TracesData JSON parser that merges spans by trace ID and extracts scalar attributes into a portable in-memory representation, with increased scanner buffer limits.

internal/evalmeasure/parse.go

registry.goLoad measurement manifest YAML and dispatch scorers +81/-0

Load measurement manifest YAML and dispatch scorers

• Introduces Registry/MeasurementSpec types, validation rules, and scorer dispatch. Only runs measurements when the trace agent matches the manifest agent; unknown scorers are skipped for forward compatibility.

internal/evalmeasure/registry.go

run.goOrchestrate parse → score → append JSONL with ledger idempotency +58/-0

Orchestrate parse → score → append JSONL with ledger idempotency

• Implements the main engine function to parse telemetry, load the registry, score matching traces, append results to JSONL, and then record ledger entries (persist-first ordering).

internal/evalmeasure/run.go

types.goDefine portable Span/Trace/EvaluationResult types and helpers +147/-0

Define portable Span/Trace/EvaluationResult types and helpers

• Introduces core data model for parsed spans and emitted measurement results, plus helpers for typed attribute access, durations, span lookup, and agent identity extraction.

internal/evalmeasure/types.go

Tests (13) +627 / -0
evalmeasure_test.goAdd CLI tests for eval-measure scoring and flag requirements +56/-0

Add CLI tests for eval-measure scoring and flag requirements

• Adds tests that run the command against fixtures and assert eval-measurements.jsonl output, verifies subcommand registration, and checks missing-required-flag behavior.

internal/cli/evalmeasure_test.go

export_local_test.goTest local export and ledger behavior +67/-0

Test local export and ledger behavior

• Covers empty writes, parent directory creation, ledger miss/hit semantics, and ledger file creation.

internal/evalmeasure/export_local_test.go

parse_test.goTest telemetry parsing, merging, and error handling +54/-0

Test telemetry parsing, merging, and error handling

• Adds tests for successful parsing, merging split traces across lines, invalid JSON line errors, and missing file behavior.

internal/evalmeasure/parse_test.go

registry_test.goTest manifest loading validation and error cases +86/-0

Test manifest loading validation and error cases

• Covers valid manifests and multiple failure cases (missing agent/id/scorer, invalid version, illegal characters, invalid YAML, missing file).

internal/evalmeasure/registry_test.go

run_test.goTest end-to-end scoring, idempotency, and cancellation +96/-0

Test end-to-end scoring, idempotency, and cancellation

• Adds integration-style tests for scoring via registry, idempotency across runs, persistence ordering (append before ledger), and error/cancelled-context behavior.

internal/evalmeasure/run_test.go

score_test.goTest trace_fitness pass/fail scenarios and scorer dispatch behavior +72/-0

Test trace_fitness pass/fail scenarios and scorer dispatch behavior

• Validates complete pass output, failure when required attributes are missing, work-item sentinel handling, agent mismatch behavior, and skipping unknown scorers.

internal/evalmeasure/score_test.go

README.mdDocument evalmeasure test fixtures purpose and ownership +6/-0

Document evalmeasure test fixtures purpose and ownership

• Explains that testdata JSONL and sample registry are synthetic fixtures and that production manifests live in the agents repository.

internal/evalmeasure/testdata/README.md

complete.jsonlAdd complete OTLP trace fixture for passing fitness +1/-0

Add complete OTLP trace fixture for passing fitness

• Provides a single-line OTLP JSON trace fixture with expected spans/attributes used by parser and scoring tests.

internal/evalmeasure/testdata/complete.jsonl

missing-cost.jsonlAdd OTLP fixture missing cost attribute for negative coverage +1/-0

Add OTLP fixture missing cost attribute for negative coverage

• Adds a variant fixture omitting cost fields to ensure trace_fitness fails appropriately.

internal/evalmeasure/testdata/missing-cost.jsonl

review-unknown-workitem.jsonlAdd OTLP fixture with unknown work item sentinel +1/-0

Add OTLP fixture with unknown work item sentinel

• Adds a fixture representing a review run where work_item_id is "unknown" to exercise work item fitness logic.

internal/evalmeasure/testdata/review-unknown-workitem.jsonl

sample-registry.yamlAdd sample measurement manifest fixture +5/-0

Add sample measurement manifest fixture

• Adds a minimal registry YAML enabling em-001/trace_fitness for triage, used by unit and CLI tests.

internal/evalmeasure/testdata/sample-registry.yaml

split.jsonlAdd split-line OTLP trace fixture for merge behavior +2/-0

Add split-line OTLP trace fixture for merge behavior

• Adds a multi-line fixture with the same trace split across lines to validate trace merging logic.

internal/evalmeasure/testdata/split.jsonl

types_test.goTest attribute coercion, duration math, and trace helpers +180/-0

Test attribute coercion, duration math, and trace helpers

• Adds unit tests for AttrString/AttrInt/AttrFloat conversions, DurationSeconds, span lookup helpers, and agent identity extraction priority.

internal/evalmeasure/types_test.go

Documentation (9) +340 / -4
config.tsExpose Eval Measurements guide in docs navigation +1/-0

Expose Eval Measurements guide in docs navigation

• Adds an "Eval Measurements" entry to the infrastructure guides sidebar for discoverability.

docs/.vitepress/config.ts

0050-distributed-tracing-instrumentation.mdCross-link distributed tracing ADR to eval measurements ADR +8/-0

Cross-link distributed tracing ADR to eval measurements ADR

• Records the follow-on decision that online trace scoring writes eval-measurements.jsonl and (planned) reuses OTLP configuration for remote export.

docs/ADRs/0050-distributed-tracing-instrumentation.md

0087-eval-measurements-online-trace-scoring.mdAdd ADR 0087: online trace scoring and tool-agnostic export +114/-0

Add ADR 0087: online trace scoring and tool-agnostic export

• Introduces an accepted ADR defining eval measurements, fail-open same-job scoring, ownership split (fullsend engine vs agents manifests), and per-measurement versioning (id@version). Establishes EM-001 trace_fitness as the first scorer and documents planned OTLP export alignment with ADR 0050.

docs/ADRs/0087-eval-measurements-online-trace-scoring.md

architecture.mdDocument eval measurements as an observability artifact +4/-0

Document eval measurements as an observability artifact

• Adds eval measurements to the observability architecture section, including the planned OTLP export direction and linking to ADR 0087 and the new guide.

docs/architecture.md

glossary.mdClarify terminology for eval measurements vs eval scenarios +4/-4

Clarify terminology for eval measurements vs eval scenarios

• Refines glossary definitions to distinguish online/trend measurements on wild traces from curated functional eval fixtures, with direct links to ADR 0087/0051 and the guide.

docs/glossary.md

README.mdList Eval Measurements guide under infrastructure guides +1/-0

List Eval Measurements guide under infrastructure guides

• Adds a new entry pointing operators to the eval measurements guide.

docs/guides/README.md

cli-internals.mdDocument new eval-measure CLI subcommand and flags +4/-0

Document new eval-measure CLI subcommand and flags

• Adds 'fullsend eval-measure' to the CLI internals reference, including telemetry, registry, and output directory flags.

docs/guides/dev/cli-internals.md

distributed-tracing.mdAdd section describing eval measurements alongside tracing +12/-0

Add section describing eval measurements alongside tracing

• Documents that eval-measure runs after each managed run, produces eval-measurements.jsonl, and will reuse OTEL_EXPORTER_OTLP_* for portable remote export when implemented.

docs/guides/infrastructure/distributed-tracing.md

eval-measurements.mdAdd operator guide for eval measurements, manifests, and CLI usage +192/-0

Add operator guide for eval measurements, manifests, and CLI usage

• Introduces a full guide covering prerequisites, artifacts, ownership boundaries, manifest resolution (local override vs agents@v0), and usage of 'fullsend eval-measure'. Includes a future-facing declarative manifest sketch and clarifies fail-open semantics.

docs/guides/infrastructure/eval-measurements.md

Other (1) +46 / -0
action.ymlAdd fail-open eval-measure post-step with manifest resolution +46/-0

Add fail-open eval-measure post-step with manifest resolution

• Adds a GitHub Actions step that locates run-telemetry.jsonl, resolves an agent measurement manifest (local FULLSEND_DIR override or agents@v0 fetch), and runs 'fullsend eval-measure'. The step is 'continue-on-error' so measurement failures never fail the agent job.

action.yml

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Site preview

Preview: https://c7807bd5-site.fullsend-ai.workers.dev

Commit: 61b9bae78e6cc4e5b766b4bd36b8b013c0d3bf1f

@ascerra
ascerra marked this pull request as draft August 10, 2026 11:50
@qodo-code-review

qodo-code-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (1)

Grey Divider


Action required

1. Ledger suppresses failed writes ✓ Resolved 🐞 Bug ☼ Reliability
Description
MeasureAndExport calls RecordScored before AppendMeasurements, so if appending
eval-measurements.jsonl fails after the ledger write succeeds, the measurement is permanently
marked as done and will be skipped on future runs. This can silently lose measurement data and
prevent recovery without manual ledger repair.
Code

internal/evalmeasure/run.go[R44-48]

+			if err := RecordScored(ledgerPath, r.TraceID, r.Name, r.Version); err != nil {
+				return all, fmt.Errorf("record scored: %w", err)
+			}
+			if err := AppendMeasurements(measPath, []EvaluationResult{r}); err != nil {
+				return all, fmt.Errorf("append measurements: %w", err)
Relevance

●●● Strong

Clear reliability bug: ledger should not mark done before successful write; likely fixed.

PR-#1682

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The code checks AlreadyScored against the ledger, then records the ledger entry and only afterward
writes the JSONL. Any write failure after recording the ledger will cause permanent suppression on
subsequent runs.

internal/evalmeasure/run.go[33-49]
internal/evalmeasure/export_local.go[47-65]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The idempotency ledger entry is recorded before the measurement is appended to `eval-measurements.jsonl`. If the append fails (e.g., ENOSPC/EIO/permission), the ledger still indicates success, preventing future re-writes.

## Issue Context
Idempotency should reflect successful persistence of the measurement record.

## Fix Focus Areas
- internal/evalmeasure/run.go[33-49]

## Suggested fix
Swap the order so `AppendMeasurements(...)` happens first, then `RecordScored(...)` only after append succeeds. Optionally, batch appends and then record ledger entries after the batch is flushed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Guides README not updated ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
This PR adds a new guide under docs/guides/ but does not update docs/guides/README.md to index
it. This makes the guides index incomplete.
Code

docs/guides/infrastructure/eval-measurements.md[1]

+# Eval Measurements
Relevance

●●● Strong

Updating guides index/README when adding a new guide has clear accepted precedent.

PR-#5778

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires updating docs/guides/README.md when adding any new guide under
docs/guides/. The PR adds eval-measurements.md as a new guide but does not include a
corresponding README index update in the diff.

docs/guides/infrastructure/eval-measurements.md[1-1]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new guide file was added under `docs/guides/`, but the guides index file `docs/guides/README.md` was not updated to include it.

## Issue Context
The index is the primary entry point for discovering guides; missing entries cause documentation drift.

## Fix Focus Areas
- docs/guides/infrastructure/eval-measurements.md[1-1]
- docs/guides/README.md[1-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Broken curl header args ✓ Resolved 🐞 Bug ≡ Correctness
Description
The Eval measurements step builds curl args via `${GH_TOKEN:+-H "Authorization: Bearer
${GH_TOKEN}"}`, but quotes produced by parameter expansion are not shell syntax and get passed
literally/word-split, so curl can fail whenever GH_TOKEN is non-empty and the remote manifest
fetch is needed. This causes eval measurement scoring to skip even when the agents manifest exists
upstream.
Code

action.yml[R500-503]

+          URL="https://raw.githubusercontent.com/fullsend-ai/agents/v0/eval/measurements/${AGENT}.yaml"
+          TMP="$(mktemp)"
+          if curl -fsSL ${GH_TOKEN:+-H "Authorization: Bearer ${GH_TOKEN}"} -o "${TMP}" "${URL}"; then
+            REGISTRY="${TMP}"
Relevance

●●● Strong

Shell quoting/arg-splitting robustness issues are routinely fixed; low-risk correctness fix.

PR-#2106

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR-added curl invocation relies on quotes embedded in a parameter expansion; since those quotes
are not interpreted as quoting, the -H value is split/broken and curl can fail, preventing remote
manifest retrieval.

action.yml[494-503]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`action.yml` uses bash parameter expansion to conditionally add a `curl -H "Authorization: Bearer …"` header. Because bash does not re-parse quotes introduced via expansion, the header is word-split into multiple argv entries and curl can fail when `GH_TOKEN` is set.

## Issue Context
This breaks the remote fallback manifest download path, which is the primary path until local manifests are present.

## Fix Focus Areas
- action.yml[500-503]

## Suggested fix
Replace the expansion with a safe conditional (or an argv array), e.g.:

```bash
CURL_ARGS=( -fsSL -o "${TMP}" "${URL}" )
if [[ -n "${GH_TOKEN:-}" ]]; then
 CURL_ARGS=( -fsSL -H "Authorization: Bearer ${GH_TOKEN}" -o "${TMP}" "${URL}" )
fi
if curl "${CURL_ARGS[@]}"; then
 REGISTRY="${TMP}"
else
 ...
fi
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (3)
4. Guide missing prerequisites section ✓ Resolved 📜 Skill insight ✧ Quality
Description
docs/guides/infrastructure/eval-measurements.md includes procedural CLI usage without a clearly
labeled Prerequisites section before the procedure. This violates the guide structure requirement.
Code

docs/guides/infrastructure/eval-measurements.md[R115-122]

+## CLI
+
+```bash
+fullsend eval-measure \
+  --telemetry path/to/run-telemetry.jsonl \
+  --registry path/to/agents/eval/measurements/review.yaml \
+  --out-dir path/to/output
+```
Relevance

●●● Strong

Docs guides commonly add explicit Prerequisites before procedures; precedent accepted.

PR-#2277
PR-#2663

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires each documentation guide to include a prerequisites section before procedural
steps. The ## CLI section introduces how to run fullsend eval-measure without any preceding `##
Prerequisites` section.

docs/guides/infrastructure/eval-measurements.md[115-122]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new guide contains procedural instructions (CLI invocation) but does not include a clearly labeled `## Prerequisites` section before those steps.

## Issue Context
Compliance requires prerequisites to appear before step 1 of any procedure in guides.

## Fix Focus Areas
- docs/guides/infrastructure/eval-measurements.md[115-122]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. ADR 0087 multiple decisions ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
ADR 0087 records multiple distinct decisions (mechanism, ownership, persistence, remote export,
first scorer, versioning) inside one ADR. This violates the requirement that each ADR record exactly
one decision.
Code

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[R65-68]

+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**.
+
Relevance

●● Moderate

ADR splitting/“one decision” enforcement is subjective; no close accepted/rejected precedent found.

PR-#2743

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires an ADR to contain exactly one decision. The Decision section in ADR 0087
introduces the core mechanism and then adds several additional decisions as separate bolded
sub-items, indicating multiple decisions in one ADR.

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[65-75]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ADR 0087` includes multiple distinct decisions within the Decision section. Compliance requires each ADR to record exactly one decision.

## Issue Context
The current Decision section contains multiple bolded sub-decisions (e.g., Ownership, Persistence, Remote export, First scorer, Versioning).

## Fix Focus Areas
- docs/ADRs/0087-eval-measurements-online-trace-scoring.md[65-88]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Planned OTLP noted without callout ✓ Resolved 📜 Skill insight ≡ Correctness
Description
The docs describe a not-yet-implemented OTLP export path as "planned" without using the required `>
**Planned:**` callout format and without an issue link. This can mislead readers about current vs
future behavior.
Code

docs/architecture.md[290]

+- Eval measurements: fail-open same-job scoring of wild-run traces into `eval-measurements.jsonl` beside telemetry; portable remote score export is designed to use the same OTLP configuration as agent traces (MLflow Assessments adapter available now; OTLP path planned); backend-specific UI adapters are optional ([ADR 0087](ADRs/0087-eval-measurements-online-trace-scoring.md)). See [Eval Measurements](guides/infrastructure/eval-measurements.md).
Relevance

●● Moderate

Planned-feature callout/link rule seems inconsistently enforced; similar issue-link add was
rejected.

PR-#3903

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires any mention of planned features to use the > **Planned:** callout format
and include an issue link. The added architecture line explicitly says the OTLP path is "planned"
but provides neither the callout format nor an issue link.

docs/architecture.md[290-290]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Documentation mentions a planned/not-yet-implemented feature without the required `> **Planned:**` callout format and without linking to a tracking issue.

## Issue Context
The checklist requires planned features to be clearly marked and traceable to an issue.

## Fix Focus Areas
- docs/architecture.md[290-290]
- docs/guides/infrastructure/eval-measurements.md[145-149]
- docs/ADRs/0087-eval-measurements-online-trace-scoring.md[80-83]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Ledger keys on name 🐞 Bug ≡ Correctness ⭐ New
Description
MeasureAndExport uses r.Name (which can be overridden via manifest measurements[].name) as part
of the ledger key, so renaming a measurement without changing id@version causes the same trace to
be re-scored and appended again. This breaks idempotency and can duplicate eval-measurements.jsonl
rows for the same (trace_id, id@version).
Code

internal/evalmeasure/run.go[R40-41]

+			done, err := AlreadyScored(ledgerPath, r.TraceID, r.Name, r.Version)
+			if err != nil {
Relevance

●● Moderate

Seems like a real idempotency bug, but docs imply ledger intentionally keys on name; could be design
choice.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
MeasureAndExport passes r.Name into ledger lookups/writes, while registry allows overriding
Name independent of ID/version; since the ledger key includes the name string, cosmetic renames
change idempotency behavior and can create duplicates.

internal/evalmeasure/run.go[37-54]
internal/evalmeasure/export_local.go[43-45]
internal/evalmeasure/registry.go[59-75]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The idempotency ledger key currently includes `EvaluationResult.Name`, but `Name` can be a cosmetic display override (`measurements[].name`). This means changing only the display name invalidates the ledger entry and re-appends the same measurement for the same trace and `id@version`.

## Issue Context
- `MeasurementSpec.Name` is documented/implemented as an optional display override.
- `MeasurementSpec.versionString()` already produces the stable `id@version` identity.
- The ledger should key on stable measurement identity (e.g., `trace_id` + `id@version`), not presentation name.

## Fix Focus Areas
- internal/evalmeasure/run.go[37-53]
- internal/evalmeasure/export_local.go[43-45]
- internal/evalmeasure/registry.go[55-64]
- internal/evalmeasure/types.go[27-38]

### Suggested approach
- Change ledger key schema to drop `evalName` from the key, e.g. `traceID + "|" + version`.
- Alternatively, add a stable `MeasurementID` field to `EvaluationResult` (or pass `m.ID` through scoring) and key the ledger on `(traceID, measurementID@version)`.
- Update `AlreadyScored`/`RecordScored` call sites accordingly and adjust tests if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Export errors not fail-open ⊘ Outdated 🐞 Bug ☼ Reliability
Description
ExportMLflowAssessments returns raw errors from trackingURIFromEnv() instead of wrapping them in
ExportError, so the CLI won’t classify them as warnings and fullsend eval-measure can exit
non-zero on export-related misconfiguration. This violates the documented “export failures warn and
exit 0” behavior.
Code

internal/evalmeasure/export_mlflow.go[R67-70]

+	base, err := trackingURIFromEnv()
+	if err != nil {
+		return err
+	}
Relevance

●●● Strong

Matches repo pattern: export-related misconfig should warn/fail-open, not abort CLI.

PR-#1682
PR-#1573

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The MLflow exporter returns a non-ExportError on URI derivation failure, while the CLI only treats
*ExportError as a warning; therefore this export failure path becomes fatal to the CLI command.

internal/evalmeasure/export_mlflow.go[61-70]
internal/cli/evalmeasure.go[42-50]
internal/cli/evalmeasure.go[64-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`trackingURIFromEnv()` derivation/parsing failures are export-related but are returned as plain errors, bypassing the CLI’s `ExportError` downgrade path.

## Issue Context
The CLI checks `errors.As(err, *ExportError)` to decide whether to warn-and-exit-0 vs fail. Export-related errors should consistently be `ExportError`.

## Fix Focus Areas
- internal/evalmeasure/export_mlflow.go[65-70]
- internal/cli/evalmeasure.go[42-49]

## Suggested fix
Change:
```go
base, err := trackingURIFromEnv()
if err != nil { return err }
```
To:
```go
base, err := trackingURIFromEnv()
if err != nil { return &ExportError{Err: err} }
```
(or, if you want explicit-vs-derived semantics, only wrap errors on the derived path but still keep the CLI behavior consistent with docs).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. OTLP/OTEL jargon undefined ✓ Resolved 📜 Skill insight ✧ Quality
Description
The new guide introduces jargon/acronyms (e.g., OTLP, OTEL_EXPORTER_OTLP_*) without an inline
definition or a glossary link on first use. This reduces readability for new users.
Code

docs/guides/infrastructure/eval-measurements.md[R19-22]

+  └─ always writes  output/**/run-telemetry.jsonl
+  └─ if OTEL_EXPORTER_OTLP_* set → live OTLP export of agent spans
+       (any compatible backend — ADR 0050)
+
Relevance

●●● Strong

Docs feedback to define terms/acronyms on first use has been accepted in similar guides.

PR-#5778

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires jargon to be defined on first use via a glossary link or inline definition.
The guide uses OTEL_EXPORTER_OTLP_*/OTLP terminology immediately in the architecture diagram
without defining it or linking to the glossary.

docs/guides/infrastructure/eval-measurements.md[19-22]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The guide uses domain jargon/acronyms on first use without defining them inline or linking to `docs/glossary.md`.

## Issue Context
The checklist requires jargon to be defined on first use to keep guides accessible.

## Fix Focus Areas
- docs/guides/infrastructure/eval-measurements.md[19-28]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (2)
10. ADR 0050 edit not called out ✗ Dismissed 📘 Rule violation § Compliance
Description
An already-accepted ADR (ADR 0050) was modified, but the PR description does not explicitly
mention ADR 0050 and summarize the change. This makes review and auditing of decision-history
edits harder.
Code

docs/ADRs/0050-distributed-tracing-instrumentation.md[R154-159]

+**2026-08-10 — Eval measurements ([ADR 0087](0087-eval-measurements-online-trace-scoring.md)):**
+online scoring of wild-run traces writes `eval-measurements.jsonl` beside
+telemetry; portable remote score export is designed to follow the same OTLP
+configuration as this ADR (MLflow Assessments adapter available now; OTLP
+path planned). Backend-specific UI adapters remain optional. Distinct from
+functional eval fixtures ([ADR 0051](0051-agent-eval-harness-for-test-infrastructure.md)).
Relevance

●● Moderate

They avoid silent edits to accepted ADRs, but no precedent for PR-description callout requirement.

PR-#5244

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires explicit PR-description callouts when an accepted ADR is edited. The diff
shows a substantive new annotation added to accepted ADR 0050, triggering the requirement.

Rule 1062059: Call out edits to accepted ADRs in PR descriptions
docs/ADRs/0050-distributed-tracing-instrumentation.md[154-159]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Accepted ADR `0050-distributed-tracing-instrumentation.md` was modified, but the PR description must explicitly call out the ADR identifier/filename and summarize what changed.

## Issue Context
The PR description currently discusses ADR 0087 but does not explicitly name ADR 0050.

## Fix Focus Areas
- docs/ADRs/0050-distributed-tracing-instrumentation.md[154-159]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. ADR 0087 too many consequences ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
ADR 0087's Consequences section contains 6 bullets, exceeding the required 3–5 bullets. This
breaks the standard ADR format and reduces readability.
Code

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[R106-113]

+## Consequences
+
+- Wild runs produce a reviewable score file beside telemetry with or without a
+  remote backend.
+- Orgs that already set OTEL for traces get a portable score export contract
+  without a second auth scheme; optional adapters may enrich one product UI.
+- Missing manifests skip cleanly; measure failure never fails the agent job.
+- Functional scenarios (gate) and eval measurements (trend) stay separate;
Relevance

●● Moderate

Consequence bullet-count is a format nit; no strong precedent they enforce this strictly.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires 3–5 consequence bullets. The ADR includes six separate consequence bullets,
violating the requirement.

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[106-119]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR Consequences section must be 3–5 one-sentence bullet points, but this ADR has more.

## Issue Context
The current Consequences section includes 6 bullets.

## Fix Focus Areas
- docs/ADRs/0087-eval-measurements-online-trace-scoring.md[106-119]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

12. Temp manifest not cleaned ⊘ Outdated 🐞 Bug ☼ Reliability ⭐ New
Description
The GitHub Action downloads the upstream manifest into a mktemp file but never deletes it on the
success path (or on failures after the download succeeds). This leaves temporary files behind on
self-hosted or reused runners.
Code

action.yml[R439-445]

+          TMP="$(mktemp)"
+          CURL_ARGS=( -fsSL -o "${TMP}" )
+          if [[ -n "${GH_TOKEN:-}" ]]; then
+            CURL_ARGS+=( -H "Authorization: Bearer ${GH_TOKEN}" )
+          fi
+          if curl "${CURL_ARGS[@]}" "${URL}"; then
+            REGISTRY="${TMP}"
Relevance

●●● Strong

Team often accepts adding EXIT traps after mktemp to prevent temp-file leaks under set -euo
pipefail.

PR-#4864
PR-#5442
PR-#2196

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The step creates TMP and assigns it to REGISTRY on successful curl, but only rm -f occurs in
the curl failure branch; there is no trap or post-command cleanup.

action.yml[423-450]
action.yml[453-456]
PR-#4864

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`action.yml` creates a temporary file for the downloaded manifest (`TMP="$(mktemp)"`) but only removes it on the curl-failure branch. On success (and on later errors), the temp file is not removed.

## Issue Context
This is low-impact on ephemeral GitHub-hosted runners, but can accumulate on self-hosted runners or any environment that reuses workspaces.

## Fix Focus Areas
- action.yml[438-456]

### Suggested approach
- Immediately after `TMP="$(mktemp)"`, add a trap to delete it:
 - `trap 'rm -f -- "${TMP}"' EXIT`
- Ensure the trap is only set in the branch where `TMP` is created, so it doesn’t interfere with `LOCAL_REG` usage.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. ADR 0087 context too long ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
ADR 0087's Context section exceeds the required 1–3 short paragraphs and includes extended bullet
lists. This makes the ADR harder to scan and pushes problem-details into the ADR instead of linking
out.
Code

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[R23-31]

+## 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.
Relevance

● Weak

Nearly identical “Context must be 1–3 paragraphs” compliance ask was explicitly rejected.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires the ADR Context section to be 1–3 short paragraphs. The added ADR includes
multiple paragraphs and a multi-item bullet list under Context, exceeding the constraint.

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[23-52]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR Context section is longer than allowed (must be 1–3 short paragraphs) and includes additional long-form content.

## Issue Context
The Context section contains multiple paragraphs plus an extended "Adjacent telemetry proposals" list.

## Fix Focus Areas
- docs/ADRs/0087-eval-measurements-online-trace-scoring.md[23-53]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. ADR 0087 exceeds 100 lines ✓ Resolved 📜 Skill insight ⚙ Maintainability
Description
ADR 0087 is 119 lines in this PR, exceeding the 100-line maximum (excluding frontmatter). This
suggests the ADR is too long and should be shortened or split.
Code

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[R116-119]

+- Retro can recommend a **manifest scorer** or a **scenario fixture** — not
+  substitutes.
+- Richer telemetry (Level 3 / Status fixes) expands what scorers *can* assert;
+  it does not replace this same-job path.
Relevance

● Weak

ADR ≤100 lines enforcement was previously rejected when requested to shrink an ADR.

PR-#2582

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist sets a maximum of 100 lines of ADR content. The added ADR file in the diff is 119
lines long, exceeding that limit.

docs/ADRs/0087-eval-measurements-online-trace-scoring.md[1-119]
Skill: writing-adrs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ADR content exceeds the 100-line limit, indicating excessive scope or repeated context.

## Issue Context
The file added in this PR spans 119 lines; the checklist caps ADRs at 100 lines of content (excluding frontmatter).

## Fix Focus Areas
- docs/ADRs/0087-eval-measurements-online-trace-scoring.md[13-119]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View low (1)
15. eval-measurements.md wrong guide path 📜 Skill insight ⌂ Architecture
Description
A new guide was added under docs/guides/infrastructure/, but guides must live under either
docs/guides/admin/ or docs/guides/user/. This breaks the required guide directory convention and
makes guide organization inconsistent.
Code

docs/guides/infrastructure/eval-measurements.md[1]

+# Eval Measurements
Relevance

● Weak

Guide taxonomy violations (dev/infrastructure vs admin/user) have precedents being rejected.

PR-#5454

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist mandates that every file under docs/guides/ be located in either admin/ or
user/. This PR adds a guide in docs/guides/infrastructure/, violating that placement
requirement.

docs/guides/infrastructure/eval-measurements.md[1-1]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new documentation guide was added at `docs/guides/infrastructure/eval-measurements.md`, but compliance requires guides to be placed under `docs/guides/admin/` or `docs/guides/user/`.

## Issue Context
This PR also links to the guide from VitePress config and other docs, so moving the file requires updating those links.

## Fix Focus Areas
- docs/guides/infrastructure/eval-measurements.md[1-1]
- docs/.vitepress/config.ts[262-262]
- docs/architecture.md[290-290]
- docs/guides/infrastructure/distributed-tracing.md[246-255]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context
✅ Compliance rules (platform): 54 rules

Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/guides/infrastructure/eval-measurements.md
Comment thread docs/guides/infrastructure/eval-measurements.md
Comment thread docs/architecture.md Outdated
Comment thread docs/guides/infrastructure/eval-measurements.md Outdated
Comment thread docs/ADRs/0050-distributed-tracing-instrumentation.md Outdated
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md Outdated
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md
Comment thread action.yml Outdated
Comment thread internal/evalmeasure/run.go Outdated
Comment thread internal/evalmeasure/export_mlflow.go Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review

Findings

Low

  • [data-quality-on-partial-failure] internal/evalmeasure/run.go:46 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). Documented in TestMeasureFile_AppendBeforeLedger; consumers can deduplicate on (trace_id, name, version).

  • [ledger-key-injection] internal/evalmeasure/export_local.go:51 — The ledger key format is traceID|evalName|version. Registry validation rejects pipe and newline in evalName fields, but traceID comes from parsed OTLP data and is not validated for pipe or newline characters. A malformed traceID containing | could cause a false-positive match in AlreadyScored, skipping a legitimate measurement. In practice, OTLP trace IDs are 32-character hex strings, and FindPlatformTelemetry ensures only host-written (top-of-runDir) files are scored.

  • [edge-case] internal/evalmeasure/find.go:37FindPlatformTelemetry returns at most one telemetry path when searching child runDirs (the newest by modification time). If an outputDir legitimately contains telemetry from multiple agent runDirs for different agents in the same job, only the newest is scored. This is documented and intentional.

  • [naming-inconsistency] internal/cli/evalmeasure.go:643 — The CLI flag --out-dir uses a different naming convention than --output-dir. Both are defined on the same command with subtly different semantics (measurements output directory vs CI output base), creating potential confusion.

Previous run

Review

Findings

Low

  • [data-quality-on-partial-failure] internal/evalmeasure/run.go:46 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). Documented in TestMeasureFile_AppendBeforeLedger; consumers can deduplicate on (trace_id, name, version).

  • [non-determinism] action.yml:426 — The find command uses -print -quit to locate run-telemetry.jsonl, which returns the first match without guaranteeing order. In practice, fullsend run produces a single telemetry file, so this is unlikely to matter.

  • [ledger-key-injection] internal/evalmeasure/export_local.go:51 — The ledger key format is traceID|evalName|version. The registry validation rejects pipe and newline characters in ID, scorer, and name fields, but traceID comes from parsed OTLP data and is not validated for pipe or newline characters. A malformed traceID containing | could cause a false-positive match in AlreadyScored, skipping a legitimate measurement. In practice, OTLP trace IDs are 32-character hex strings, so this is unlikely outside adversarial input.

  • [GHA-workflow-command-injection] action.yml:444 — The printf statement uses %q for ${AGENT} (good), but %s for ${LOCAL_REG} and ${URL}, both of which contain the unsanitized ${AGENT} value. All known callers hardcode simple identifiers, keeping practical risk low.

  • [path-traversal] action.yml:433 — The AGENT input is interpolated into a filesystem path (${FULLSEND_DIR}/eval/measurements/${AGENT}.yaml) and a GitHub raw-content URL without path-component sanitization. LoadRegistry's strict schema validation limits exploitability to denial-of-service.

Previous run (2)

Review

Findings

Low

  • [data-quality-on-partial-failure] internal/evalmeasure/run.go:46 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). Documented in TestMeasureFile_AppendBeforeLedger; consumers can deduplicate on (trace_id, name, version).

  • [non-determinism] action.yml:426 — The find command uses -print -quit to locate run-telemetry.jsonl, which returns the first match without guaranteeing order. In practice, fullsend run produces a single telemetry file, so this is unlikely to matter.

  • [GHA-workflow-command-injection] action.yml:444 — The printf statement uses %q for ${AGENT} (good), but %s for ${LOCAL_REG} and ${URL}, both of which contain the unsanitized ${AGENT} value. All known callers hardcode simple identifiers, keeping practical risk low.

  • [path-traversal] action.yml:433 — The AGENT input is interpolated into a filesystem path (${FULLSEND_DIR}/eval/measurements/${AGENT}.yaml) and a GitHub raw-content URL without path-component sanitization. LoadRegistry's strict schema validation limits exploitability to denial-of-service.

Previous run (3)

Review

Findings

High

  • [logic-error] action.yml:532 — The PR appends a new "Upload fullsend artifacts" step at the end of the composite action, but the original upload-artifact step at line 412 is left in place. Both use artifact name fullsend-${{ inputs.agent }} and path ${{ github.workspace }}/output. The existing upload runs BEFORE fullsend eval-measure, so eval-measurements.jsonl is never included in the artifact. The second upload will fail with a duplicate artifact name error (upload-artifact v7 default overwrite: false), and since it lacks continue-on-error: true, the failure affects job status.
    Remediation: Move the eval-measure step to run before the existing upload step at line 412 and remove the duplicate upload step at the end.

Low

  • [data-quality-on-partial-failure] internal/evalmeasure/run.go:46 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). For a local JSONL artifact this is low-severity since consumers can deduplicate on (trace_id, name, version).

  • [GHA-workflow-command-injection] action.yml:521 — The echo statement interpolates ${AGENT} directly into stdout. While inputs.agent is workflow-author-controlled (not event-payload-sourced) and all known callers hardcode simple identifiers, the composite action is a public contract where external consumers could theoretically wire untrusted data.

  • [unused-parameter-idiom] internal/evalmeasure/run.go:18MeasureAndExport accepts context.Context but immediately discards it with _ = ctx. The parameter is reserved for future OTLP export per the doc comment.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

Low

  • [data quality on partial failure] internal/evalmeasure/run.go:48 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). For a local JSONL artifact this is low-severity since consumers can deduplicate on (trace_id, name, version).

  • [error handling gap] internal/evalmeasure/run.go:37 — When the scoring loop encounters a write error, MeasureAndExport returns partial results alongside the error. The CLI handler discards results on error, so measurements already written to disk are not printed to stdout.

  • [test adequacy] internal/evalmeasure/run_test.go — No test covers partial-failure scenarios where AppendMeasurements or RecordScored fails mid-batch.

  • [unused parameter idiom] internal/evalmeasure/run.go:20MeasureAndExport accepts context.Context but immediately discards it with _ = ctx. The parameter is reserved for future OTLP export per the doc comment.

  • [cross-repo-schema] internal/evalmeasure/registry.go:27 — The measurement manifest YAML schema is introduced without a formal schema version field. ADR 0087 designs per-measurement versioning (id@version), and the YAML parser silently ignores unknown keys, so adding new optional fields later will not break existing binaries.

  • [cross-repo-compatibility] action.yml — The eval measurements step hardcodes the agents-repo manifest path convention (eval/measurements/${AGENT}.yaml) and pins to v0. This is consistent with other agents-repo fallbacks in this codebase.

Previous run (5)

Review

Findings

Low

  • [missing-docs-for-new-public-symbol] docs/guides/dev/cli-internals.md:112 — The CLI command tree documents every top-level subcommand but does not include the new eval-measure subcommand or its flags (--telemetry, --registry, --out-dir). The tree is already stale (missing poll), and eval-measure has its own dedicated guide.

  • [missing-docs-for-new-public-symbol] docs/cli/README.md:24 — The "Additional commands" table does not include eval-measure. Other CI-internal commands (post-review, post-comment, reconcile-status, poll) are also absent, suggesting the table intentionally covers only user-facing commands.

  • [data quality on partial failure] internal/evalmeasure/run.go:44 — If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). For a local JSONL artifact this is low-severity since consumers can deduplicate on (trace_id, name, version).

  • [error handling gap] internal/evalmeasure/run.go:33 — When the scoring loop encounters a write error, MeasureAndExport returns partial results alongside the error. The CLI handler discards results on error, so measurements already written to disk are not printed to stdout.

  • [test adequacy] internal/evalmeasure/run_test.go — No test covers partial-failure scenarios where AppendMeasurements or RecordScored fails mid-batch.

  • [path traversal] action.yml:478AGENT input is interpolated into file paths and URLs without sanitization. Risk is minimal: the step has continue-on-error: true, AGENT comes from trusted workflow inputs (not PR content), and the worst case is a failed file check or 404.

  • [cross-repo-schema] internal/evalmeasure/registry.go:27 — The measurement manifest YAML schema is introduced without a formal schema version field. ADR 0087 designs per-measurement versioning (id@version), and the YAML parser silently ignores unknown keys, so adding new optional fields later will not break existing binaries.

  • [cross-repo-compatibility] action.yml:35 — The eval measurements step hardcodes the agents-repo manifest path convention (eval/measurements/${AGENT}.yaml) and pins to v0. This is consistent with other agents-repo fallbacks in this codebase.

  • [unused parameter idiom] internal/evalmeasure/run.go:17MeasureAndExport accepts context.Context but immediately discards it with _ = ctx. The parameter is reserved for future OTLP export per the doc comment.

Previous run (6)

Review

Findings

Critical

  • [reusable-workflow-contract-break] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — The PR removes the entire with: block from the composite action step (uses: ./.defaults/) in all 6 reusable workflows, deleting the required agent input along with version, fullsend-dir, run-url, status-repo, status-number, and mint-url. The composite action declares agent as required: true with no default in action.yml. Without the with: block, inputs.agent resolves to an empty string, causing fullsend run "" to execute with no agent name. Every consumer repo calling these reusable workflows will break. This change is also unrelated to the eval measurements feature — it is not mentioned in the PR title, body, or ADR 0087.
    Remediation: Restore the with: blocks on all 6 reusable workflow files. If the removal is intentional (e.g., moving input passing to a different mechanism), it should be landed as a separately scoped PR with an explanation of the alternative mechanism.

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — Six protected workflow files under .github/ are modified. The PR has no linked issue and the description does not explain the with: block removals from these governance and infrastructure files. Human approval is always required for changes to protected paths.

Medium

  • [data loss on partial failure] internal/evalmeasure/run.go:42RecordScored (ledger write) is called before AppendMeasurements (measurement write). If AppendMeasurements fails after RecordScored succeeds, the ledger marks the trace as already-scored but the measurement was never persisted. On retry, AlreadyScored returns true and the measurement is permanently lost. The code was rewritten since the prior review but the ordering bug persists.
    Remediation: Swap the order — call AppendMeasurements before RecordScored so the ledger entry is only written after the measurement is safely persisted. A duplicate measurement line on retry is recoverable; a lost measurement is not.

  • [missing-docs-for-new-public-symbol] docs/cli/README.md:24 — The "Additional commands" table lists fullsend run, lock, and scan but does not include the new fullsend eval-measure subcommand.
    Remediation: Add a row for fullsend eval-measure with description pointing to the eval measurements guide.

  • [missing-docs-for-new-public-symbol] docs/guides/dev/cli-internals.md:112 — The CLI command tree documents every subcommand but does not include the new eval-measure subcommand or its flags (--telemetry, --registry, --out-dir). The Key Source Files table also omits internal/cli/evalmeasure.go and internal/evalmeasure/.
    Remediation: Add eval-measure with its flags to the CLI command tree and add the new source files to the Key Source Files table.

Low

  • [missing-authorization] — No linked issue for a non-trivial change (1,700+ new lines of Go code, new CLI subcommand, new ADR, GHA post-step wiring, six reusable workflow modifications). ADR 0087 provides architectural authorization but an issue link would improve traceability.

  • [error handling gap] internal/evalmeasure/run.go:33 — When the scoring loop encounters a ledger or write error, MeasureAndExport returns partial results alongside the error. The CLI handler treats any non-nil error as fatal, so partial results already written to disk are not printed to stdout. The user sees an error but no indication of which measurements succeeded.

  • [test adequacy] internal/evalmeasure/run_test.go — No test covers partial-failure scenarios where RecordScored or AppendMeasurements fails mid-batch. The idempotency test covers the happy-path retry but not the failure-then-retry path where the ordering of RecordScored vs AppendMeasurements matters.

  • [naming specificity] internal/cli/evalmeasure.go:57 — The helper printResults is more generic than existing CLI helpers in this package (printResolvedDeps, printStatusTable, printSkipGuidance). Consider renaming to printMeasurementResults to match the established print<Domain> pattern.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (7)

Review

Findings

High

  • [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml — Six protected workflow files under .github/ are modified. The PR has no linked issue; human approval is always required for changes to governance and infrastructure files. Affected protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml.

Medium

  • [data loss on partial failure] internal/evalmeasure/run.go:42RecordScored (ledger write) is called before AppendMeasurements (measurement write). If AppendMeasurements fails after RecordScored succeeds, the ledger marks the trace as already-scored but the measurement was never persisted. On retry, AlreadyScored returns true and the measurement is permanently lost.
    Remediation: Swap the order — call AppendMeasurements before RecordScored so the ledger entry is only written after the measurement is safely persisted.

  • [missing consumer for new feature] .github/workflows/reusable-dispatch.ymlreusable-dispatch.yml (per-repo dispatch workflow) was not updated to forward MLFLOW_TRACKING_URI, MLFLOW_TRACKING_USERNAME, or MLFLOW_TRACKING_PASSWORD. It also does not declare MLFLOW_TRACKING_PASSWORD in its secrets section. All six reusable-{stage}.yml files were updated but this file was missed. Per-repo installations using reusable-dispatch.yml will silently skip MLflow Assessment export even when the org has the secrets configured. Per docs/contributing/workflow-contracts.md, both installation-mode chains must be updated when a reusable workflow adds a new secrets: entry.
    Remediation: Add MLFLOW_TRACKING_PASSWORD to the secrets section of reusable-dispatch.yml and forward MLFLOW_TRACKING_URI, MLFLOW_TRACKING_USERNAME, MLFLOW_TRACKING_PASSWORD in the env block of each action invocation.

  • [missing-docs-for-new-public-symbol] docs/cli/README.md:24 — The "Additional commands" table lists fullsend run, lock, and scan but does not include the new fullsend eval-measure subcommand.
    Remediation: Add a row for fullsend eval-measure with description pointing to the eval measurements guide.

  • [missing-docs-for-new-public-symbol] docs/guides/dev/cli-internals.md:112 — The CLI command tree documents every subcommand but does not include the new eval-measure subcommand or its flags (--telemetry, --registry, --out-dir).
    Remediation: Add an eval-measure entry to the CLI command tree.

Low

  • [missing-authorization] — No linked issue for a non-trivial change (1,700+ new lines of Go code, new CLI subcommand, new ADR, GHA post-step wiring, six reusable workflow modifications). ADR 0087 provides architectural authorization but an issue link would improve traceability.

  • [error handling gap] internal/evalmeasure/run.go:33 — On non-ExportError failures in the scoring loop, the CLI handler returns the error without printing partial results that were already written to disk.

  • [credential-scope] internal/evalmeasure/export_mlflow.go:97 — When MLFLOW_TRACKING_URI is unset, trackingURIFromEnv() derives the MLflow base URL from OTEL endpoints. If OTEL points to a non-MLflow backend, Basic Auth credentials would be sent to an unintended host (requires both a derived URI mismatch AND the password being set).

  • [comment consistency across workflows] .github/workflows/reusable-triage.yml:180 — The triage workflow's MLFLOW comment is 3 lines (includes "URI defaults from OTEL endpoint when unset") while the other five workflows use a 2-line comment.

  • [test seam pattern] internal/evalmeasure/export_mlflow.go:32 — The assessmentHTTPDo test seam var is annotated "Not safe for t.Parallel()" but there is no enforcement mechanism beyond the comment.

  • [test adequacy] internal/evalmeasure/run_test.go — No test covers partial-failure scenarios where RecordScored or AppendMeasurements fails mid-batch, which is particularly relevant given the ordering bug in the write path.

  • [missing-cross-reference] docs/guides/user/tracing-with-mlflow.md:97 — The MLflow tracing guide's "See also" section does not cross-reference the new eval measurements guide, which documents the MLFLOW_TRACKING_* env vars for the Assessments adapter.


Labels: PR adds new Go eval-measure package (internal/evalmeasure/), modifies CI workflows (.github/workflows/), and adds documentation (docs/guides/, docs/ADRs/)


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added type/feature New capability request component/ci CI pipelines and checks go Pull requests that update go code component/docs User-facing documentation labels Aug 10, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 1:34 PM UTC · Ended 1:54 PM UTC

Commit: 49f0d19 · View workflow run →

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:34 PM UTC · Completed 1:54 PM UTC

Commit: 49f0d19 · View workflow run →

@ralphbean

Copy link
Copy Markdown
Member

Heads up — this looks adjacent to #5524 (ADR 0075, harness-snapshot.json). Both build on ADR 0050 and add a new file riding alongside run-telemetry.jsonl. ADR 0087 already lists #5947/#5944/#2423 as adjacent proposals — wonder if 0075 belongs there too?

ascerra added a commit that referenced this pull request Aug 11, 2026
- Fix data-loss bug: swap AppendMeasurements before RecordScored so ledger
  only marks scored after measurement is persisted
- Fix broken curl header args in action.yml using bash array instead of
  parameter expansion (word-split safe)
- Add Prerequisites section to eval-measurements guide
- Add OTEL/OTLP inline definitions on first use in guide
- Use > **Planned:** callout format for unimplemented OTLP export
- Update docs/guides/README.md with eval measurements entry
- Consolidate ADR 0087 Decision into single paragraph (was multiple
  sub-decisions); trim Consequences to 5 bullets
- ADR 0050 cross-reference uses Planned callout format
- Rename printResults → printMeasurementResults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 1:04 AM UTC · Ended 1:14 AM UTC

Commit: 43a6ebd · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:15 AM UTC · Completed 1:33 AM UTC

Commit: 4bf9b26 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed stale reviews from themself August 11, 2026 01:33

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 615dd69

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Aug 13, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:49 AM UTC · Completed 12:02 PM UTC

Commit: 615dd69 · View workflow run →

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional review findings (security/reliability), verified against current head and cross-checked against existing PR comments for duplicates.

Comment thread action.yml Outdated
Comment thread action.yml Outdated
Comment thread action.yml Outdated
Comment thread internal/evalmeasure/fitness.go Outdated
Comment thread internal/evalmeasure/parse.go

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional review findings (security/reliability), verified against current head and cross-checked against existing PR comments for duplicates.

Comment thread action.yml Outdated
Comment thread internal/evalmeasure/registry.go Outdated
Comment thread internal/evalmeasure/fitness.go
Comment thread internal/evalmeasure/fitness.go Outdated

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional review findings (3 items) — see inline comments.

Comment thread internal/evalmeasure/fitness.go
Comment thread action.yml Outdated
Comment thread internal/evalmeasure/fitness.go

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review sweep — 1 finding.

Comment thread internal/cli/evalmeasure.go Outdated

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional findings on files not touched by this PR's diff (so they can't be attached as inline comments):

[MEDIUM] docs/problems/operational-observability.md (line 194) — open questions not annotated even though ADR 0087 partially answers them

This PR does not touch docs/problems/operational-observability.md at all, yet two of its still-open questions are partially resolved by ADR 0087: "How do we measure 'is the system getting better'?" (line 194) now has a partial answer via the new deterministic trace-fitness scoring/trend layer, and "At what scale does a dedicated LLM observability platform justify its operational overhead?" (line 195) is partially answered by the ADR's explicit choice to stay backend-agnostic (local JSONL + reuse of OTLP export) rather than adopt a vendor platform. Other ADRs in this problem doc (0041, 0021, 0050) are annotated with strikethrough + a pointer once decided; these two are left unmarked.

Suggestion: Annotate the relevant open questions in docs/problems/operational-observability.md with a pointer to ADR 0087 (following the existing strikethrough + link pattern used for ADR 0041/0021/0050), noting it's a partial/first-ship answer where applicable.


[MEDIUM] internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-agent.yml (line 299) — GitLab-managed agent jobs get no equivalent eval-measure step

ADR 0087 states measurements run "in the same managed job" as fullsend run, but only the GitHub composite action (action.yml) was updated with an Eval-measurements step; the GitLab CI template calls fullsend run "${STAGE}" ... (line 299) with no follow-on fullsend eval-measure invocation anywhere in the file. Confirmed via grep: no eval-measure, eval_measure, or EvalMeasure reference exists under internal/scaffold/fullsend-repo-gitlab/. GitLab-hosted agent runs therefore never get scored, silently diverging from GitHub-hosted runs with no documentation of this gap.

Suggestion: Add the same fail-open eval-measure step to the GitLab CI template, or explicitly scope ADR 0087 / this PR as GitHub-first and note the GitLab gap as a follow-up in the ADR's Consequences or a tracking issue.

Comment thread internal/evalmeasure/export_local.go
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md Outdated
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md
SHA-pin agents@v0 measurement manifests in the eval-measure binary,
score only platform run-telemetry.jsonl, skip unknown scorers and
pre-script-skipped runs, and keep GitLab measure fail-open after a
failed agent run.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

E2E tests are running

Authorization passed for this commit. See the E2E Tests workflow for results.

@ascerra
ascerra requested a review from waynesun09 August 17, 2026 14:24
@ascerra

ascerra commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Pushed c8438d83 with Wayne’s HIGH/MEDIUM findings plus the follow-up squad fixes.

  • Manifest fetch is SHA-pinned in fullsend eval-measure (no floating v0 curl).
  • Scores only platform run-telemetry.jsonl at the top of each runDir.
  • Missing agent identity still scores (identity=fail); unknown scorers and pre-script skips are label: skip.
  • GitLab always measures after fullsend run (including failed runs), then exits with the run status.

Thread replies are on the review comments. Left threads unresolved for Wayne to re-review.

@ascerra ascerra left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline responses on the current diff (c8438d83) for Wayne’s eval-measure findings. Earlier replies were attached to the pre-push commit and GitHub marked those threads outdated.

Comment thread action.yml
Comment thread internal/evalmeasure/find.go
Comment thread internal/evalmeasure/registry.go
Comment thread internal/evalmeasure/fitness.go
Comment thread internal/evalmeasure/parse.go Outdated
Comment thread internal/evalmeasure/fitness.go
Comment thread internal/cli/evalmeasure.go
Comment thread internal/evalmeasure/export_local.go
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md Outdated
Comment thread docs/ADRs/0087-eval-measurements-online-trace-scoring.md

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review sweep on top of the c8438d8 fixes — 6 findings that verify against current source and are distinct from the threads already discussed/fixed in this PR (called out inline where a finding is a follow-up to an already-resolved issue in the same area).

Comment thread action.yml
# Eval measurements (fail-open): score run-telemetry.jsonl with the agents
# measurement manifest. Same job as fullsend run; never fails the agent.
# Scores always land in eval-measurements.jsonl (tool-agnostic artifact).
- name: Eval measurements

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Eval-measure step/scaffold never inject GH_TOKEN, so manifest GetRef runs unauthenticated

The "Eval measurements" step's env: block (action.yml:418-420) sets only AGENT and FULLSEND_DIR — unlike the preceding "Run fullsend" step (action.yml:379), which explicitly sets GH_TOKEN: ${{ inputs.github_token }}. evalMeasureFetchContext (internal/cli/evalmeasure.go:191) calls token, _ := resolveToken() and discards the error; resolveToken() (internal/cli/admin.go:91-106) checks GH_TOKEN, then GITHUB_TOKEN, then gh auth token, returning an error if none are set — which happens for this step. The resulting empty token is passed into gh.New(token) and used by fetchPinnedAgentsRepoFile (internal/cli/run.go:3751) to call forgeClient.GetRef(ctx, ..., "tags/"+config.DefaultUpstreamRef) against api.github.com. An unauthenticated GetRef is subject to GitHub's 60 req/hour per-IP limit, shared across the whole GitHub-hosted runner IP pool.

The identical gap exists in the new GitLab CI scaffold step (internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-agent.yml:310-320), which also invokes fullsend eval-measure --agent ... --fullsend-dir ... with no GITHUB_TOKEN/GH_TOKEN configured anywhere in that pipeline (only GITLAB_TOKEN is exported).

Because the whole step is fail-open (continue-on-error: true / || true), failures are silent StepWarn skips, so stock-agent measurement scoring will intermittently (GHA) or consistently (GitLab, no GitHub identity at all) fail to fetch the SHA-pinned manifest from fullsend-ai/agents@v0.

Note: this is distinct from the earlier reviewed-and-fixed concern about sending a Bearer token to the public raw.githubusercontent.com curl (that curl call is gone in c8438d83) — this is about the replacement GetRef API call having no token available at all.

Suggestion: add GH_TOKEN: ${{ inputs.github_token }} to the action.yml "Eval measurements" step's env block (mirroring "Run fullsend"), and document/accept explicitly that the GitLab scaffold step has no GitHub token available so stock-agent measurement fetch will reliably skip there unless an operator wires one in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3d5aefc6. The Eval measurements step now sets GH_TOKEN: ${{ inputs.github_token }} for GetRef of agents@v0 (not a Bearer header to raw.githubusercontent.com).

GitLab: documented skip — this job has no GitHub token, so stock-agent manifests skip unless an operator exports GH_TOKEN/GITHUB_TOKEN. Local .fullsend override still works. Empty-token path logs a warning (TestActionYML_EvalMeasureNoFloatingV0Curl asserts GH_TOKEN: on the step; GitLab test asserts stock-agent manifests skip).

│ └── url # Validate URLs against SSRF attacks
├── post-review # Post PR review comments to GitHub
├── post-comment # Post issue/PR comments to GitHub
├── eval-measure # Score wild-run traces (eval measurements)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] cli-internals.md lists eval-measure's --telemetry/--registry as unconditionally required

This doc documents only --telemetry <path> (required), --registry <path> (required), and --out-dir. The actual contract in internal/cli/evalmeasure.go is alternative flags: resolveEvalMeasureTelemetry (~line 120) errors only if both --telemetry and --output-dir are empty ("either --telemetry or --output-dir is required"), and resolveEvalMeasureRegistry (~line 130) errors only if both --registry and --agent are empty ("either --registry or --agent is required"). The doc omits --agent, --fullsend-dir, and --output-dir entirely, even though those are exactly the flags action.yml and the GitLab scaffold template actually pass (--agent, --fullsend-dir, --output-dir) — an operator following only this doc's tree would conclude the real managed-job invocation is invalid.

Suggestion: update the eval-measure entry to show both flag pairs as mutually-exclusive alternatives (--telemetry|--output-dir, --registry|--agent) and add the missing --agent/--fullsend-dir/--output-dir flags.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the merge (4ff94995 / 3d5aefc6). docs/guides/dev/cli-internals.md now lists both pairs: --telemetry or --output-dir, --registry or --agent, plus --fullsend-dir and --out-dir.

return ok && v != ""
}

func modelOK(run Span, agents []Span) bool {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] modelOK() treats empty-string gen_ai.request.model/gen_ai.system as present

modelOK checks _, ok := run.AttrString("gen_ai.request.model") / _, ok := a.AttrString("gen_ai.system") and treats ok alone as satisfying the check. AttrString (internal/evalmeasure/types.go:44-55) returns (v, true) for any present non-nil value, including the empty string "" — it only returns false when the key is absent or nil. This is inconsistent with attrNonEmpty/identityOK/workItemOK in this same file, which explicitly require v != "". As written, a span carrying gen_ai.request.model="" or gen_ai.system="" (effectively missing instrumentation) still satisfies the model sub-check in EM-001's 8-check contract.

Suggestion: reuse attrNonEmpty (or add an explicit v != "" check) for both gen_ai.request.model and gen_ai.system inside modelOK.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3d5aefc6. modelOK uses attrNonEmpty for gen_ai.request.model and gen_ai.system, so "" fails the model check. Test: TestScoreFitness_EmptyModelStringFails.

Comment thread internal/evalmeasure/parse.go
Comment thread internal/evalmeasure/find.go Outdated
Comment thread internal/evalmeasure/fitness.go
ascerra and others added 2 commits August 17, 2026 14:45
Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Inject GH_TOKEN for SHA-pinned agents@v0 GetRef, score only the host
runDir for --agent, reject empty gen_ai model/system strings, and warn
when telemetry JSONL lines are unreadable.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>

@ascerra ascerra left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline replies on 3d5aefc6 for Wayne’s second-round findings (also merged origin/main to clear conflicts).

Comment thread action.yml
Comment thread docs/guides/dev/cli-internals.md
Comment thread internal/evalmeasure/fitness.go
Comment thread internal/evalmeasure/parse.go
Comment thread internal/evalmeasure/find.go
Comment thread internal/evalmeasure/types.go
@ascerra

ascerra commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Merged origin/main (conflicts were cli-internals.md only) and pushed 3d5aefc6 for Wayne’s second-round findings.

  • HIGH GH_TOKEN on the eval-measure step for GetRef; GitLab documents stock-manifest skip without a GitHub token.
  • MEDIUM cli-internals flag pairs, empty gen_ai strings, corrupt JSONL warning, sibling runDir filter, gen_ai.* constants + ADR semconv note.

Inline replies are on the current diff.

@ascerra
ascerra requested a review from waynesun09 August 17, 2026 18:51
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:51 PM UTC · Completed 7:08 PM UTC

Commit: 3d5aefc · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The following inline comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • internal/evalmeasure/run.go:46: [low] data-quality-on-partial-failure

If AppendMeasurements succeeds but RecordScored fails, the measurement is persisted to eval-measurements.jsonl but the ledger is not updated. On retry, the same measurement is appended again (duplicate line). Documented in TestMeasureFile_AppendBeforeLedger; consumers can deduplicate on (trace_id, name, version).

  • internal/evalmeasure/export_local.go:51: [low] ledger-key-injection

The ledger key format is traceID|evalName|version. Registry validation rejects pipe and newline in evalName fields, but traceID comes from parsed OTLP data and is not validated for pipe or newline characters. A malformed traceID containing | could cause a false-positive match in AlreadyScored. In practice, OTLP trace IDs are 32-character hex strings, and FindPlatformTelemetry ensures only host-written files are scored.

  • internal/evalmeasure/find.go:37: [low] edge-case

FindPlatformTelemetry returns at most one telemetry path when searching child runDirs (the newest by modification time). If an outputDir legitimately contains telemetry from multiple agent runDirs for different agents in the same job, only the newest is scored. Documented and intentional.

  • internal/cli/evalmeasure.go (file-level): Line 643 · [low] naming-inconsistency

The CLI flag --out-dir uses a different naming convention than --output-dir. Both are defined on the same command with subtly different semantics (measurements output directory vs CI output base), creating potential confusion.

Suggested fix: Consider renaming --out-dir to --measurements-dir or --score-dir to distinguish it from --output-dir.

Comment thread internal/cli/evalmeasure.go Outdated
printer.StepWarn(fmt.Sprintf("%s: skipped %d of %d unreadable telemetry line(s)", p, stats.SkippedLines, stats.NonEmptyLines))
}
if err != nil {
return all, false, err

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] runEvalMeasure drops the failing telemetry file's already-scored/written results when returning an error

In the loop over telemPaths, evalmeasure.MeasureAndExport is called per file and, on a mid-loop failure (ledger-check/append-measurements/record-scored error), it returns its own partial results alongside the error (see internal/evalmeasure/run.go: MeasureAndExport appends each already-persisted EvaluationResult to its local all before hitting a failing step). But this caller's loop does return all, false, err here without first doing all = append(all, results...) — it only carries forward results from prior, already-succeeded iterations of the outer loop. Since RunE's error branch calls printMeasurementResults(printer, results, false) with this returned all, any measurements already written to eval-measurements.jsonl by the failing file's MeasureAndExport call are silently omitted from stdout, even though the file on disk has them.

This contradicts the earlier review threads on run.go (lines 37/39/41) claiming "Fixed in 509444e: CLI prints partial results before returning a write/ledger error" — those threads are still open, and commit 509444e4 is not an ancestor of the current PR head (it diverged, likely dropped in a rebase/squash before the multi-file telemPaths loop was introduced here). The existing test TestEvalMeasureCmd_ErrorDoesNotPrintWrote only asserts the "Wrote N measurement(s)" summary line is absent on error — it does not assert that already-scored rows from the failing file are actually printed, so it doesn't catch this gap.

Suggested fix: In the error branch, append the current call's partial results before returning, e.g. return append(all, results...), false, err, and add a regression test with 2+ traces in one telemetry file where the second trace's ledger/append/record step fails, asserting the first trace's result is present in the printed/returned results.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 61b9bae7. The error branch now returns append(all, results...), so already-persisted rows from the failing file stay in the CLI result slice and get printed. wroteOK stays false, so we still do not print Wrote N.

Regression: two traces in one telemetry file; the second persist is forced to fail after the first RecordScored. Tests: TestRunEvalMeasure_ErrorIncludesPartialResults, TestEvalMeasureCmd_ErrorPrintsPartialFromFailingFile, TestMeasureAndExport_KeepsFirstWhenSecondPersistFails.

Forward MeasureAndExport's already-scored results when a later
append/ledger write fails so stdout matches the JSONL on disk.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Adam Scerra <ascerra@redhat.com>

@ascerra ascerra left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reply on the persist-error thread (61b9bae7).

printer.StepWarn(fmt.Sprintf("%s: skipped %d of %d unreadable telemetry line(s)", p, stats.SkippedLines, stats.NonEmptyLines))
}
if err != nil {
return append(all, results...), false, err

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 61b9bae7: return append(all, results...), false, err so the failing file's already-scored rows are printed. Tests: TestRunEvalMeasure_ErrorIncludesPartialResults, TestEvalMeasureCmd_ErrorPrintsPartialFromFailingFile.

@ascerra
ascerra requested a review from waynesun09 August 18, 2026 01:58
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 1:58 AM UTC · Completed 2:00 AM UTC

Commit: 61b9bae · View workflow run →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/ci CI pipelines and checks component/docs User-facing documentation go Pull requests that update go code ready-for-merge All reviewers approved — ready to merge type/feature New capability request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants