Safe model routing
Yieldpoint can describe what a change needs without selecting a model or calling a provider. Your host owns models, keys, vendor requests and final actions.
Start observe-only
{
"routing_session": {
"enabled": true,
"max_model_switches": 1,
"capsule_max_chars": 12000,
"router_overhead_fraction": 0.05,
"allow_unverified": false,
"checkpoint_events": ["verification_failed", "repair_exhausted", "loop_tripped"]
}
}
Routing is off by default, and off means off: with enabled false,
assess returns exactly what it returned before routing existed. Turn it on
first in a non-enforcing integration, compare the recommended tier with the model that
actually completed the task, then choose any provider policy outside Yieldpoint. Do not
use an uncalibrated confidence value as permission to act.
Every bound travels with the profile
The profile’s handoff block carries the switch budget, the live
checkpoint events, the overhead ceiling and the unverified policy. That is deliberate:
the Node and Java hosts receive only this document, so a limit left behind in Python
would be a limit they could not apply. The block is covered by the profile’s
profile_id digest, which the Python engine recomputes whenever it reads a
profile back — so a profile edited to widen its own budget is refused by the CLI
and the MCP tools rather than honoured. Node and Java validate structure and pairing
and apply the bounds; the digest stays the canonical engine’s to check.
One admission, one bounded handoff
git diff > change.diff
yieldpoint assess-routing --diff change.diff --verdict verdict.json --repair-attempt 1
yieldpoint build-capsule --profile profile.json --session session.json \
--objective "Charge each line item at its own price" --acceptance "Unit tests pass"
yieldpoint handoff-check --profile profile.json --session session.json \
--event verification_failed --overhead 0.04 \
--capability strong_reasoning --capability tool_use
--diff profiles the whole change set, which is usually what a task is:
the riskiest file decides the tier, every file’s requirements accumulate, and one
file the engine cannot read makes the whole set inexact. Use
--path/--after for a single file.
Keep the admitted model for normal work. A handoff is allowed only at a checkpoint
your policy lists, only within the switch budget, only when the estimated router and
capsule input fits router_overhead_fraction, and only when the candidate
has every capability the profile requires. Each refusal names the bound it failed.
Route on evidence, not on its absence
Pass --verdict. Without one the profile reports
unverified, and an unverified change is not handed to a larger model:
there is no evidence a stronger model would help, and spending one to find out is the
cost this feature exists to avoid. A pass buys no second opinion and a
block is a person’s decision; neither is routed around. Set
allow_unverified if your pipeline genuinely wants the opposite.
Hooks and SDK integration
The MCP tools yieldpoint_routing_profile,
yieldpoint_build_task_capsule and yieldpoint_handoff_check
provide the same contracts for developers wiring an agent loop. The Python LangGraph
adapter supplies make_admission_node and make_handoff_router;
admission reads the verdict, repair count and loop state already in graph state, so
placing it after a verify node is all the wiring it needs. Node and Java apply the same
bounds from the same profile, checked against shared fixtures.
See the runnable SDK and CLI examples, including
the optional Jev adapter.
Measure without collecting content
yieldpoint routing-stats
This reads only the local .yieldpoint/routing.jsonl ledger. It contains
event counts, tier, selection source, switch counts, capsule size and handoff outcomes;
it never contains model names, prompts, provider payloads, diffs or secrets. Disable it
with YIELDPOINT_NO_METRICS=1 or "metrics": { "enabled": false }.