Core concepts

Routers

A named policy over your model names, when one source is not enough.

A model name can point at several sources. A router is the policy that decides which one a call gets, kept as its own named object so the decision is reviewable and can change without touching a client.

Order of setup: create the target aliases, allow them on a bundle, then create the router. Creating a router fails if no bundle already allows every target.

Create and inspect

seams routers add balanced --strategy round_robin \
  --target acme/fast --target acme/smart

# router    balanced
# strategy  round_robin
# targets   acme/fast, acme/smart
# attached to every bundle that allows both targets

seams routers show balanced
seams routers list

How it attaches to a bundle

You do not run bundles allow with the router slug. Allow the target aliases on the bundle first. When you create (or replace) the router, Seams attaches it to every bundle that already allows all of those targets. A key minted under that bundle may then call the router slug or any allowed alias.

If you later allow the same targets on another bundle, create or replace the router again so it attaches there too.

Strategies

StrategyPicks
cheapestThe lowest priced target your bundle can serve. The default
round_robinWalks targets in order, wrapping; cursor is process-local
randomA uniform random target among those that can serve the call

All three skip a target that cannot serve the call, no tool support when the call carries tools, no vision when it carries an image, a context window too short for the prompt. A router whose every target fails that check returns no_capable_model rather than picking one that would fail.

Order matters for round_robin. --target is repeatable and the order you pass is the walk order.

What a caller sees

They call the router by its slug, the same way they call any other model name. The response still says that name. Which target answered stays on the request record you see in the console and the request log. Targets can be different providers; the inbound client does not care.

const res = await client.chat.completions.create({
  model: "balanced",
  messages: [{ role: "user", content: "Say hello" }],
})
// caller still sees model: "balanced"

Remove

seams routers rm balanced --yes

One target per call

A router picks one target and the call goes there. If that provider returns an error, the error reaches your caller. The router does not walk on to the next target, and it does not send the same call to several providers to compare answers. Handle a failed call the way you handle any other error from a model provider: read the code on the response and decide.

Cross-protocol examples (OpenAI client against an Anthropic target, and the reverse) are on Calling the gateway.