M004 — Performance is executable evidence, and the hot path does no canonical work twice
Every public command can be benchmarked from the command registry with cold and warm distributions, the phases and work counters of any run can be printed, the caches and batch reads that remove repeated work preserve every observable result, an accepted baseline lives in the repository and a check refuses a regression by policy, and the pre-commit hook is fast enough that nobody routes around it.
ACTIVE 11 of 27 issues done
- ready
- 6
- blocked
- 10
- active
- 0
- verify
- 0
- done
- 11
- cancelled
- 0
Problem
The shell tool is slow enough to be avoided: doctor takes ten seconds and runs on every commit through the pre-commit hook, watch takes the same, knowledge nodes six seconds, plan validate four, the site-data check and a single test case half a minute, and the suite half an hour. Nobody could say which phase paid for it, because nothing measured phases; the two validators that cost two thirds of doctor are loops of one awk process per key over files that were already flattened once. The Rust executable measured at the same time is fast, so the slowness is the shell tool's own.
Outcome
Every public command can be benchmarked from the command registry with cold and warm distributions, the phases and work counters of any run can be printed, the caches and batch reads that remove repeated work preserve every observable result, an accepted baseline lives in the repository and a check refuses a regression by policy, and the pre-commit hook is fast enough that nobody routes around it.
Current state
MJ_TIMING prints per-validator phases and two work counters (feature/performance-truth 77f5d79). No benchmark command, no baseline, no regression check, no parse cache, no performance doctrine; the numbers above are the measured baseline on this repository.
Desired state
majordomus bench derives its targets from share/commands.yaml, records p50, p90, p95, p99, min, max, mean and stddev per command and mode, persists local runs under .ai/local/benchmarks/, compares against .ai/repo/benchmarks/baseline.json under the policy's benchmark block, and exits non-zero on a regression. Registry, catalogue, project-model and coverage validators read each flat file once. Flattened YAML is cached under .ai/local/cache/ by content hash. Project rules state the doctrine, docs/PERFORMANCE.md explains it, and CI runs the structural checks.
Scope
- lib
- bin/majordomus
- share/commands.yaml
- share/allow
- share/standard/majordomus/
- share/skeleton
- .ai/repo/rules/project
- .ai/repo/benchmarks
- docs
- scripts
- test/cases
- test/lib.sh
- .github/workflows/validate.yml
Out of scope
- Any change to apps/majordomus-cli, whose measured latency is not the problem
- A metrics database, a dashboard, a tracing backend or any telemetry service
- Hot reload or filesystem watching
- A persistent capability result cache across processes beyond the flatten cache
- Benchmarking external services
Acceptance criteria
- Every public command in share/commands.yaml is a benchmark target without a second list anywhere
- Cold and warm distributions are recorded separately with p50, p95 and p99 and never mixed
- A local run is persisted under .ai/local/benchmarks/ and never treated as a baseline
- The committed baseline is updated only by an explicit command on a clean tree
- bench --check exits non-zero on a regression over the policy thresholds and names the command, the metric, both values and the threshold
- Every canonical file is flattened at most once per command, proved by the work counters in a case
- A cache hit and a cache miss produce byte-identical command output for every read-only command, proved by a case
- doctor on this repository runs under the budget the policy declares, and the pre-commit hook inherits it
- The performance rules are project rules under .ai/repo/rules/project, and docs/PERFORMANCE.md is the reference the README points at
Validation
- bash test/run.sh
- bin/majordomus doctor
- bin/majordomus watch
- bin/majordomus bench --check
- scripts/generate-site-data --check
- shellcheck -x -s bash bin/majordomus lib/*.sh
Evidence required
- phase_timing
- batch_reads
- flatten_cache
- bench_targets_derived
- bench_persisted
- baseline_explicit
- regression_refused
- doctor_under_budget
- docs_integrated
Risks
- A cache is a second thing that can be believed. The flatten cache is keyed by the content hash of the file it caches, so a stale entry cannot be read for a changed file; it can only cost a miss.
- Wall-clock thresholds are noisy across machines. The regression policy is conservative and relative, the structural counters are exact, and only the counters gate CI strictly.
- Batching reads changes how validators are written. Each rewrite is proved by the case that already exists for the validator, run before and after, plus the counter case.
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I0817 | DONE | 0 | — | Phase timing and work counters behind MJ_TIMING |
| I0818 | DONE | 1 | I0817 | The command registry is read in one pass |
| I0819 | DONE | 1 | I0817 | The project model is loaded with one flatten per file and no per-file lookups |
| I0820 | READY | 2 | I0818, I0819 | Catalogue, DAG, context and command-surface validators read their inputs once |
| I0821 | READY | 1 | I0817 | Flattened YAML is cached under .ai/local/cache by content hash |
| I0822 | BLOCKED | 3 | I0818, I0819, I0820 | Every canonical file is flattened at most once per command |
| I0823 | DONE | 1 | I0817 | majordomus bench: targets derived from the command registry |
| I0824 | DONE | 2 | I0823 | Bench persists local runs and keeps a latest projection |
| I0825 | DONE | 3 | I0824 | An explicit committed baseline |
| I0826 | DONE | 4 | I0825 | bench --check refuses a regression by policy |
| I0827 | DONE | 2 | I0823 | The benchmark policy block |
| I0828 | BLOCKED | 3 | I0818, I0819, I0820, I0827 | doctor under a declared budget, and the hook inherits it |
| I0829 | DONE | 0 | — | Performance doctrine as project rules |
| I0830 | DONE | 5 | I0823, I0826, I0829 | docs/PERFORMANCE.md, README, CONTRIBUTING and CLI reference |
| I0831 | BLOCKED | 5 | I0822, I0826 | Performance claims at their proven status |
| I0832 | READY | 2 | I0817, I0819 | generate-site-data instrumented and its hot spots removed |
| I0833 | DONE | 1 | I0817 | knowledge nodes and edges read each source once |
| I0834 | BLOCKED | 4 | I0828 | The test runner reports per-case time and the suite budget |
| I0835 | BLOCKED | 5 | I0822, I0826 | CI runs the structural performance checks |
| I0836 | READY | 2 | I0819 | A synthetic repository generator for scaling cases |
| I0837 | READY | 1 | I0817 | Timing report as JSON |
| I0838 | READY | 5 | I0826 | bench compare and the top regressions report |
| I0839 | BLOCKED | 4 | I0828 | watch inherits every doctor fix and its own budget |
| I0840 | BLOCKED | 3 | I0820, I0821 | context and its subcommands under a second |
| I0841 | BLOCKED | 2 | I0821 | Cache equivalence proved by property-shaped cases |
| I0842 | BLOCKED | 3 | I0818, I0819, I0820, I0832, I0833 | Before and after evidence for every optimisation |
| I0843 | BLOCKED | 6 | I0830, I0831, I0835, I0841, I0842 | The performance workflow is discoverable and complete |
The graph
flowchart LR
I0817["I0817<br/>Phase timing and work counters behind MJ_TIMING"]:::done
I0818["I0818<br/>The command registry is read in one pass"]:::done
I0819["I0819<br/>The project model is loaded with one flatten per file and no per-file lookups"]:::done
I0820["I0820<br/>Catalogue, DAG, context and command-surface validators read their inputs once"]:::ready
I0821["I0821<br/>Flattened YAML is cached under .ai/local/cache by content hash"]:::ready
I0822["I0822<br/>Every canonical file is flattened at most once per command"]:::blocked
I0823["I0823<br/>majordomus bench: targets derived from the command registry"]:::done
I0824["I0824<br/>Bench persists local runs and keeps a latest projection"]:::done
I0825["I0825<br/>An explicit committed baseline"]:::done
I0826["I0826<br/>bench --check refuses a regression by policy"]:::done
I0827["I0827<br/>The benchmark policy block"]:::done
I0828["I0828<br/>doctor under a declared budget, and the hook inherits it"]:::blocked
I0829["I0829<br/>Performance doctrine as project rules"]:::done
I0830["I0830<br/>docs/PERFORMANCE.md, README, CONTRIBUTING and CLI reference"]:::done
I0831["I0831<br/>Performance claims at their proven status"]:::blocked
I0832["I0832<br/>generate-site-data instrumented and its hot spots removed"]:::ready
I0833["I0833<br/>knowledge nodes and edges read each source once"]:::done
I0834["I0834<br/>The test runner reports per-case time and the suite budget"]:::blocked
I0835["I0835<br/>CI runs the structural performance checks"]:::blocked
I0836["I0836<br/>A synthetic repository generator for scaling cases"]:::ready
I0837["I0837<br/>Timing report as JSON"]:::ready
I0838["I0838<br/>bench compare and the top regressions report"]:::ready
I0839["I0839<br/>watch inherits every doctor fix and its own budget"]:::blocked
I0840["I0840<br/>context and its subcommands under a second"]:::blocked
I0841["I0841<br/>Cache equivalence proved by property-shaped cases"]:::blocked
I0842["I0842<br/>Before and after evidence for every optimisation"]:::blocked
I0843["I0843<br/>The performance workflow is discoverable and complete"]:::blocked
I0817 --> I0818
I0817 --> I0819
I0817 --> I0821
I0817 --> I0823
I0817 --> I0832
I0817 --> I0833
I0817 --> I0837
I0818 --> I0820
I0818 --> I0822
I0818 --> I0828
I0818 --> I0842
I0819 --> I0820
I0819 --> I0822
I0819 --> I0828
I0819 --> I0832
I0819 --> I0836
I0819 --> I0842
I0820 --> I0822
I0820 --> I0828
I0820 --> I0840
I0820 --> I0842
I0821 --> I0840
I0821 --> I0841
I0822 --> I0831
I0822 --> I0835
I0823 --> I0824
I0823 --> I0827
I0823 --> I0830
I0824 --> I0825
I0825 --> I0826
I0826 --> I0830
I0826 --> I0831
I0826 --> I0835
I0826 --> I0838
I0827 --> I0828
I0828 --> I0834
I0828 --> I0839
I0829 --> I0830
I0830 --> I0843
I0831 --> I0843
I0832 --> I0842
I0833 --> I0842
I0835 --> I0843
I0841 --> I0843
I0842 --> I0843
classDef done stroke:#16a34a,fill:#052e16,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,stroke-width:2pxCanonical record: .ai/repo/project/milestones/M004.yaml. Read it back with majordomus plan show M004.