Skip to main content
Vitruvio’s planner selects indices by cost, from measured statistics. The claim is easy to check, and worth checking, because it runs in both directions. At 3000 blocks:
At 4 blocks, same code, opposite decision:
A heuristic router that maps “natural language ⇒ vector index” is simply wrong on a small brain: embedding the query costs about 4500 µs, and at 200 blocks that is more than reading every block in the module. A cost model notices.

Cost is in microseconds

Not in abstract units. The operators span dict probes, HNSW walks, blob reads and a neural forward pass; any synthetic unit needs conversion factors anyway, and being honest about the unit is what lets explain --analyze validate the model rather than merely display it.

Recall is part of the objective

Three layers, and the stratification is how pruning happens without violating the protocol:
  1. A validity rule, never costed. With two or more scoring generators available, a plan using fewer than two is inadmissible. Single-authority plans are structurally inexpressible. (One exemption: a plan whose only generator is an exhaustive scan is a no-authority plan, not a single-authority one.) The scan earns its recall of 1.0 by reading what the indices read: a canonical block’s normalized view, through the module store, so every generator observes one text. A block registered with no view matches on its media type alone, for the scan exactly as for the indices, and explain says how many such blocks a canonical scan is walking past.
  2. A recall floor per intent, as feasibility rather than as a price — 1.00 exact, 0.95 lexical, 0.85 semantic, 0.80 associative. Capped at the best recall any valid indexed plan actually achieves, and the capping is reported as a degradation: an unreachable floor forced a 5.3-second scan over a 600-microsecond probe at 100k blocks.
  3. J = cost + λ·(1 − recall)·C_miss, to choose among plans that are already correct.

Reading explain

Four fields answer most questions:
  • indices_available vs indices_consulted. “Why did it not use the vector index” has exactly four possible answers — absent, stale, model tag mismatched, or cost — and all four are visible here.
  • considered — every rejected plan with its reason. only 1 scored generator with 3 available is the single-authority rule refusing a plan.
  • statistics — per module: fresh or stale, and the fingerprint.
  • prelude_us — the provenance ledger, cached per root. It once charged 136 ms to a query that did not cause it.
--analyze reports the estimate beside the actual per node, and estimation_error summarises the gap. Refitting the constants automatically is not built. They are measured defaults, and [planner] in vitruvio.toml overrides them by hand — which is enough to correct a machine whose disk or CPU disagrees with the ones they were measured on, and honest about the fact that nothing refits itself yet.

Enumeration is exhaustive

There is no memo structure, because the combinatorial explosion that justifies one comes from join reordering and there are no joins here. Exhaustive search over a few hundred plans per scope buys a property worth more than the saved microseconds: adding an index can never worsen the chosen plan, because the new space is a strict superset and both are searched completely. It is asserted as a property test.