| 1 | # Extension Runtime v2 — Performance baselines |
| 2 | |
| 3 | Soft CI thresholds live in `internal/extension/bench_threshold_test.go`. |
| 4 | Benchmarks live in `internal/extension/benchmark_test.go`. |
| 5 | |
| 6 | ## Targets (developer machine / CI soft fail) |
| 7 | |
| 8 | | Operation | N | Soft upper bound | |
| 9 | | --- | --- | --- | |
| 10 | | `BuildDependencyGraph` | 32 components | < 50ms | |
| 11 | | `DiffRuntimePlan` no-op | same graph | < 20ms | |
| 12 | | `EffectScope.Dispose` | 64 effects | < 50ms | |
| 13 | |
| 14 | ## Incremental vs full rebuild |
| 15 | |
| 16 | - **No-op / interceptor / UI / provider / MCP-only** (`RebuildFrom` + true subgraph patch): must **not** call `BuildRuntime`. Metrics: `NoOpRebuilds` / `SubgraphRebuilds`. |
| 17 | - **Full** (`SubgraphSidecar` / `SubgraphFull`): `FullRebuilds` + full `BuildRuntime`. |
| 18 | |
| 19 | Measure: |
| 20 | |
| 21 | ```bash |
| 22 | go test ./internal/extension/ -run 'TestGraphAndPlanLatencyBaseline|TestEffectScopeDisposeBaseline' -count=1 |
| 23 | go test ./internal/extension/ -bench 'BenchmarkDependencyGraphAndPlan|BenchmarkExtensionKernelStartup' -benchmem -count=3 |
| 24 | go test ./internal/boot/ -run 'TestIntegrationNoOpDoesNotBuildNewController|TestRebuildFromNoOp' -count=1 |
| 25 | ``` |
| 26 | |
| 27 | ## Cache hit expectation |
| 28 | |
| 29 | No-op, UI/interceptor-only, and backend-only Provider/MCP plans keep the frozen |
| 30 | system prompt, tool schemas, and `RuntimeSnapshot.CacheHash` byte-stable. |
| 31 | Provider capability changes remain visible through `providerChanged` without |
| 32 | falsely reporting `prefixChanged`. MCP schema additions, removals, or renames |
| 33 | are classified as full rebuilds and intentionally recompute the prefix. |
| 34 | Discovery of skills/commands/hooks is skipped while `ReuseAssembly` is retained. |
| 35 | |
| 36 | ## Sidecar start / drain |
| 37 | |
| 38 | `StartPackagesWithPlan` adopts Unchanged clients (`SidecarAdopts` metric) and |
| 39 | only starts Added/Reloaded. Drain uses `Manager.DrainPlan` after publish. |
| 40 | Drain TTL defaults to 30s; force-expire fires registered cancel callbacks then |
| 41 | writes `drain-timeout-<gen>` receipts. |
| 42 | Cold publishes allocate no watcher. While drains exist, rapid publishes share |
| 43 | one timer watcher per runtime owner; the watcher sleeps only until the oldest |
| 44 | drain reaches its TTL. Expired-generation markers are capped at 256 per owner. |
| 45 | |
| 46 | Receipt evidence is process-local and bounded to 32 generations with 256 |
| 47 | receipts per generation. Retention truncation is conservative: it prevents a |
| 48 | clean-rollback claim instead of hiding missing evidence. |
| 49 | Message dedup keys are removed with their matching evicted receipts. File-prior |
| 50 | retention is bounded to 8 MiB per write and 32 MiB per runtime owner; an |
| 51 | oversized prior is not retained and blocks a clean-rollback claim. |
| 52 | Completed provider streams remove their drain callbacks immediately, so a |
| 53 | long-lived generation retains only active stream cancellation state. |
| 54 |