How the harness decides which model does what. Policy home:
config/model-registry.yaml, schema-gated by
model-registry.schema.json in traust-contracts. A deployment may place
its own copy in TRAUST_CONFIG_HOME, which then takes precedence — the
procedure, the per-role granularity and the schema requirements are in
model-classes.md.
Concrete model identifiers live only in the registry. Skills, docs, and
scripts name tier classes (mythos-class, opus-class, sonnet-class,
haiku-class); anything that needs a real model ID resolves it:
python3 -m traust.cli registry models resolve <role> # at the role floor
python3 -m traust.cli registry models resolve <role> --tier opus-class # escalatedWhat each class is for, the eleven role floors, and the per-skill index: model-classes.md.
Alignment rule A12 (pre-commit) fails any skill or script that hardcodes a model ID outside the registry. This is the lock-in firewall: changing vendors or tiers is a reviewed data change, never a prose hunt.
Roles map to the work the harness does (deep-audit, triage-verifier, fact-judge, patch-author/reviewer, validation-planner, render-narrate, …). Each role declares:
- floor — the minimum tier;
resolverefuses anything below it (tier-downs below the floor are never allowed, silently or otherwise); - approved — models cleared for use;
- candidates — awaiting eval-gated promotion: recall-benchmark within tolerance of the baseline, consistency thresholds, redaction/injection spot-check;
- ledger_validity_writer — roles that write the disposition ledger's validity axis; cross-vendor is forbidden here until two consecutive benchmark cycles pass;
- claude_code_only — runtime features the role currently depends on (subagent fan-out, effort control); a cross-runtime pilot must replace or forgo them.
Three layers, none of them runtime:
- Resolution time (hard):
resolvecomputes the wanted tier as--tier or floorand raises on any tier-down below the floor; onlyapprovedmodels at the wanted tier are returnable. - Commit time (hard): the registry is schema-gated
(
python3 -m traust.cli registry models validate, approved-below-floor flagged) and alignment rule A12 fails the pre-commit for any concrete model ID outside the registry — code cannot bypass the resolver by hardcoding. - Provenance (soft): reports stamp
metadata.additional.model_routing {role, model}, batch templates pin the resolved ID at batch start, and spend declarations record the model used — a below-floor run is visible after the fact.
Known gap: nothing enforces the floor at execution time. An interactive
session runs whatever model it was launched with, and the ledger emit scripts
do not check the writing model — ledger_validity_writer is contract
documentation, not a runtime gate. Closing it means a pre-flight in batch
templates (resolve the role, refuse launch on a below-floor session model)
and the equivalent check in orchestrator job specs.
Drivers start at the role floor and escalate one tier when a trigger fires (dual-pass disagreement, citation-gate failure, ≥high severity proposed by a sub-floor tier, consistency-monitor flip). Escalations are recorded, never silent: reports carry
"model_routing": {"role": "fact-judge", "model": "…",
"registry_sha": "…", "floor": "sonnet-class"}in metadata.additional (emit with
python3 -m traust.cli registry models stamp). The registry file's short
sha ties every routed decision to the exact policy revision that made it.
Every model-consuming batch declares a spend row into the metrics ledger:
python3 -m traust.cli registry models spend --skill secure-code-audit \
--model <id> --tokens-in N --tokens-out N [--batch <batch-id>] \
[--repo <slug> --loc <size>]--repo/--loc attribute the row to one audited target — the calibration
tuple (spend, repo, size) the scan estimator fits against. Batch drivers
SHOULD pass both on per-repo rows; batch-level rows omit them. Every
model-heavy per-target skill carries a "Spend declaration (calibration tuple)"
section instructing the row; any other skill that consumes model tokens should
declare too — an unknown --skill name falls to the budget policy's default
ceiling. Estimator calibration is per skill.
Two properties of declared rows to keep in mind:
- They are routing markers, not cost claims. An agent cannot observe its own token usage mid-run, so the token fields are best-effort and often zero. Actual cost is measured from the runtime's own usage records by the deployment's spend-collection chain, which is deployment tooling, not part of this contract.
- Embedded environments may skip the declaration cleanly. A workspace with
no metrics ledger (a platform worker that records usage itself) skips at the
script level;
HARNESS_REQUIRE_SPEND_LEDGER=1turns the skip into a failure where the ledger is mandatory.
Cost, where a dashboard shows one, comes from the registry rate card
(price_per_mtok_*); verify it against the current published prices before
treating a dashboard figure as billing truth.
Registry models carry batch_eligible. Non-interactive sweep classes (L2
re-scores, backfills, projection narration) should prefer the provider's batch
lane; the flag and the routing policy are in place so drivers can declare
intent, and the submission client lands with its first consumer.
- Data-handling and licensing intake first (external-dependencies.md discipline) — embargoed findings leave the boundary only to approved processors; cost never overrides the embargo boundary.
- Add under
providerswith tier and prices; add to the target role'scandidates. - Run the eval gate and record results under the role's
evals. - Promotion to
approvedis a reviewed change touching only the registry.