Skip to content

Commit a5f69a3

Browse files
authored
feat: add gh aw forecast command for projecting workflow effective token usage (experimental) (#31377)
1 parent b0dbcb7 commit a5f69a3

8 files changed

Lines changed: 2948 additions & 0 deletions

File tree

cmd/gh-aw/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
768768
lintCmd := cli.NewLintCommand()
769769
domainsCmd := cli.NewDomainsCommand()
770770
experimentsCmd := cli.NewExperimentsCommand()
771+
forecastCmd := cli.NewForecastCommand()
771772

772773
// Assign commands to groups
773774
// Setup Commands
@@ -802,6 +803,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
802803
healthCmd.GroupID = "analysis"
803804
checksCmd.GroupID = "analysis"
804805
experimentsCmd.GroupID = "analysis"
806+
forecastCmd.GroupID = "analysis"
805807

806808
// Utilities
807809
mcpServerCmd.GroupID = "utilities"
@@ -844,6 +846,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
844846
rootCmd.AddCommand(projectCmd)
845847
rootCmd.AddCommand(domainsCmd)
846848
rootCmd.AddCommand(experimentsCmd)
849+
rootCmd.AddCommand(forecastCmd)
847850

848851
// Fix help flag descriptions for all subcommands to be consistent with the
849852
// root command ("Show help for gh aw" vs the Cobra default "help for [cmd]").
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# ADR-31377: Monte Carlo Projection for `gh aw forecast` Command
2+
3+
**Date**: 2026-05-10
4+
**Status**: Draft
5+
**Deciders**: Unknown (PR authored by `app/copilot-swe-agent`; human deciders TBD)
6+
7+
---
8+
9+
## Part 1 — Narrative (Human-Friendly)
10+
11+
### Context
12+
13+
Users of `gh-aw` want to project the future cost and yield of their agentic workflows before scheduling them at higher cadence or rolling them out organization-wide. Historical run data is highly variable: per-run effective token usage can vary by an order of magnitude depending on agent decisions, runs per period follow a counting process, and not every run succeeds. A naive point estimate (e.g. `avg(tokens) × avg(runs/period)`) hides this uncertainty and tends to under-state tail risk. The command must also integrate with existing analysis infrastructure (episode classification, A/B experiment variant tracking, JSON output for agent consumers) and remain useful on small samples (≤30 days of history).
14+
15+
### Decision
16+
17+
We will introduce a new **experimental** `gh aw forecast` CLI command that projects per-workflow effective token usage using **Monte Carlo simulation** (10 000 trials) rather than a single point estimate. Each trial composes three independent sources of uncertainty — Poisson-distributed run counts, bootstrap-resampled per-run effective tokens, and Bernoulli-distributed success — and the aggregated trials yield P10/P50/P90 confidence intervals. The command lives in `pkg/cli/forecast*.go`, reuses the existing `buildEpisodeData` engine from `logs_episode.go` for episode analysis, supports remote repositories via `--repo`, and is gated as experimental (stderr warning + `(experimental)` short description) because the interface and statistical assumptions may change.
18+
19+
### Alternatives Considered
20+
21+
#### Alternative 1: Point estimates from historical averages
22+
23+
Compute `mean(effective_tokens) × mean(runs_per_period) × success_rate` and report a single projected number per workflow. Simple, deterministic, and cheap. Rejected because it hides variance, gives users no way to reason about tail risk (which is the operationally interesting question for cost budgeting), and makes side-by-side comparisons across workflows misleading when their variance profiles differ.
24+
25+
#### Alternative 2: Closed-form analytical distribution (e.g. compound Poisson)
26+
27+
Model run count as Poisson(λ) and per-run tokens as a parametric distribution (lognormal, gamma) and derive percentiles analytically. More elegant and faster than simulation. Rejected because the historical token distribution is typically multi-modal (different agent paths produce qualitatively different cost profiles) and ill-suited to a single parametric family; bootstrap resampling preserves the empirical shape without forcing a fit. Closed form also makes per-variant A/B splits and success-rate composition awkward.
28+
29+
#### Alternative 3: Reuse the existing `audit` command and add a `--forecast` flag
30+
31+
Extend the audit command instead of creating a new top-level command. Rejected because forecasting has a different mental model from auditing (forward projection vs. retrospective analysis), a different input shape (workflow IDs vs. run IDs), and different output structure (per-period projections vs. per-run metrics). Bundling them would muddy both commands' interfaces.
32+
33+
### Consequences
34+
35+
#### Positive
36+
- Users get P10/P50/P90 intervals, exposing tail risk that point estimates would hide.
37+
- Bootstrap resampling preserves the empirical token distribution without imposing a parametric model.
38+
- JSON output (`monte_carlo` field) gives downstream agents structured access to the full distribution summary.
39+
- Reuse of `buildEpisodeData` avoids duplicating episode-classification logic and keeps semantics consistent with `logs`/`audit`.
40+
- Experimental gating lets us iterate on the statistical model (e.g. switching distributions, adjusting trial count) without a stability commitment.
41+
42+
#### Negative
43+
- Monte Carlo introduces nondeterminism in output — two consecutive runs on the same data produce slightly different P50/P10/P90 values unless a seed is pinned. This complicates regression testing and snapshot comparisons.
44+
- 10 000 trials × N workflows × bootstrap sampling adds CPU cost; the Poisson sampler has two regimes (Knuth exact for λ ≤ 15, Normal approximation otherwise) to stay within ~10 ms/workflow, but this adds complexity vs. a closed-form approach.
45+
- Episode counts for orchestrator-style workflows are a lower-bound estimate because `AwContext` (dispatch/workflow_call) lineage is unavailable without artifact downloads, which the command intentionally skips for speed.
46+
- Remote-repo mode (`--repo`) degrades frontmatter metadata to empty since Markdown source is local-only, creating a subtle behavior split between local and remote forecasts.
47+
- Adds three new files in `pkg/cli/` (forecast_command.go, forecast.go, forecast_montecarlo.go) plus tests, increasing maintenance surface in an already large package.
48+
49+
#### Neutral
50+
- The `--days` flag is capped at 30, which is a deliberate sampling-window choice; longer windows would require pagination changes in `gh run list`.
51+
- The W3C-style specification at `docs/src/content/docs/reference/forecast-specification.md` (sidebar order 1355) commits us to keeping spec and implementation in sync while the command is experimental.
52+
- Trial count (10 000) is currently hardcoded; making it configurable is a future option but not part of this decision.
53+
54+
---
55+
56+
## Part 2 — Normative Specification (RFC 2119)
57+
58+
> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).
59+
60+
### Projection Algorithm
61+
62+
1. The `forecast` command **MUST** project per-workflow effective token usage using Monte Carlo simulation, not a single point estimate.
63+
2. The simulation **MUST** run at least 10 000 independent trials per workflow per forecast invocation.
64+
3. Each trial **MUST** compose three independent random variables: run count drawn from a Poisson process, per-run effective tokens drawn by bootstrap resampling of historical observations, and per-run success drawn as a Bernoulli with the historical success rate.
65+
4. The Poisson sampler **MUST** use Knuth's exact algorithm when λ ≤ 15 and **MUST** use a Normal approximation when λ > 15.
66+
5. The command **MUST** report P10, P50, and P90 effective-token percentiles in both the console table and JSON output.
67+
6. The command **MUST NOT** emit only a point estimate without accompanying P10/P90 bounds.
68+
69+
### Command Interface
70+
71+
1. The command **MUST** be registered in the `analysis` command group as `gh aw forecast`.
72+
2. The command **MUST** be marked experimental: its Cobra short description **MUST** include the literal substring `(experimental)`, and it **MUST** print an experimental warning to stderr at runtime.
73+
3. The `--days` flag **MUST** accept only the values `7` and `30`; values outside this set **MUST** be rejected with a clear error.
74+
4. The `--json` flag **MUST** emit the full `ForecastResult` struct including a `monte_carlo` object with `mean_projected_effective_tokens`, `std_dev_effective_tokens`, and P10/P50/P90 fields.
75+
5. The command **MAY** accept multiple workflow IDs as positional arguments; when omitted, it **MUST** forecast all agentic workflows discoverable in the target repository.
76+
6. When `--repo owner/repo` is supplied, workflow discovery **MUST** use the GitHub API (`fetchGitHubWorkflows`) and **MUST NOT** read local `.lock.yml` files for that invocation.
77+
7. Workflow ID matching against remote repositories **MUST** be case-insensitive against both display names and file-path basenames.
78+
79+
### Episode Analysis
80+
81+
1. Episode grouping **MUST** reuse `buildEpisodeData` and `classifyEpisode` from `logs_episode.go`; it **MUST NOT** reimplement episode classification.
82+
2. Because no artifacts are downloaded, episode linkage **MUST** rely only on GitHub Actions API fields (`event`, `headSha`, `headBranch`) and **MUST** gracefully degrade when `AwContext` is unavailable.
83+
3. The console output **SHOULD** display an episode breakdown table only when `runs/episode > 1` (i.e. orchestrator-style workflows).
84+
85+
### Frontmatter and Variants
86+
87+
1. When forecasting local workflows, the command **MUST** surface active trigger types and concurrency configuration from each workflow's Markdown frontmatter.
88+
2. When forecasting via `--repo`, frontmatter-derived fields **MAY** be empty without causing the forecast to fail.
89+
3. When a workflow defines A/B experiment variants, run counts and fractions **MUST** be reported per variant in both console and JSON output.
90+
91+
### Conformance
92+
93+
An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. Failure to meet any **MUST** or **MUST NOT** requirement constitutes non-conformance.
94+
95+
---
96+
97+
*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/25642964043) workflow. The PR author must review, complete, and finalize this document before the PR can merge.*

0 commit comments

Comments
 (0)