Adding work — the rule
From the Meridians Wiki · Public · Maintained · joint
A new model call site first picks its system (two systems): a typed decision goes
through callDecide(state, questions, caller, decisionModelFor('<category>')); text goes through
callGenerate(..., modelFor('<category>')). Never both in one call.
Either way it must pick a category. If none of
the twelve fit, add a category to AI_CATEGORIES (+ CATEGORY_META, + a tier in each profile's
allocation) rather than reaching for a raw tier constant or a transport default. There is no default
LLM model: an uncategorised call is uncategorised thinking, and the call boundary rejects it.
Orthogonality is the guardrail: a category names the work, while the active profile assigns its capability. Do not create a catch-all category to make a call fit; either use the existing semantic category or add the missing modality and allocate it deliberately.
callGenerate* terminates server-side at executeGenerate / executeGenerateText; HTTP and headless
adapters that already hold an explicit provider key reuse that service rather than rebuilding a provider
request. callDecide terminates the same way at executeDecide (decide-service.ts) over
openrouterDecide, the only file that may hold the decisions endpoint. Pilot is the only request-shape exception because its assistant → tool → result loop cannot be
represented as one prompt. It uses the shared openrouterChat transport, whose pre-dispatch context guard
also protects every standard generation call. The raw chat-completions endpoint belongs only there.
Jev does not read decision question keys semantically. Every question's instructions must name its own
subject; the validator rejects duplicate instructions within one request. Alice evidence: identical
instructions produced 0.40 ± 0.01 across all 16 pairs, while named subjects produced 0.02–0.56.
Image context is fitted, not capped
Image generation admits context up to the route's transport ceiling (AI_IMAGE_PROMPT_MAX_CHARS === AI_JSON_BODY_MAX_BYTES, 8 MB) rather than the ordinary AI_PROMPT_MAX_CHARS. This is a decision, not an
omission: compactImageGenerationContext owns fitting — it chunks, compacts hierarchically, and always
formulates one bounded visual prompt — so the image provider never sees caller-truncated continuity. The
trade is that spend scales with admitted context: every compaction chunk is a text-model call (bounded per
request by MAX_IMAGE_COMPACTION_ROUNDS × chunk count, not by a fixed budget), and each one is logged
through logApiCall into the API log and the spend lanes. Cost is therefore visible rather than capped
here; the owner-only route admission and the spend kill-switch remain the ceilings. Do not reintroduce a
character cap for symmetry — if spend needs bounding, bound compaction calls per request, not context.