Every Bedrock-invoking cartridge in the platform (chatbot, the obs Lambda's narrative path, the future code-agents, every agent ported from paycargo-ai) is one decision deep: which model to call. That decision lives today as a hard-coded modelId string in cartridge code, or as a manifest entry in the cartridge's DDB. When a cheaper or better model lands, every cartridge has to change.
That is the wrong place for the decision. Cartridges should call into substrate by logical role ("the scout role for my recon turn," "the writer role for my output turn"), not by vendor-specific model ID. The substrate maps roles to backends. When a new backend becomes available (an in-house model server, a new Bedrock model, a peered partner's API), the substrate gets one new entry in the route table and every cartridge benefits.
This is the same separation the substrate already enforces for KMS keys (cartridges declare data class, substrate selects key), for guardrails (cartridges declare risk tier, substrate selects guardrail), and for network egress (cartridges declare egress need, substrate selects allowlist entry). Tiered inference fits the same pattern: cartridges declare role, substrate selects backend.
A small Terraform module plus a shared zero-npm client library. ~200 LOC total.
The route table itself is a JSON manifest in SSM Parameter Store. Same pattern the schedules substrate uses (Step 6.k). Operator edits land within 15 minutes of cold start without code redeploy.
Default routes registered at apply:
{
"routes": {
"scout-local": {
"backend": "openai-compat",
"url": "http://10.42.0.10:11434/v1/chat/completions",
"auth": { "type": "bearer", "secret_arn": "...mac-mini-token" },
"model": "qwen2.5-coder:14b",
"timeout_ms": 5000,
"fallback": "scout-cloud"
},
"scout-cloud": {
"backend": "bedrock",
"modelId": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
"timeout_ms": 10000,
"fallback": null
},
"writer-opus": {
"backend": "bedrock",
"modelId": "us.anthropic.claude-opus-4-6-<datestamp>-v1:0",
"timeout_ms": 60000,
"fallback": "writer-sonnet"
},
"writer-sonnet": {
"backend": "bedrock",
"modelId": "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
"timeout_ms": 30000,
"fallback": null
}
}
}
The (model, prompt) doctrine from Step 6.l becomes (route, prompt). An agent's turn list in DDB references logical routes:
{
"pk": "code-agents-config",
"sk": "golden-spec",
"turns": [
{ "name": "recon", "route": "scout-local", "prompt": "...", "max_tokens": 400 },
{ "name": "plan", "route": "scout-local", "prompt": "...", "max_tokens": 500 },
{ "name": "gather", "route": "scout-local", "prompt": "...", "max_tokens": 300 },
{ "name": "write", "route": "writer-opus", "prompt": "...", "max_tokens": 4000 }
]
}
Two operators can serve the same agent from different backends just by editing the route table. A development environment can route writer-opus to Sonnet for cheaper testing. A production environment in a regulated tenant can route the same role to an on-prem endpoint. The cartridge code does not know.
The obs Cartridges tab's Trigger config section surfaces the per-turn route as a dropdown populated from the active SSM manifest. Adding a new route to the substrate makes it appear in every cartridge's dropdown automatically.
Local inference has a different failure mode than cloud inference. The Mac Mini can be off, the Tailscale net can be flaky, the model server can be loading a different model. The router treats every route as having a primary backend and a fallback chain.
Try primary, timeout at the configured ms, move to the next fallback in the chain. Each fallback hop logs the failure (so the operator sees that the Mac Mini was unreachable). The agent does not fail unless every backend in the chain fails. The obs Cartridges tab gains a "Fallback rate" column per route so the operator can see when a backend is sick.
The default chain at GA: scout-local → scout-cloud → null and writer-opus → writer-sonnet → null. When the Mac Mini is healthy, scouts run free. When it is down, scouts fall over to cloud Haiku at $0.005-$0.03 per turn (acceptable burst cost during an outage). When Opus is throttled by Anthropic's regional capacity, the writer falls to Sonnet automatically.
This substrate makes a specific bet on where open-source inference is heading. The bet is concrete.
| Generation | Mac Mini / on-prem can do | Cloud is for | Cost per golden-spec invocation |
|---|---|---|---|
| Today (Phase 1.5) | Scout, plan, gather. Navigation and cheap thinking. Qwen 2.5 Coder 14B-32B is good at "given a tree, pick the relevant files." | Writer turn. Opus 4.6 or Sonnet 4.5. | ~$0.48 (Opus writer) or ~$0.10 (Sonnet writer) |
| 6-12 months (Phase 2) | Most writer turns for routine work. 70B-class open models on a Mac Studio with 192GB unified memory will hit Sonnet 3.5-class quality on coding tasks. The next generation of Qwen Coder and DeepSeek Coder are converging fast. | Genuinely novel architectural problems. Becomes the "I am uncertain, escalate" tier. | ~$0.05-$0.10 |
| 1-2 years (Phase 3) | Frontier open-source approaches Opus-class. Local handles nearly all PayCargo agent work. | Verification and the rare genuinely hard problem. | Near zero (electricity + occasional cloud escalation) |
Two trends compound in favor of the substrate. Bedrock pricing is rising as Anthropic invests in better models. Open-source quality is rising while local inference cost falls toward electricity. The gap widens. The substrate captures every step of that widening without any cartridge code change.
Competitors who pile features on hosted-only Bedrock can match velocity for now. They cannot match unit economics once open-source crosses a quality threshold. Their cost structure is locked to Anthropic's pricing power; ours is locked to electricity. Five years out the gap is plausibly a 10x to 100x cost differential. A competitor at that point either sacrifices margin to match price or sacrifices price to match margin. Substrate compounds. Bolt-on features do not.
Scout turns processing customer code or PII never leave the perimeter once the Mac Mini handles them. As Phase 2 lands, the writer turn moves local too for routine work. By Phase 3 nearly nothing transits a third-party API. PayCargo's compliance posture becomes "by construction" rather than "by policy." That is a sellable position to financial regulators and to enterprise customers who do not want their architecture decisions sent to OpenAI or Anthropic.
When a vendor ships a small-context-window specialist optimized for "given a file tree, return relevant paths," it is one entry in the router. When DeepSeek's next coder model hits 90% Sonnet quality at 1% the cost, it is one entry. When a regulated tenant requires a specific vetted model class for HR data, it is one entry scoped to that tenant. The substrate does not pick winners; it stays optionable.
Vibe-coded competitors who write new BedrockRuntimeClient() directly in every Lambda inherit Anthropic's pricing power as a permanent dependency. Migration to local inference for them is a refactor across every cartridge. For us, it is a route table edit. The cost of switching matters more in five years than it does today, and asymmetric switching cost is one of the most durable forms of moat.
BedrockRuntimeClient directly outside the router itself.*-complete log line carries the route name and the backend that actually served it. CloudTrail shows the SSM reads. Cost attribution per backend is visible in the obs Cartridges tab.