Menu
popagent
publicLatest change dc4b371554df3a78c40c29085ead1db3de7e24f8 - Add model routing policy, companyStatus tool, Markdown channel output, compact Runtime settings by AkurAI Build
# Models
LLM access, catalog, defaults, and the routing policy. Framework facts live in
[AGENTS_AGENT.md](AGENTS_AGENT.md).
## Provider and catalog
- LLM gateway: local **9Router** (`http://127.0.0.1:20128/v1`, OpenAI-compatible),
via `createOpenAICompatible` from `@ai-sdk/openai-compatible`.
Env: `NINEROUTER_URL`, `NINEROUTER_API_KEY` (see `.env.example`).
- The default model and model source are persisted in the singleton
`popagent_agent_runtime_settings` row. The source can use the configured
9Router model, force Titan's local Ornith route, or force 9Router's
`auto/coding:free` route backed by currently available OpenRouter free coding
models. `GET /api/models` merges the effective default with the gateway's
current model IDs and context-window metadata; do not hard-code the catalog,
default, or context limits in UI code.
- Per-request selection: `POST /api/chat` accepts an optional `model` body field;
when omitted, the server reads the persisted effective default.
- Production agent profiles and system-owned self-update and Build-maintenance
schedules use the configured cloud route (`cc/claude-sonnet-5`); Titan-local
Ornith (`titan/ornith-1.0-9b-mtp-q4_k_m`) is reserved for scheduled work that
needs little reasoning or tool use — currently the five nightly Simulation
schedules — so cloud quota goes to reports, mail security, and self-update.
- `src/models.ts` admits at most `POPAGENT_LOCAL_MODEL_CONCURRENCY` local
generations at once (default `2`, matching llama.cpp). Additional local calls
wait FIFO without their own timeout, aborted waiters are removed, and a slot
remains leased until the response stream ends or is cancelled. Remote models
bypass this queue.
- A changed llama.cpp alias becomes selectable only after OmniRoute imports the
upstream `/models` catalog.
- Bun's maximum 255-second idle timeout leaves enough time for slow local models
to emit their first stream event.
## Model routing policy
- `popagent_agent_runtime_settings.model_routing` (JSONB, `ModelRouting`) holds
`enabled`, `strategy` (`fallback` | `round-robin`), `cooldownMs`, and an
ordered `routes[]` of `{ model, reasoningEffort }` where effort is
`default | low | medium | high`. Edited under Settings → Runtime → Model
routing; `PATCH /api/settings/agent-runtime` validates every route model
against the catalog and refuses an empty enabled policy.
- The pseudo model id `policy/routing` (`ROUTING_MODEL_ID`) is listed in
`GET /api/models` and accepted wherever a model is selectable (default model,
agent profile, task, schedule, chat). `src/model-router.ts` resolves it per
attempt: `fallback` returns the first route not cooling down; `round-robin`
rotates through healthy routes; when every route is cooling down the one that
recovers first is used. Concrete model ids pass through unchanged. With
routing disabled or empty, the runtime default model is used and a warning is
logged.
- A route's effective spec is `model@effort` (`modelSpec`/`parseModelSpec` in
`api-types.ts`). `resolveModel` accepts the spec unchanged; `applyReasoningEffort`
in `src/models.ts` strips the suffix on the wire and sends OpenAI-compatible
`reasoning_effort`, so 9Router receives the real model id. `isKnownModel`
validates the base id.
- `createAgentTaskExecutor` picks one spec per attempt (`task.model.routed`
log, "Routing to …" progress line) and, on failure, calls
`ModelRouter.reportFailure`: only rate-limit (429/usage-limit/quota) and
unknown-model failures take a route out of rotation for `cooldownMs` (a
"reset after Nm" hint extends it). Cooldowns are process-local; a restart
clears them. `GET /api/models/routing` exposes per-route cooldown, last error,
and pick counts, rendered as LEDs on the settings page.
- Contracts: `src/model-router.test.ts`, `src/agent-runtime-settings.test.ts`.