Configure hybrid local + frontier routing
For anyone who wants to combine a fast local model for simple steps and a more capable model on a server for complex steps, with an automatic cost ceiling.
Prerequisites
- At least one backend for the default side, a
.ggufmodel served by the embeddedllama-serverengine. See Download local models. An Ollama server does not belong in that role: Apollia reaches it over HTTP like any other OpenAI-compatible endpoint, so it is a remote backend even when it runs on your own network. - At least one frontier backend declared: an OpenAI-compatible server. Apollia has no dedicated cloud provider since 0.3.0, so a hosted model is reached through a gateway you operate, such as LiteLLM. See Connect a remote model.
- The exact name of each backend as you named it during configuration.
How hybrid routing behaves
The hybrid router routes each agent reasoning step according to its estimated complexity:
- Simple steps (information retrieval, formatting, direct tool calls) are handled by the default backend.
- Complex steps (multi-hop reasoning, long synthesis, uncertain judgement) are escalated to the frontier backend.
- When the cumulative cost estimated for the frontier reaches
cost_ceiling_usd, all remaining steps are handled by the default backend, whatever their complexity level.
The router does not guarantee a clean cut to the cent: steps already in flight when the ceiling is crossed finish on the backend that started them.
Steps - Enable hybrid routing
[llm.routing.hybrid] is a subsection of [llm.routing], which is itself
mandatory and has two required keys. Writing the hybrid table alone creates the
parent table without them, and the file then fails to load. Copy the whole
block, not the last three lines:
[llm.routing]
precise = "local-qwen3-8b"
fast = "local-qwen3-4b"
[llm.routing.hybrid]
frontier = "frontier-server"
cost_ceiling_usd = 2.00
ceiling_action = "stay_local"
The same file must already carry a [llm] section with a default key and at
least one [[llm.backends]] entry; both are required too.
preciseandfast: names of the backends used for deep reasoning and for lightweight extraction. Both are required, and both must match a declared backend.frontier: exact name of the frontier backend declared in[llm.backends]. The value cannot be empty.cost_ceiling_usd: ceiling in US dollars per routing session. Set a strictly positive value. The cost of a call is estimated from a rate table built into Apollia, by model name; a model the table does not know counts as zero, so the ceiling never fires for it.
ceiling_action: what happens when the ceiling is crossed.stay_local, the default, keeps the run going on the local backend, silently degraded.hard_stopends the run cleanly with a structured error instead. Choosehard_stopwhen a silently local answer would be worse than no answer.
Restart the daemon after the change.
Verification
At startup the daemon checks only that frontier is not empty and that
cost_ceiling_usd is strictly positive, and refuses the file otherwise; it does
not check that the name matches a backend, and it prints nothing that names
your frontier or your ceiling. The one routing line it emits when it
starts carries the two backends of [llm.routing] and nothing else:
precise="local-qwen3-8b" fast="local-qwen3-4b" llm.routing.propagated
Hybrid routing shows up later, at the moment a step is escalated. Two lines tell the whole story, and both carry the frontier name:
frontier="frontier-server" session_cost_usd=0.31 ceiling_usd=2 llm.hybrid.escalation.routed
frontier="frontier-server" reason="the session cost ceiling is reached" llm.hybrid.escalation.blocked
Run one deliberately complex task and look for either of them. To watch escalation and cumulative cost in real time, see the observability page. See Monitor LLM costs.
If it does not work
- Nothing loads at all after the change: the
[llm.routing]table is incomplete.preciseandfastare required as soon as the table exists, and writing[llm.routing.hybrid]alone is enough to create it. - No escalation ever happens, and no error anywhere: a wrong
frontiername is not caught when the daemon starts. It surfaces at the first escalation asllm.hybrid.escalation.blockedwithreason="the frontier backend is absent from the router". Check the exact name against the output ofapollia-os llm status, the match is case sensitive. - The ceiling is reached immediately: the blocked line carries
reason="the session cost ceiling is reached". Raisecost_ceiling_usdor split your tasks into shorter sessions. - Every step stays local when you expect escalation: the router only escalates on a step it judges complex. Test with an explicitly complex task before concluding the configuration is wrong.
Technical reference: Configuration - the
[llm]section, its backends and its routing keys.