---
version: 0.22.0
date: 2026-06-18
headline: "The result-side fold is now per-tool too — the last unindexed O(features) cost on the dispatch path is closed."
themes:
  - perf
  - abi
---

**TL;DR** — Follow-up to v0.21.0's O(1) hot path, driven by an adversarially-verified audit of every remaining per-call cost. The result-admission fold (run on every produced result) is now per-tool via the same opt-in `CallScope` seam, backed by one generic bucketing primitive shared with the adjudicator fold. Separately, the reference monitor now indexes its arg-value rules by tool, so a large policy no longer taxes every call. Both are behavior-identical for the current driver set; `abi`/`kernel`/`adjudicator` pass and the canonical Linux/amd64 build is green.

## Per-tool result-admission fold (#perf)

- **`admitResult` folds only the gates that apply to the call's tool.** A `ResultAdmitter` scopes itself with the optional `CallScope{ Tools() }` (the same interface adjudicators use); `abi.ResultAdmittersFor(c)` returns the unconditional gates plus the gates scoped to that tool — a single atomic load + map index, O(1) and allocation-free.
  - *Why:* the result fold ran every registered gate on every result (every Reap of a dispatched call, every gateway `AdmitResult`, the kvmmu KV-quarantine gate). It was the last unindexed O(features) fold on the dispatch path — the dual of the adjudicator fold v0.21.0 already scoped.
  - *How:* `internal/abi/registry.go` (`ResultAdmittersFor`, `raByTool`/`uncondRA`), wired in `internal/kernel/kernel.go`, `internal/kvmmu`, `cmd/ctxbench`.
- **Fail-closed by default.** A gate that doesn't implement `CallScope` is unconditional / always-run — the exfil/quarantine floor is never weakened. A scoped gate must admit-as-is (Allow) for unlisted tools, so skipping it is verdict-equivalent. Proven by `TestScopedResultAdmitterRoutesByTool` (a fetch-scoped quarantine routes to fetch, not read).
- **One primitive, both folds.** The adjudicator and result-admitter indexes now share a single generic `byToolScopeIndex[T]`, so the next tool-scoped fold is one line, not another hand-rolled bucketer.

## Reference monitor: arg rules indexed by tool (#perf)

- **The rank-100 monitor evaluates only the arg-value predicates targeting the call's tool.** Predicates are grouped by tool once at `SetPolicy` (`argByTool`), so a policy with N per-tool rules costs O(rules-for-this-tool) per call, not O(N).
  - *Why:* the floor previously flat-scanned the whole policy on every call (skipping non-matching tools inside the loop), so per-call cost grew with total policy size — a single-rung O(policy) cost the abi-level `CallScope` cannot address.
  - *How:* `internal/adjudicator/decide.go`. Behavior-identical (every existing arg-predicate test passes); `BenchmarkAdjudicateArgScaling` is flat (~510 ns/op) across 0/100/2000 unrelated predicates.

## Scaling contract updated (#abi)

- **`fak/ARCHITECTURE.md`** now records that both folds are per-tool and adds the intra-driver rule: a driver's own per-call work must be O(this call), not O(policy size).
