Repository navigation
Description
Analysis
gh aw experiments analyze gets its per-run measurements from only two sources: grader artifacts and eval answers. The function that loads them is loadMetricObservationSetsForAnalysis (experiments_command.go#L286). The compiler recognizes only eval: and grader: metric references (compiler_experiments.go#L428, #L453). Graders read only the run's own trace and have no network access, so they can't see a merge that happens after the run.
daily-harness-experiment-proposer tells the agent to prefer "a labeled/merged PR outcome" as the primary metric (L274). It also requires guardrails to be grader: or eval: (L282). The analyzer can't measure an outcome metric, so the experiment stays at EXTEND. As a result, the proposer's PROMOTE/REJECT ledger can only learn from signals measured during the run.
Outcome evaluation already exists and is deterministic: collectRunOutcomes (logs_outcomes.go#L68) and evalCreatePullRequest (outcome_eval_pr.go#L16). This issue proposes feeding those results into experiment analysis.
Outcome lag is short but varies by workflow
I checked the last 1,000 PRs opened by gh-aw workflows in this repo (author app/github-actions, 2026-08-08 to 2026-11-04):
- Time to merge: median 1.7 h, 90th percentile 8.5 h, maximum 38 h.
- 97.2% were merged or closed within 24 h, and 99.9% (999 of 1,000) within 72 h.
Across the 25 workflows with at least 10 PRs, the 95th-percentile time to decision ranges from 2.2 h (ci-coach, smoke-multi-pr) to 48.7 h (jsweep). Repos with slower human review will differ even more. No single fixed window fits everyone. Too long delays analysis for fast workflows. Too short scores slow-but-good PRs as not accepted. The window should be learned per workflow from its own outcome data.
Many workflows merge more than 90% of their PRs, which leaves little room for accepted to separate variants. zero_touch is the stricter metric for those workflows.
Proposed semantics
| Metric | Per-run value |
|---|---|
outcome:accepted |
accepted outputs ÷ resolved outputs |
outcome:zero_touch |
zero-touch outputs ÷ resolved outputs. This is a subset of accepted: merged with no non-bot comments, reviews, or commits after the PR was created. |
"Outputs" means safe outputs that produce an outcome status (see rule 3). "Resolved" means accepted, rejected, or still pending after the window (see rule 2).
- Maturity window (learned). Only runs older than the window W are measured. Younger runs are reported as
immatureand don't count towardmin_samples. By default (outcome_window: auto), W is learned per workflow at analyze time:- Estimate time to decision from the experiment's own outcome-bearing outputs, with all variants pooled so the window doesn't favor one variant.
- Treat outputs that are still pending as censored, using a Kaplan–Meier estimate, rather than dropping them. Dropping them biases W short, because the slowest outputs are exactly the ones still open.
- Set W to the time by which an estimated 95% of outputs are decided, clamped to between 24 h and 30 days.
- If there are fewer than 20 decided outputs, fall back to 72 h. An explicit duration such as
outcome_window: 7doverrides learning. - Report W, its source (
learned,configured, ordefault), and the sample it came from in the analyze output.
- Stale pending. An output still
pendingafter the window counts as not accepted (0) and appears in the coverage report. - Exclusions.
unknownanderrorare excluded and reported as unavailable, the same way evalUNKNOWNanswers are.lifecycle,lifecycle_close,ignored, andskippeddon't produce an outcome status. - No-op runs. Runs with no outputs that produce an outcome status are excluded and reported per variant. This prevents a variant from winning by doing less.
- Zero-touch evidence. Today
ZeroTouchisfalseboth when a human touched the PR and when the supporting evidence was incomplete (outcome_eval_pr.go#L128). This metric must exclude incomplete evidence instead of scoring it 0. - CLI-only. Outcomes are evaluated when
experiments analyzeruns. This matches ADR-70205, which uses no scheduled collector.
Implementation plan
Please implement the following changes:
- Compiler (
pkg/workflow/compiler_experiments.go,ExperimentConfig,pkg/parser/schemas/main_workflow_schema.json):- Add
ParseExperimentMetricOutcomeReference. It should acceptoutcome:acceptedandoutcome:zero_touchinmetric,secondary_metrics, andguardrail_metrics. - Reject unknown outcome kinds using the error style "[what's wrong]. [what's expected]. [example]". Example:
unknown outcome metric "outcome:merged". Expected outcome:accepted or outcome:zero_touch. Example: metric: outcome:accepted. - Add an optional
outcome_window:auto(default) or a duration.
- Add
- Observations (new
pkg/cli/experiments_outcome_observations.go, modeled onexperiments_eval_observations.go):- For each assigned run in the assignment history, call
collectRunOutcomes. - Add
estimateOutcomeWindow, a pure function that implements rule 1: Kaplan–Meier over the pooled decided and censored times, a 95% target, clamping, and the cold-start fallback. Then apply rules 1–5. - Produce one
{run_id, variant, value}observation per run, using the existing observation-set type. - Merge the results in
loadMetricObservationSetsForAnalysis, for both the primary metric and guardrails.
- For each assigned run in the assignment history, call
- Zero-touch evidence (
pkg/cli/outcome_eval_pr.go,OutcomeReportinpkg/cli/outcome_eval.go): record whether the zero-touch evidence was complete, so rule 5 can tell incomplete evidence apart from a PR a human touched. - Analysis and reporting:
- Reuse the existing statistics. When
analysis_typeis absent, apply the R-STAT-004 defaults: the two-proportion test when every per-run value is 0 or 1, and Welch's t-test otherwise. - Report the window used (W, its source, and the sample size) and per-variant coverage (
immature,stale_pending,unavailable,no_op) in both text and--jsonoutput.
- Reuse the existing statistics. When
- Proposer (
.github/workflows/daily-harness-experiment-proposer.md): allowoutcome:acceptedandoutcome:zero_touchas primary and guardrail metrics. - Tests:
- Fixtures for mature and immature runs, merged, closed, and stale-pending PRs, no-op runs, runs that create several PRs, and zero-touch with incomplete evidence.
estimateOutcomeWindowtests: censored pending outputs, the cold-start fallback, clamping at both bounds, and an explicit override.- Compiler validation tests for valid and invalid
outcome:references and foroutcome_window(auto, a valid duration, and an invalid value).
- Docs: update
docs/src/content/docs/experimental/experiments.mdandexperiments-specification.md. - Follow guidelines: run
make agent-finish.
Out of scope
- Post-merge reverts and escaped defects for
create_pull_request. That can be a separate follow-up. - Scheduled or background outcome collection.
Activity
pelikhan commentedon Nov 4, 2026
@copilot make maturity window configurable
jkupaMSFT commentedon Nov 4, 2026
Updated the plan based on a caveat from @pelikhan: the right window differs from one repo to another, so it should be learned rather than fixed at 72 h.
The data supports this even within this repo. Across the 25 workflows with at least 10 PRs, the time by which 95% of PRs are merged or closed ranges from 2.2 h (
ci-coach) to 48.7 h (jsweep).Changes to the body:
outcome_windownow defaults toauto. The window is learned per workflow at analyze time as the 95th-percentile time to decision, with all variants pooled.estimateOutcomeWindowand its tests to the plan.