Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/instruction-surfaces.json
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,18 @@
"review_owner": "z-shell maintainers",
"canonical_for": []
},
{
Comment thread
ss-o marked this conversation as resolved.
Resolved
"id": "decision-0024",
"path": "decisions/0024-benchmarks-observed-not-gated.md",
"kind": "decision",
"authority": "canonical-detail",
"consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"],
"tasks": ["architecture-decision"],
"file_patterns": ["**"],
"required": true,
"review_owner": "z-shell maintainers",
"canonical_for": []
},
{
"id": "decision-index",
"path": "decisions/README.md",
Expand Down
49 changes: 49 additions & 0 deletions decisions/0024-benchmarks-observed-not-gated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 24. Benchmarks Are Observed, Not Gated, With Flag Thresholds and Committed Per-Release Results
Comment thread
ss-o marked this conversation as resolved.
Resolved

- **Status:** ACCEPTED
- **Date:** 2026-09-18
- **Deciders:** ss-o
Comment thread
ss-o marked this conversation as resolved.
Resolved
- **Supersedes:** None
- **Superseded by:** None

## Context

Two repositories ship benchmarks with different shapes. `z-shell/zpmod` runs a Zsh harness that emits JSON, TSV, SVG, and Markdown, commits per-release results under `benchmarks/results/<tag>-<os>-<arch>/` (today `v2.0.6-linux-x86_64`), and states that hosted-runner results are evidence, not a blocking gate. `z-shell/z-a-meta-plugins` drives a baseline-versus-candidate comparison from Python on every pull request: alternating order, five warmups, thirty samples, median and p95, a functional failure invalidating the timings, and no timing thresholds. `z-shell/zi` is adding a third suite ([z-shell/zi#553](https://github.com/z-shell/zi/issues/553)) after a promotion review found a +20% ice-tokenizing regression only through an ad hoc A/B ([z-shell/zi#554](https://github.com/z-shell/zi/issues/554)).

Nothing in `decisions/`, `PATTERNS.md`, or the runbooks records how benchmark results relate to merging, which statistics a comparison must report, or where results are published. [ADR-0009](0009-testing-ci-strategy.md) settles the analogous question for coverage ("observed, not gated") but is silent on performance. Each repository has therefore decided alone, and the third suite would have been a third convention.

## Decision

1. **Observed, not gated.** A benchmark job never fails on timing. It reports, and the maintainer decides. This extends ADR-0009's coverage rule to performance and matches zpmod's stated stance on hosted runners.
2. **Flag thresholds for review.** A comparison marks a case for review when its median regresses by more than 10 percent or its p95 by more than 15 percent against the baseline measured in the same run. A flag is a notice on the job and the pull request, not a failure. Repositories may tighten the thresholds, not loosen them, and must record the values in their benchmark README.
3. **Comparison shape.** A comparison measures baseline and candidate in the same runner with alternating (balanced) order, discards warmup rounds, reports median, p95, minimum, and sample count per variant and the median and p95 deltas, and includes an A/A control (the baseline measured twice) so the noise floor is visible next to the real comparison. A functional failure on either side invalidates that case and fails the job, because timing a broken behaviour is meaningless.
4. **Determinism.** Benchmark workloads are network-free and fixture-driven. Network-dependent behaviour belongs in smoke or integration jobs, not in a comparison.
5. **Publication.** Per-release results are committed by a maintainer under `benchmarks/results/<tag>-<os>-<arch>/`, the naming zpmod already uses, after a release, together with the exact source revision, Zsh version, architecture, and runner or host identity. Automation uploads artifacts and job summaries; it never commits results. Results are compared only when the environment identity matches: Zsh version, architecture, and runner or host identity, plus equal sample and warmup counts. Source revisions are expected to differ; they are what a comparison is about.
6. **Canonical comparison schema, version 1.** A comparison report carries `schema_version: 1`, `baseline` and `candidate` identity (`label`, `source_revision`, `environment{zsh_version,architecture,cpu,runner_image}`, `workload{warmups,samples}`), a boolean `comparable` derived from those fields, and `cases[case]` where each case is either `{failure: {baseline, candidate}}` or `{results: {baseline, candidate}, change, flag}` with `results[variant] = {median, p95, min, count, samples}` and `change = {median_delta_ms, median_delta_percent, p95_delta_ms, p95_delta_percent}`; `control[case]`, present whenever the A/A control from decision 3 was measured, carries the same row shape with the control measurement in the `candidate` position, so the noise floor is exchangeable and validatable alongside the comparison; `flagged` and `failed` list the affected case names, `failed` covering control rows as well. `z-shell/zi` implements this schema. `z-a-meta-plugins` today emits `results[variant][metric]{median,p95,samples}` and `change[metric]{median_delta_ms,median_delta_percent}` per metric; it adapts to version 1 (adding `min`, `count`, and the p95 deltas, and lifting its per-metric nesting to one case per metric or an explicit `metrics` map in a schema revision) before the shape is admitted to `PATTERNS.md` as an observed two-repository pattern. Until then the pattern is proposed, not observed.

## Consequences

### Positive

- A performance regression becomes visible in the pull request that introduces it, with its size, without blocking unrelated work on hosted-runner noise.
- Three repositories share one vocabulary, so a reader can compare reports and reviewers know what a flag means.
- Committed per-release results give every release a performance record tied to an exact revision.

### Costs and risks

- A flag that nobody reads is a regression that ships. The weekly organization review ([`runbooks/org-review.md`](../runbooks/org-review.md)) lists flagged benchmark runs under "Needs review".
- Hosted runners are noisy; the A/A control makes that visible but does not remove it. Repeated flags on the control column mean the runner, not the change.
- The thresholds are chosen from the first zi A/B, where unrelated cases sat within ±1 percent and the real regression was +20 percent; they should be revisited after the first ten flagged runs.

## Alternatives considered

1. **Blocking above a threshold.** Rejected for now: without an A/A history, hosted-runner noise produces false timing failures on unrelated pull requests, and every one of them would have to be waved through by the maintainer bypass that ADR-0013 keeps available for deadlocks, turning an escape route into routine practice. Revisit once the control column has a track record.
2. **Report only, no thresholds.** Rejected: z-a-meta-plugins' experience is that unhighlighted tables are not read; the zi regression was found only because a human ran and read an A/B.
3. **One central benchmark runner for the organization.** Deferred: zpmod measures a compiled module and z-a-meta-plugins measures real plugin groups; the workloads differ too much for one runner, while the report shape can be shared.

## References

- [z-shell/.github#629](https://github.com/z-shell/.github/issues/629): the decision request.
- [z-shell/zi#553](https://github.com/z-shell/zi/issues/553): the zi suite; [z-shell/zi#554](https://github.com/z-shell/zi/issues/554): the measured ice-tokenizing regression; [z-shell/zi#555](https://github.com/z-shell/zi/issues/555): the manifest-reader cost recorded as a baseline and optimization target.
- `z-shell/zpmod` `benchmarks/README.md` and `z-shell/z-a-meta-plugins` `benchmarks/README.md`: the two existing conventions.
- [ADR-0009](0009-testing-ci-strategy.md): coverage observed, not gated.
51 changes: 26 additions & 25 deletions decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,29 @@ Check: python3 scripts/decision-records.py --check
Durable organization decisions. Draft new records with `runbooks/adr.md`; only
a maintainer moves a record from `PROPOSED` to `ACCEPTED`.

| ADR | Title | Status | Date | Deciders |
| ------------------------------------------------------- | -------------------------------------------------------------------------- | -------- | ---------- | -------- |
| [0001](0001-meta-repo-and-agents-md.md) | Adopt a meta-repo pattern centered on `AGENTS.md` | ACCEPTED | 2026-05-29 | ss-o |
| [0002](0002-zi-as-canonical-plugin-manager.md) | `zi` is the canonical plugin manager for the z-shell ecosystem | ACCEPTED | 2026-05-29 | ss-o |
| [0003](0003-conventional-commits.md) | Adopt Conventional Commits across z-shell repositories | ACCEPTED | 2026-05-29 | ss-o |
| [0004](0004-dependabot-unification.md) | Standardize on Dependabot for Dependency Management | ACCEPTED | 2026-05-20 | ss-o |
| [0005](0005-workflow-naming-conventions.md) | No Emojis in Workflow and Job Name Fields | ACCEPTED | 2026-05-21 | ss-o |
| [0006](0006-wiki-content-root-boundaries.md) | Wiki Content-Root Boundaries | ACCEPTED | 2026-05-29 | ss-o |
| [0007](0007-release-publication-flow.md) | Release and Publication Flow | ACCEPTED | 2026-05-26 | ss-o |
| [0008](0008-branching-model.md) | Branching Model | ACCEPTED | 2026-07-25 | ss-o |
| [0009](0009-testing-ci-strategy.md) | Testing and CI Strategy | ACCEPTED | 2026-07-25 | ss-o |
| [0010](0010-security-incident-response.md) | Security Incident Response | PROPOSED | 2026-05-29 | TBD |
| [0011](0011-zsh-lint-semantic-analyzer-architecture.md) | zsh-lint Conditional Semantic Analysis Pipeline | ACCEPTED | 2026-07-25 | ss-o |
| [0012](0012-hybrid-dependency-management.md) | Split Dependency Updates Between Renovate and Dependabot | ACCEPTED | 2026-06-21 | ss-o |
| [0013](0013-repository-settings-baseline.md) | Repository Settings Baseline by Class | ACCEPTED | 2026-07-25 | ss-o |
| [0014](0014-portable-agent-instruction-architecture.md) | Adopt portable agent-instruction delivery | ACCEPTED | 2026-07-23 | ss-o |
| [0015](0015-zsh-scripting-standard.md) | Adopt an organization-wide Zsh scripting standard | ACCEPTED | 2026-08-27 | ss-o |
| [0016](0016-promotion-trigger-criteria.md) | Next-to-Main Promotion Trigger Criteria | ACCEPTED | 2026-08-16 | ss-o |
| [0017](0017-licensing-standard-by-provenance.md) | Licensing Standard by Provenance and Consumption | ACCEPTED | 2026-08-18 | ss-o |
| [0018](0018-portable-worktree-management.md) | Adopt Portable Worktree Management | ACCEPTED | 2026-08-27 | ss-o |
| [0019](0019-trunk-on-main-default.md) | Trunk-on-Main Default with a Zi Integration Exception | ACCEPTED | 2026-08-28 | ss-o |
| [0020](0020-adopt-zsh-plugin-standard-2.md) | Adopt Zsh Plugin Standard 2 as a Clean Portable Contract | ACCEPTED | 2026-08-28 | ss-o |
| [0021](0021-derive-chroma-knowledge-at-runtime.md) | Derive Chroma Command Knowledge at Runtime | ACCEPTED | 2026-08-29 | ss-o |
| [0022](0022-issue-traceability-on-pull-requests.md) | Enforce Issue Traceability on the Pull Request, Not the Branch Name | ACCEPTED | 2026-09-02 | ss-o |
| [0023](0023-zsh-lint-parser-front-end-strategy.md) | zsh-lint Tracks Upstream, Fixes Locally, and Forks the Parser on a Trigger | ACCEPTED | 2026-09-15 | ss-o |
| ADR | Title | Status | Date | Deciders |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------- | ---------- | -------- |
| [0001](0001-meta-repo-and-agents-md.md) | Adopt a meta-repo pattern centered on `AGENTS.md` | ACCEPTED | 2026-05-29 | ss-o |
| [0002](0002-zi-as-canonical-plugin-manager.md) | `zi` is the canonical plugin manager for the z-shell ecosystem | ACCEPTED | 2026-05-29 | ss-o |
| [0003](0003-conventional-commits.md) | Adopt Conventional Commits across z-shell repositories | ACCEPTED | 2026-05-29 | ss-o |
| [0004](0004-dependabot-unification.md) | Standardize on Dependabot for Dependency Management | ACCEPTED | 2026-05-20 | ss-o |
| [0005](0005-workflow-naming-conventions.md) | No Emojis in Workflow and Job Name Fields | ACCEPTED | 2026-05-21 | ss-o |
| [0006](0006-wiki-content-root-boundaries.md) | Wiki Content-Root Boundaries | ACCEPTED | 2026-05-29 | ss-o |
| [0007](0007-release-publication-flow.md) | Release and Publication Flow | ACCEPTED | 2026-05-26 | ss-o |
| [0008](0008-branching-model.md) | Branching Model | ACCEPTED | 2026-07-25 | ss-o |
| [0009](0009-testing-ci-strategy.md) | Testing and CI Strategy | ACCEPTED | 2026-07-25 | ss-o |
| [0010](0010-security-incident-response.md) | Security Incident Response | PROPOSED | 2026-05-29 | TBD |
| [0011](0011-zsh-lint-semantic-analyzer-architecture.md) | zsh-lint Conditional Semantic Analysis Pipeline | ACCEPTED | 2026-07-25 | ss-o |
| [0012](0012-hybrid-dependency-management.md) | Split Dependency Updates Between Renovate and Dependabot | ACCEPTED | 2026-06-21 | ss-o |
| [0013](0013-repository-settings-baseline.md) | Repository Settings Baseline by Class | ACCEPTED | 2026-07-25 | ss-o |
| [0014](0014-portable-agent-instruction-architecture.md) | Adopt portable agent-instruction delivery | ACCEPTED | 2026-07-23 | ss-o |
| [0015](0015-zsh-scripting-standard.md) | Adopt an organization-wide Zsh scripting standard | ACCEPTED | 2026-08-27 | ss-o |
| [0016](0016-promotion-trigger-criteria.md) | Next-to-Main Promotion Trigger Criteria | ACCEPTED | 2026-08-16 | ss-o |
| [0017](0017-licensing-standard-by-provenance.md) | Licensing Standard by Provenance and Consumption | ACCEPTED | 2026-08-18 | ss-o |
| [0018](0018-portable-worktree-management.md) | Adopt Portable Worktree Management | ACCEPTED | 2026-08-27 | ss-o |
| [0019](0019-trunk-on-main-default.md) | Trunk-on-Main Default with a Zi Integration Exception | ACCEPTED | 2026-08-28 | ss-o |
| [0020](0020-adopt-zsh-plugin-standard-2.md) | Adopt Zsh Plugin Standard 2 as a Clean Portable Contract | ACCEPTED | 2026-08-28 | ss-o |
| [0021](0021-derive-chroma-knowledge-at-runtime.md) | Derive Chroma Command Knowledge at Runtime | ACCEPTED | 2026-08-29 | ss-o |
| [0022](0022-issue-traceability-on-pull-requests.md) | Enforce Issue Traceability on the Pull Request, Not the Branch Name | ACCEPTED | 2026-09-02 | ss-o |
| [0023](0023-zsh-lint-parser-front-end-strategy.md) | zsh-lint Tracks Upstream, Fixes Locally, and Forks the Parser on a Trigger | ACCEPTED | 2026-09-15 | ss-o |
| [0024](0024-benchmarks-observed-not-gated.md) | Benchmarks Are Observed, Not Gated, With Flag Thresholds and Committed Per-Release Results | ACCEPTED | 2026-09-18 | ss-o |
Loading