## Description
Companion to #2545 (the "sources" double-count fix) — this fixes the
"sinks" half found in the same savings audit: **tool-schema / deferral
savings were never aggregated into `Metrics`**. They lived only in
per-request log tags, so every sink that reads `metrics.*` silently
dropped them, and one CLI mode disagreed with another.
Confirmed sinks that under-reported:
- **Session-summary printout** — `Tokens saved:` is message-only; a
24K-tool-deferral turn printed `0`.
- **`cost.py` session summary** (feeds `/stats.summary`) —
`total_tokens_saved_with_rtk` etc. were message+CLI only.
- **`/stats` `all_layers_tokens_saved`** — the advertised "total"
excluded the `tool_search` layer it enumerates in `by_layer`.
- **`headroom perf --format json/csv`** — omitted `tool_saved` while the
**text** output of the same command showed it.
Closes #
## Type of Change
- [x] Bug fix (non-breaking) / observability correctness
## Changes Made
- `PrometheusMetrics.tool_search_saved_total` — new counter, accumulated
in `record_request` from a new `tool_search_saved` arg;
`emit_request_outcome` fills it from the `tool_search_deferred_tokens` +
`turn_hook_tools_saved_tokens` tags. **One source of truth.**
- Fed into: session summary (`Tool schemas deferred:` line), `cost.py`
summary (new `tool_schema_tokens_saved` +
`total_tokens_saved_all_layers`; existing fields unchanged for
back-compat), `/stats` `all_layers` total, and `build_perf_summary`
(`tool_saved`).
- Kept **distinct** from `tokens_saved_total` (message compression) —
tool bytes never move `tok_before/after`, so it's a separate layer, not
a merge (no double-count).
## Testing
- [x] `ruff` + `ruff format --check` + `mypy` clean
- [x] Regression tests + existing suites pass
### Test Output
```text
pytest tests/test_savings_tool_search_aggregation.py tests/test_cli_perf_format.py -q → 18 passed
pytest tests/test_cli_perf_format.py test_proxy_savings_history.py test_dashboard_token_savings.py
test_bundled_tools_savings.py test_openai_chat_turn_hooks.py → 68 passed, 2 skipped
mypy (metrics/outcome/cost/analyzer) → clean
```
## Real Behavior Proof
- Standalone: `record_request(tool_search_saved=1500)` then `(…=800)` →
`metrics.tool_search_saved_total == 2300`, `tokens_saved_total == 200`
(message stays separate); `build_perf_summary` over records with
`tool_saved` 5000+3000 → `tool_saved == 8000`.
## Checklist
- [x] Self-reviewed; no new warnings; tests pass; did **not** edit
`CHANGELOG.md`
## Additional Notes
Together, #2545 (record once) + this (surface every layer) make savings
correct **and** complete end-to-end across `/stats`, the dashboard,
`headroom perf`, the session summary, and cost/budget. The `/stats`
`by_layer.tool_search` and dashboard card already showed the layer
(windowed, from the log scan); this makes the lifetime/metrics-based
sinks agree.