MeridiansMeridians

The two systems

From the Meridians Wiki · Public · Maintained · joint

Every model call in the engine is one of two kinds of thinking, and the boundary it crosses declares which. The split is the first question to ask of any new call — before category, before tier.

System 1 — decideSystem 2 — generate
What it producesA typed, calibrated answer: a choice among named options, a score on ordered levels, or a noul (probability a statement is true) — each with a probability distribution and confidence. A Score's score is the probability-weighted expected level index and may be fractional.Text and structured artifacts: prose, plans, graphs, JSON payloads, chat turns.
BoundarycallDecide(state, questions, caller, decisionModelFor(category))src/lib/engine/ai/infra/decide.tscallGenerate*(prompt, system, maxTokens, caller, modelFor(category), reasoningBudget, …)src/lib/engine/ai/infra/api.ts
ProviderOpenRouter Decisions (/api/alpha/decisions), model DECISION_MODEL (~typesafe/jev-latest), one model for the whole system.OpenRouter chat completions on the tiered fleet (TIER_MODEL, allocated per category by the active profile).
Shape of a callOne state string + a batch of questions, evaluated together in one round trip. No output tokens, no reasoning budget, no temperature.One prompt, one generated response (optionally streamed), sized by MAX_TOKENS_*, with an explicit reasoning budget.
Latency / costSub-second; input-only pricing ($0.042/M, output free).Seconds to minutes; input + output token pricing per tier.
Failure modeSchema is guaranteed by the provider; the boundary still rejects an envelope that is incomplete, mistyped, or out of range — never a partial answer set.Free text may be malformed or truncated; parse once, validate, fail loudly.
AuthorityProposes only. Every answer passes a legality/authority gate (legalizeAgentPlays, a reducer, a Director) before anything canonical moves.Proposes only. Same gates apply.

The rule for choosing

Ask what the output is:

  • If the answer is one of a known set, a level on a known ladder, or a probability that something holds — and the prose around it is not the product — it is System 1. Route it through callDecide. Bounded action selection, routing, classification, eligibility, feasibility, conflict existence, closure, and distributions over known outcomes are all System 1.
  • If the words are the product — narration, scene prose, plans whose content is read, chat, names, descriptions, queries, explanations the user reads — it is System 2.
  • A call that would need both is two calls, never one: decide first, generate only what the decision says is needed (and only if the prose is actually wanted). Mixing a decision into a prose prompt dilutes both — the decision becomes an invented number inside a story and the prose becomes a rationale for it. The agent seat is the reference case: cards, stake, concealment, and travel are one callDecide; table talk is a separate post-reveal generation (generateTableReactions), not a field on the decision.

Why the split matters beyond speed and cost: System 1 answers carry calibration. A probability vector over known outcomes can be checked against what later happened (settled Scenario ledgers, resolved positions, extracted records), which makes the decision layer measurable and improvable in a way prose never is. That is the honest reading of "calibrated": vendor-claimed until Meridians has checked it on its own resolved outcomes.

Context budget

Jev has a 32k-token context window. callDecide preflights each request with assertModelContextFits; complete evidence enters the boundary, and if it exceeds the window a single System 2 compaction call condenses prose while preserving every item and id, with the reduction logged. Batching remains preferred; truncation by callers is never allowed. Jev uses a calibrated estimate of 2.5 characters per token rather than the generic four: Alice Run 7 reported 51,618 provider prompt tokens for a decision locally estimated at 31,603 with the generic ratio, a 1.63× underestimate.

State construction is complete evidence: callers never slice or summarise it to fit. Judgment lives in the question, instructions never sit in the state, and subjects are quoted in full. The boundary may compact prose when necessary, but the only caller-side guard is the loud assertModelContextFits failure plus the 80% headroom warning; batching remains preferred for states that do not fit.

How the two systems meet the three layers

The system is orthogonal to tier, category, and profile:

  • Category still names the work (action, predict, extract, …) and every call — either system — states one. A category may carry calls of both systems (action today: the agent seat decides via System 1; other live-move surfaces still generate). Do not create categories per system.
  • Tier / profile allocate capability to System 2 only. System 1 has one model today (decisionModelFor(category) returns DECISION_MODEL for every category); the category argument is kept so attribution, the generated call ledger, and a later per-category decision allocation need no call-site change.
  • Reasoning budget is a System 2 lever. System 1 has none — a decision that needs deliberation is either mis-shaped (split it) or needs a richer state, not more thinking tokens.

Where the seam lives

System 1                                        System 2
core/ai/decision.ts        pure types, parse    —
engine/ai/infra/decide.ts  callDecide           engine/ai/infra/api.ts      callGenerate*
engine/ai/decide-service   executeDecide        engine/ai/generate-service  executeGenerate*
engine/ai/infra/openrouter openrouterDecide     engine/ai/infra/openrouter  openrouterChat
app/api/ai/decide          route                app/api/ai/generate         route
AiRuntime.decide           headless seam        AiRuntime.generate          headless seam

Both boundaries stamp the same ApiLogEntry, price through MODEL_PRICING, classify into the llm spend lane, and appear in the generated model-call ledger, which lists every call by system and rejects a decisionModelFor or modelFor lookup that does not reach its boundary.

Migration ledger

System 1 is adopted call by call, each as its own non-mixed change. The audit of decision-shaped calls and the order they are taken in:

OrderSurfacePrimitivesStatus
1Scenario agent seat (decideAgentPlays)choice · score · noul · choiceSystem 1
2Fate scoring (scoreThreadsWithAI: realized probabilities, seat drive)choice-as-distribution · scoreSystem 1; explanation is a separate System 2 generation
3Program watch checks (scoreProjectionArticleCheck, …WindowCheck)score · choiceSystem 1; log line is a separate System 2 generation
4Fate-search priors (priceFutures / pricePresent over generated coordinations)scoreSystem 1; candidates remain System 2 generation
5Survey closed cellschoice · score · noulSystem 1; open and estimate cells remain System 2 generation
6Conflict settlement outcome / closure (resolveConflictRealism, detectConflicts)choice · noulSystem 1; telling stays System 2 prose
7Chat / constellation routing, coordination preset, map-scale classification, attachment relevancechoice · score · noulSystem 1; suggestTableCast stays System 2: unbounded roster
8Streams (instantiateStream, generateSeatStream, scoreStreamPrior)choice · score · noulSystem 1 pricing for landing distributions, log type, horizon, action availability, travel, and evidence; outcome/question prose stays System 2
9Topic filing, location parents, findings routing, prose / plan quality verdictschoice · noulSystem 1; new-topic proposals, observations, critiques, overall analysis, and patterns stay System 2. reviewBranch stays System 2 because merge/insert/move carry targets and new content.
10System invocations, detected paradigm, scene count, research loop/reminder shapenoul · choice · scoreSystem 1; attribution edges, pattern prose, scene directions, names/sites/topics/labels/prompts stay System 2.
11Extraction reconciliation (reconcileEntities, reconcileSemantic, reconcileThemeTypes)choiceSystem 1 for entity identity, open-thread identity, repeated System rules/facts, repeated Theme propositions, and Theme Type territory. System 2 still proposes the source entities and propositions; System 1 verifies bounded folds.
12Causality flag verification (verifyCausality)noulSystem 1 verifies turning-point admission and claimed Fate settlement after System 2 cites candidate flags. The fixed scene-to-scene wuxing ring prior was rejected and removed; coupling is measured per record, not presumed.

Calls whose parsed output is prose or open structure (generateDomain, expandWorld, generateScenes, scene writers, chatCompact, chat turns, newsletter, notes, suggestions, generateTableReactions) stay System 2 by definition; a JSON return shape alone does not make a call a decision.

Seat-stream idleReason remains a speculative System 2 field and is surfaced only when the typed canAct decision says the seat sits out; a separate third call is not worth the latency and cost. Canonical feasible travel options are passed as structured choices from the scenario advance callers. Standalone stream operations lack room/economy context, so they omit the travel question rather than extracting destination ids from rendered spatial prose.

The following remain System 2 because the product is open structure or text, or because a bounded field determines the shape of the same generation and splitting it would require another generation: generateSurveyProposal and generateSurveyBatch (question type determines the choice options or likert scale the call must also write); refineArticle.tags (open vocabulary); reorganizeCurriculum, treeSkeleton, suggestConstellationGraph, suggestOnboardingArchitecture, suggestPublicResearchFlow, groupScenesIntoArcs, extraction structure phases, fileConversion, all plan-category graph and scene-plan generators, generateSceneGameAnalysis, generateRoundGameTheoryAnalysis, and craftEditorial (the structure or text is the product and option sets are unbounded).

The generated model-call ledger is the current executable inventory; this table records the migration principle and intentionally retained System 2 boundaries.