The ACMM advisor is a pure, read-only computation that answers one question: has this hive earned progression to the next ACMM level? It implements the "you earn automation with test coverage" thesis from the CNCF reference architecture (src/docs/cncf-reference-architecture.md:174) as a concrete, auditable checklist, and is exposed over the dashboard API as GET /api/acmm-recommendation (see the API reference).
This page documents what exists in pkg/acmmadvisor today. It is advisory-only and is not currently wired into the dashboard UI — see What this does NOT do below.
The advisor evaluates a fixed set of six signals (Signals struct, src/pkg/acmmadvisor/acmmadvisor.go:124-144):
| Signal | Meaning |
|---|---|
CurrentLevel |
The ACMM level the hive is applying right now (1–6). |
CoveragePct |
Current test-coverage percentage (0–100). |
GreenStreak |
Count of consecutive green CI runs with no red, measured from default-branch Actions history. See below. |
MergeSuccessRate |
Fraction (0.0–1.0) of recent PRs that merged cleanly. |
ActionableIssues |
Count of open actionable issues the hive has surfaced but not yet resolved. |
HoldCount |
Count of open PRs still carrying a hold label awaiting human review. |
HasQualityAgent |
Whether an active quality/coverage agent is present in the config. |
Levels run L1 (Assisted) through L6 (Fully Autonomous), matching the level pack files and the dashboard's 1–6 bound (src/pkg/acmmadvisor/acmmadvisor.go:34-42). The advisor never proposes a target more than one level above the current level — advancement is always incremental (levelStep = 1, same file).
The dashboard assembles Signals for the running hive in buildACMMStatusInputs (src/pkg/dashboard/api_acmm_recommendation.go:47-106):
CurrentLevelandHasQualityAgentcome from the live config (detectACMMLevel, presence of an agent namedqualityincfg.Agents, viahasQualityAgentatsrc/pkg/dashboard/api_acmm_recommendation.go:126-132).MergeSuccessRateis read from the fleet-stats collector's cached 90-day merged/rejected counts (mergeSuccessRateFromFleetStats,src/pkg/dashboard/api_acmm_recommendation.go:135-141) — no fresh GitHub call is made on the request path.ActionableIssuesandHoldCountcome from the most recent published status snapshot (status.Governor.Issues,status.Hold.Total).CoveragePctis read fromstatus.AgentMetrics["ci-maintainer"]["coverage"], the value the coverage badge collector populates (coverageFromAgentMetrics,src/pkg/dashboard/api_acmm_recommendation.go:164-182).GreenStreakis the count of consecutive non-red completed runs on the primary repo's default branch, counting back from the most recent run (Client.GreenCIStreak,src/pkg/github/health.go). It is measured on the status-build path — the same pass that already fetches workflow health — and cached, so no fresh GitHub call is made on the request path. When no measurement has ever succeeded (no GitHub client, an Actions API failure, or a repo with no CI history at all) the signal stays at zero and reads as unknown / not yet earned rather than as a measured zero — a repo with no CI must never read as green. A failed refresh leaves the last real reading in place rather than clobbering it with an unknown.
All of the above is nil-safe: a freshly-booted hive with no config, no status snapshot yet, or a nil fleet-stats collector collapses to conservative zero-value signals rather than panicking or fabricating data (comment at src/pkg/dashboard/api_acmm_recommendation.go:41-45, confirmed by TestHandleACMMRecommendationEmpty in src/pkg/dashboard/api_acmm_recommendation_test.go).
The advisor computes the checklist for advancing to the level one above the current one. Coverage floors rise monotonically and are anchored on the project's own enforced 90% coverage gate (coverageGate = 90.0, src/pkg/acmmadvisor/acmmadvisor.go:52-63):
| Target level | Quality agent | Coverage floor | Green-CI streak | Merge success rate | Actionable-issue ceiling | Open-holds ceiling |
|---|---|---|---|---|---|---|
| L2 (Instructed) | required | — | — | — | — | — |
| L3 (Measured) | required | ≥ 40% | ≥ 3 | — | — | — |
| L4 (Adaptive) | required | ≥ 60% | ≥ 5 | ≥ 70% | — | — |
| L5 (Semi-Automated) | required | ≥ 75% | ≥ 8 | ≥ 85% | ≤ 25 | ≤ 15 |
| L6 (Fully Autonomous) | required | ≥ 90% | ≥ 12 | ≥ 95% | ≤ 10 | ≤ 5 |
Source: criteriaForTarget and the threshold constants in src/pkg/acmmadvisor/acmmadvisor.go:32-104,259-338. All thresholds are compile-time constants; there is no config field or environment variable that overrides them (see What this does NOT do).
If a hive is already at L6, the advisor reports stay with Ready: true and a rationale stating there is nothing higher to propose (Recommend, src/pkg/acmmadvisor/acmmadvisor.go:206-217).
A recommendation (Recommendation struct, src/pkg/acmmadvisor/acmmadvisor.go:178-197) has:
Advise—"stay"or"raise"."raise"is only ever emitted when every criterion for the target level is met.CurrentLevel/TargetLevel— the evaluated levels. WhenAdviseis"stay", they are equal (except at the L6 ceiling case above, where target also equals current).Ready— whether every criterion forTargetLevelis met.Met/Unmet— the satisfied and unsatisfied criteria as a renderable checklist. EachCriterion(src/pkg/acmmadvisor/acmmadvisor.go:161-174) carries a human label, a comparator (>=,<=, oris), the required and actual values formatted for display, whether it passed, and a one-line rationale for why the criterion exists.Rationale— a human-readable summary, e.g."Stay at L2. 2 of 3 criteria for L3 not yet met: Green-CI streak, Test coverage."
- It never changes the applied ACMM level. The package comment states this explicitly: "This package is ADVISORY ONLY. It never changes the applied ACMM level... A human always approves the actual level change." (
src/pkg/acmmadvisor/acmmadvisor.go:12-17). The only code path that changes a hive's level isPUT /api/packs/level→handlePackSetLevel→ApplyPack, documented in ACMM policy matrix — Changing a hive's ACMM level. Nothing inpkg/acmmadvisoror the/api/acmm-recommendationhandler calls that path. - It is a pure function with no I/O.
RecommendandRecommendFromStatustake already-collected signals and return a value; they do not read config, call GitHub, or touch the clock. Signal collection does talk to GitHub, but only on the status-build path, and its results are cached — neither the API endpoint nor the status payload triggers a GitHub call of its own. - It never auto-applies a recommendation. The recommendation now travels on the status payload as
acmmAdvice(#5225) in addition to the API endpoint, but both are read-only advice. Nothing consumes them to change a level. - Thresholds are not configurable. All coverage/streak/rate/backlog numbers are Go constants in
pkg/acmmadvisor; there is nohive.yamlfield or environment variable to adjust them. - It does not measure streak quality, only streak length.
GreenStreakresets on any red, including a flake unrelated to code quality, and a repo that rarely commits can hold a stale streak indefinitely because the signal is count-based rather than time-based. The scan also reads at most one page of runs (ciStreakRunsLimit), so a very long streak is reported as "at least N" — the cap sits above every advisor threshold, so it can only ever under-report.
GET /api/acmm-recommendation returns the JSON-encoded Recommendation computed from the hive's live signals. See the full request/response contract in the API reference.
- Whether the streak should become time-based ("days since last red") rather than count-based is unresolved. A count-based streak lets a quiet repo hold a stale value; a time-based one would change the meaning of the existing
greenStreakL3..L6thresholds, so it is deliberately not attempted here. - Whether a "green" streak should require the run to have actually executed gates is unresolved:
skippedruns are currently transparent to the streak (matchingciPassRate), so a repo whose workflows are entirely skipped on the default branch could accumulate a streak without any gate ever running.