RUNTIME_GUARDRAIL_ARCHITECTURE.md

systems/RUNTIME_GUARDRAIL_ARCHITECTURE.md

<!-- The runtime GUARDRAIL spine: the deterministic scaffolding that makes a dumb local LLM reliable at runtime for "Humanity" on consumer hardware. Operationalizes the /dev/continuo lesson (a lesser model wrapped in code that encodes its failure/reward patterns hits near-perfect reliability) for a shipping game. Extends docs/RUNTIME_GENERATIVE_LAYER.md (the split + the alive-not-slop framing) and docs/CANON_INTEGRITY_ARCHITECTURE.md (the canon graph + recall = the truth store). Sits in the T99 operational tier alongside PIPELINE_ADAPTER/ROADMAP; invents zero world canon; the cardinal do-not-invent rule binds. Absolute numbers (latency ms, VRAM, quant) stay illustrative shape pending a Phase-5M-class runtime perf audit. -->

Humanity — Runtime Guardrail Architecture

The safety spine that lets a dumb local LLM ship without slop or lore-breaks

Companion to docs/RUNTIME_GENERATIVE_LAYER.md (which fixes *what* the model may generate) and to docs/CANON_INTEGRITY_ARCHITECTURE.md (which builds the truth store). This doc specifies the *guardrail* — the five deterministic mechanisms that make the strategic-only local LLM reliable at runtime on a player's own PC / Xbox / PlayStation, fully offline, inside a frame budget. It is the runtime analog of the harness/ F1–F10 gates: the code that catches the model's mistake classes before a player ever sees them.

---

0. The bet, stated precisely (the continuo lesson, operationalized)

/dev/continuo (C:\dev\continuo\CLAUDE.md) proved a specific, transferable thing: a *dumb* 14B model reached near-perfect reliability on untrusted extraction not by being smart but by being wrapped in deterministic code that encoded every failure/reward pattern and gave it perfect-precision recall over a bounded, structured truth store. Its constitution names the exact moves we inherit:

The continuo scorecard was a number: fraction of extractions that passed all deterministic checks. Ours is the same number — first-try guardrail pass rate — and §4 below makes it the ship gate.

The load-bearing inversion: the model owns *zero* facts. The harness/canon_graph.json (3,118 nodes / 11,027 edges, live on disk) owns *all* of them. The model performs surface texture over a truth the deterministic layer already proved. It cannot lie because it is never asked to know.

---

1. Scope lock — what the local LLM is allowed to touch (Josh's hard constraint, made structural)

Josh's constraint: mostly deterministic code; the local LLM is confined to STRATEGIC generation only — goals, dispositions, plans, dialogue, adaptation, faction-intent — NEVER per-frame mechanics. This is not a policy the model is asked to respect; it is a wall the architecture enforces. The model's output is consumed only through a typed API surface that *has no per-frame or state-mutating verbs*.

1.1 — The strategic/tactical boundary (the wall)

TierOwnerCadenceExamplesEnforcement
Per-frame tacticalDeterministic sim only30–60 HzHitboxes, i-frames, damage, phase HP gates, aggro, attack selection, dodge resolution, the ~30 combat clocks (COMBAT_ENCOUNTER_SYSTEM.md Part B §4)The model has no API to write here. It cannot select an attack, set a timer, or move an entity.
Strategic (bounded, low-cadence)Local LLM → validated → deterministic consumerseconds–minutes, or on discrete eventsGoal/disposition selection, multi-step plans, faction-intent, dialogue/barks, tactical *read* (which known weakness to needle), adaptation *bias*The model emits a typed strategic token (an enum choice, a plan graph over a fixed action vocabulary, or a text line) that a deterministic executor *interprets* — never raw control.
Cosmetic surfaceLocal LLM → validatedon eventBark phrasing, fauna flavor beats, NPC ambient/reaction lines, deflectionsPure output leaf; text with no sim consequence.

The rule that draws the line: the model proposes strategy in a vocabulary the deterministic layer defines; the deterministic layer disposes. A boss's *plan* ("bait the roll-spammer, then punish") is strategic and may be model-chosen — but every *step* of executing it is a deterministic behavior-tree action from a fixed vocabulary, on a Sigma-bounded timer (COMBAT_ENCOUNTER_SYSTEM.md §8). The model picks *which* authored tactic; the sim *runs* it. A hallucinated tactic that isn't in the vocabulary is unrepresentable (§3 constrained decode) and, if it somehow appeared, unexecutable (no matching action id).

1.2 — Strategic generation is still guarded

Confining the model to strategy does not make strategy safe — a bad *goal* ("provoke the protected Sámi community," HL_0003) is as canon-breaking as a bad *fact*. So the guardrail loop (§2–§5) wraps strategic generation exactly as it wraps dialogue: a faction-intent choice is validated against the entity's hard-line rule vector before it reaches the sim; a plan graph is checked so every node is a real action id and no node violates a protected-community flag. Strategy is generated inside the same four-stage sandwich as speech.

---

2. The guardrail loop — one model call, four deterministic stages

Every generation — strategic or cosmetic — is a deterministic sandwich around a single bounded model call. The model is the meat; it never touches the outside world directly. This is the runtime shape of the build-time cascade recall → generate → lint → baseline loop.

  IN: (entity_id, event, live_sim_state_slice)
       |
  [1] CONTEXT PACK   — deterministic. Assemble a frozen, canon-true prompt from the
       |               entity's precomputed pack + the live state slice.  (§3)
       v
  [2] CONSTRAINED    — local LLM. Grammar/enum/canon-vocab-constrained decode, hard token
      DECODE           cap, prefix-cached persona preamble.  Malformed = impossible.  (§4)
       |
       v
  [3] VALIDATE       — deterministic. Battery of cheap checks vs the canon graph + rule
       |               vector (the runtime cousin of lint_canon_graph.py's G-gates).  (§5)
       |                 PASS  -> emit
       |                 FAIL  -> [4]
       v
  [4] FALLBACK       — deterministic. Emit the authored line/plan for (entity, state_bucket).
                       Log the miss to the reward corpus. Never block the frame.  (§6)

Four properties hold by construction: the prompt is always canon-true (stage 1 is code, not model); the output is always well-formed (stage 2 makes malformation unrepresentable); the output is always canon-valid or discarded (stage 3 is cite-or-drop); and the player always gets *something shippable* (stage 4 is the authored floor). The model is pure upside — never a dependency, exactly as continuo's free tier works fully offline with the model as enhancement, not requirement.

---

3. Deliverable (1) — the CONTEXT PACK: recall.py's "all-canon-for-a-node" query, frozen per entity

This is recall.py's recall node <id> union query (CANON_INTEGRITY_ARCHITECTURE.md §D, query shape 2 — "the load-bearing cascade query") run once at asset-bake time per entity, not per frame. It is continuo's §4.2 move exactly: pay the expensive, must-be-perfect recall once, deterministically, and hand the dumb model a bounded, pre-verified truth slice it cannot escape.

3.1 — What a pack is

A context pack is a small frozen struct, produced by a new offline exporter recall packs that walks canon_graph.json outward from the entity node and emits, per entity:

FieldSource (all live on disk / declared)Role
persona_cardT0_Voice_Registry.voice_substrate + T0_Character_Index / T0_Antagonist_Network_Registry; for fauna T0_Creature_Roster.folklore_behavior_substrate + folklore_attribution_source (HL_0002 primary-sourced)4–8 terse traits, a speech register, and the entity's language_primary. Persona lives in the pack, not in weights — one shared model, N personas.
fewshot_exemplarsAuthored alongside VO (same cascade authoring work)3–5 canon-clean lines that set the game's actual literary register (the 30M-word prose voice), pulling the model *toward* authored voice and *away* from generic-assistant voice. Also the fallback seeds (§6).
hardline_rule_vectorT0_Hard_Lines rows whose applies_to_chapter_range / applies_to_high_care_chapter / applies_to_pipeline (add enum value Runtime) touch this entity's chapter+regionCompiled to (a) a prompt preamble (natural language) *and* (b) a machine-checkable bit-vector for stage 3. Same rows the build-time G4 gate reads — one shared truth.
fact_whitelistThe entity's canon-graph neighborhood: the *only* worldstate/lore nodes this entity may nameThe provenance boundary. A fact not in the whitelist is, to this entity, unknowable → the persona is instructed to deflect, and stage 3 hard-fails any named entity not on the list. This is how a Ch 10 villager structurally *cannot* leak a Ch 76 reveal — the fact was never in its pack (F4).
retired_alias_indexrecall aliases reverse index + docs/id_normalization.json (Agartha→Maatherion, Hyperborea/Thule→Pohljar, Lemuria/Mu/Zealandia→Tiamarath, …)The same reverse index the build-time §17.7 straggler gate (G1) uses. Lets stage 3 catch a hallucinated retired name even though canon renamed it.
care_bandapplies_to_high_care_chapter + §17.1high_care / elevated_care flag. Raises the fallback bias and tightens the lexical denylist for §17.1-protected material.
action_vocabulary (strategic entities only)T0_Boss_Encounter_Registry phase/verb fields + the behavior-tree action tableThe closed set of tactic/plan-step ids the model may compose into a strategic plan. A plan node outside this set is unrepresentable (§4) and unexecutable.
state_bucket_mapAuthoredThe finite enumeration of (entity, state_bucket) keys that index the fallback bank (§6) — e.g. phase_2_enrage, player_high_integrity.

✅ Launch posture RULED (D-COMBAT-TD R7, Josh 2026-07-19) — the entity-class scalpel at the high-care chapters (Ch 29 / 42 / 57): bosses + generic ambient fauna ship runtime-generation ON under the standard V1–V5 guardrail, but each such boss's fact_whitelist excludes the Dreaming / songline / joik nodes (HL_0007). Community and cultural NPCs, and folklore-bearing fauna, are AUTHORED-ONLY; any entity whose care_band is high_care/elevated_care but is left untagged defaults to authored-only (fail-safe). Every affirmatively-ON tag takes a two-reviewer audit pre-launch, and any later community-NPC flip-on is gated on a human source-fidelity review, never the §7 metric alone. Cite basis: HL_0002 (retained) + HL_0003 / HL_0005 / HL_0007. This ruling supersedes the §9 open-item recommendation below.

3.2 — Why per-entity-frozen is the whole efficiency story

Because the pack is precomputed and immutable at entity-load, the per-frame cost is zero graph traversal. Runtime prompt assembly is: load cached pack (already resident) → splice in the live sim-state slice (a handful of scalars: current phase, player's last action class, integrity band, recent-event flags) → hand to the decoder. The frozen persona preamble is KV-prefix-cached on the inference server (continuo §4.2 read-time-cheap), so only the short variable tail is ever decoded. On consumer hardware this is the difference between shippable and not: no per-frame graph walk, no per-frame retrieval, no growth without bound (continuo §4.5 — the pack is current-truth state, not an append-only context that balloons).

3.3 — The live-state slice is the *only* dynamic input, and it is trusted

Everything canon in the prompt is frozen and pre-verified. The only per-call variability is the live-state slice, and it comes from the deterministic sim, not the model — the boss's own next-move intent, the player's measured player_habit_vector (COMBAT_ENCOUNTER_SYSTEM.md §4.9), the worldstate delta. So when a boss "reads you," the tactical fact is *true by construction* (the sim measured it); the model only chooses how to voice it. This is what makes the adaptivity real and safe at once: the model never invents the tactic, it phrases a tactic the sim handed it.

3.4 — Player text is a retrieval key, never a prompt (injection defense)

For talk systems, raw player text is never concatenated into the prompt as instruction. It is (a) intent-classified by a small local classifier / keyword map to a topic id, which selects a whitelist slice, and (b) quarantined behind a delimiter marked untrusted, with the system preamble stating so. The player cannot steer the model outside its pack because the pack, not the player, defines what is knowable. (Continuo M1 — eliminate the injection surface at the source.)

---

4. Deliverable (2) — CONSTRAINED DECODING: structurally & canonically invalid output made impossible

Continuo M7 — *make illegal states unrepresentable*. The highest-value guardrail is the one that deletes a failure class instead of catching it. Constrained decoding removes malformed and out-of-vocabulary output *before it can be sampled*, so stage 3 never has to catch F8 (malformed structure) at all.

4.1 — Three layers of constraint, tightest-first

1. Structural grammar (GBNF / JSON-schema / regex-constrained sampler — llama.cpp grammars, Outlines, or the engine-embedded equivalent). The output *shape* is guaranteed: a bark is a JSON object {emotion: <enum>, line: <string ≤N tokens>}; a strategic choice is {intent: <enum>, target: <enum>}; a plan is an array of nodes each {action: <action_id ∈ action_vocabulary>, arg: <enum>}. The decoder cannot emit malformed JSON, an out-of-enum emotion, an unknown action_id, or an over-length line. This single mechanism deletes: structural corruption, invalid strategic enums, and hallucinated action ids (a plan step outside action_vocabulary is off the grammar and un-sampleable).

2. Canon-vocabulary constraint (the novel layer — see §4.2). For fields that name entities, the grammar's terminal for "proper noun slot" is restricted at decode time to the union of the entity's fact_whitelist surface forms plus a common-noun escape. The model literally *cannot spell* a person/place/item outside its whitelist, because those tokens are masked out of the proper-noun position. This preempts F1 (hallucinated entity) and F2 (retired alias) at the sampler, not the validator.

3. Hard token cap per class. Barks 12–20 tokens; ambient ≤40; probe responses ≤80; a strategic plan ≤K nodes. Continuo §4.4 — over budget ⇒ truncate to fallback, never expand. Short outputs are faster, cheaper, and have less surface to go wrong.

4.2 — The improvement over the state of the art (do not copy — better it)

Standard grammar-constrained decoding (Outlines, llama.cpp grammars, XGrammar) constrains output to a *static* schema — a fixed JSON shape or a fixed enum known at compile time. The novelty here is a per-entity dynamic canon-vocabulary mask driven by the canon graph: the set of legal proper-noun tokens is not a compile-time constant, it is the *runtime* fact_whitelist of *this* entity, compiled to a token-id allow-set when the pack loads and installed as a logit mask on the proper-noun grammar terminal. Two entities standing next to each other run the same model and the same base grammar but decode against different legal-name universes — the Ch 10 villager physically cannot utter "Maatherion"; the Ch 69 sage can. This is retrieval-augmented *generation-time* constraint (the graph shapes the sampler, not just the prompt), and it collapses the two most dangerous lore-break classes (invented names, retired aliases) from "validated after the fact" to "impossible to sample." It is cheap: a precomputed token-id set per pack and a logit mask applied only at proper-noun positions (a tiny fraction of decode steps), so it costs near nothing per token on consumer hardware. Stage 3's name-resolution check (§5) then becomes a belt-and-suspenders assertion that should never fire — and if it does, it means a pack-build bug, which is a build-time failure, not a runtime one.

The design rule: prefer the constraint that makes the mistake unrepresentable over the check that catches it. Everything a grammar or a mask can prevent, it prevents; the validator (§5) only catches what cannot be structurally forbidden (semantics: intent, register, slop).

---

5. Deliverable (3) — POST-GENERATION VALIDATION: the deterministic gate (where continuo lives)

The runtime cousin of lint_canon_graph.py's G-gate battery: cheap deterministic checks that catch the *specific* ways this dumb model fails, ordered fast-and-common first, any hard-fail → fallback. Each check maps to a build-time gate so build and runtime enforce the same canon from the same source of truth (continuo: one Constitution, cited).

#CheckTypeRuntime cousin ofCatches
V1Cultural denylist — regex + phrase set over the §17.1 forbidden-framing corpus (HL_0005: Vodou-as-zombie, shaman-as-mystical-other, temple-as-trap; joik/songline misuse; slurs). Runs first, fastest.Hard failG9 (encoding/sentinel hygiene) + §17.1 corpusF3
V2Hard-line rule-vector check — the entity's compiled hardline_rule_vector. For strategic output: a faction-intent/plan must not carry combat-provocation intent against a protected-community flag (HL_0003, Ch 29/42/57). For dialogue: no §17.5 Grand-Sage pre-reveal token, no protected-combat provocation. Intent is classified by a small local classifier.Hard failG4 (HL + §-ref resolution)F4 (leak), F5 (provoke)
V3Canon-graph name resolution — every proper noun / named entity in the output must resolve to a node in this entity's fact_whitelist, checked *including* the retired-alias reverse index. Should be near-vacuous given §4.2's decode-time mask; a fire here = a pack-build bug, flagged loud.Hard failG1 (retired-name straggler) + G2 (entity resolution)F1, F2
V4Persona / register check — output language_primary matches the pack; register keywords present/absent; length in band; strategic choice is legal for the entity's current state.Soft fail (one retry, else fallback)G-lint persona rulesF6
V5Anti-slop / repetition — reject if within edit-distance ε of this entity's last K lines, or matches the global slop-phrase set ("Ah, a challenger approaches," "You dare?," generic LLM tics). Keeps the pervasive layer feeling authored, not autocompleted.Soft fail (one retry, else fallback)G8 (doc-health / dup detection), spirit-ofF7

V1–V3 are correctness gates (hard-fail → immediate fallback, no retry — correctness is not negotiable and a retry burns the latency budget). V4–V5 are quality gates (soft-fail → one cheap retry if the latency budget allows, else fallback). Every failure — hard or soft — writes {entity, state, prompt_hash, output, failed_check, latency} to the rotating runtime_generation_log. That log is the reward corpus (§7). The validator is *stateless and cheap*: regexes, a set-membership test against a precomputed token/name set, a length check, an edit-distance against a K-line ring buffer, and one small intent classifier — all comfortably inside the frame-off-thread budget.

---

6. Deliverable (3, cont.) — AUTHORED FALLBACK: the shippable floor

Continuo M4 — never silently fail or guess; surface the miss, ship the safe answer. Every generative slot has a deterministic authored fallback keyed by (entity_id, state_bucket) — the same state_bucket_map in the pack (§3.1). The bank per bucket is 3–6 hand-written lines (RNG-picked, Sigma-bounded so it doesn't repeat) authored alongside the few-shot exemplars.

Fallback triggers on any of: a V1–V3 hard fail, a V4–V5 soft-fail after the one-retry budget, a latency-budget miss (§8), or a decoder/grammar error. For strategic output, the fallback is the deterministic default behavior the sim would run absent the model — the authored behavior-tree default, the scripted disposition. So a boss whose strategic generation misses simply plays its hand-authored fight; the player sees a solid, canon-clean encounter, never a hole, hang, or slop line.

This is what makes the whole system safe to ship at AAA quality: the local LLM is upside, never a dependency. Worst reachable output is a hand-written line or the authored default behavior — identical to shipping the game with no LLM at all. The model can only *improve* on the floor; it can never drop below it. (Continuo §4.11: the free tier — here, the fallback-only mode — works fully, offline, forever.)

---

7. Deliverable (4) — FAILURE/REWARD DISCIPLINE: the F1–F10 gates as runtime code

Continuo's real lesson was not "add a model" — it was enumerate the specific ways the dumb model fails, encode each as a deterministic catcher, and turn every caught failure into a reward signal that hardens the system. This is the runtime analog of the harness's F1–F10 gate discipline. Each row is a real, cheap, deterministic mechanism, most-prevented-by-construction-first:

#Dumb-LLM failure modeDeterministic catcher (stage)Prevented or caughtReward-pattern encoding
F1Hallucinates a person/place/item not in canon§4.2 canon-vocab decode mask → §5 V3Prevented (masked at sampler); V3 is the assertionLogged miss → negative few-shot; if the name is *wanted*, it routes to Josh as [DECISION NEEDED - JOSH], never auto-canonized
F2Uses a retired alias (Agartha, Hyperborea, Lemuria…)§4.2 mask (aliases excluded) + §5 V3 reverse-indexPreventedSame id_normalization.json reverse index as build-time G1 — one shared truth source
F3Violates a §17.1 cultural hard line§5 V1 denylist + V2 rule vectorCaughtDenylist corpus grows from every catch; high-care packs bias to fallback
F4Leaks a future/protected fact (Grand-Sage pre-Ch-76, §17.5; a plot reveal)§3 fact_whitelist absence + §5 V2 token denylistPrevented (fact never in pack)A leak = a pack-build bug, gated at build, not runtime — structural prevention
F5Provokes combat vs a protected community (HL_0003)§5 V2 intent classifier vs protected flagCaughtCh 29/42/57 entities carry the flag from the pack; catch → fallback + log
F6Breaks persona / wrong register / wrong language§5 V4Caught (soft)Persistent misses re-tune that entity's exemplar set
F7Slop / repetition / generic-LLM tic§5 V5 edit-distance + global slop setCaught (soft)Global slop set shared across all entities, grows from logs
F8Malformed structure (bad JSON, out-of-enum, over-length)§4.1 grammar-constrained decodePreventedImpossible by construction — the highest-value encoding
F9Latency miss under load§8 budget timeout → §6 fallbackCaughtMiss rate is a monitored metric; sustained misses trigger pre-warm/cache tuning, never player-visible degradation
F10Tries to affect game state / act per-frame§1 wall: no write path, no per-frame verb in the APIPreventedStructural — generated output is a pure leaf; strategy is a *proposal* the sim disposes

The reward loop closes exactly like continuo's and like this repo's harness. Every caught failure writes to the local runtime_generation_log, which (a) grows the authored-fallback bank, (b) grows the denylist/slop sets, and (c) becomes eval fixtures. A nightly/CI offline job replays the logged prompts against candidate model+prompt versions and scores them with the *same §5 checks* — yielding a truth-recall-style metric: first-try guardrail pass rate, directly analogous to continuo's near-perfect scorecard. You ship a model+prompt version only when its first-try pass rate clears the bar. The fallback floor guarantees quality never drops below "shippable" regardless of the model version — so the model can be upgraded fearlessly across the game's life. This is the whole continuo bet applied to runtime: the model gets smarter over time, but the system is already correct on day one because the deterministic scaffolding — not the model — guarantees canon.

---

8. Deliverable (5) — OFFLINE & DETERMINISM-WHERE-NEEDED GUARANTEES

The layer must be invisible, fully offline, deterministic where reproducibility matters, and never the reason a frame drops. On consumer hardware (a player's own GPU shared with the game), efficiency is the gating constraint, so every choice below is an efficiency choice as much as a safety one.

8.1 — Fully offline, self-contained (continuo §4.11)

Model weights, canon_graph.json, the per-entity packs, the validator, the denylists, the alias index, and the fallback bank ship in the build. Zero network calls; no cloud inference; no telemetry required to function. The runtime_generation_log is local-only; §7's tuning corpus uploads only on explicit opt-in. The game is fully playable — at the authored floor — with the LLM feature disabled entirely.

8.2 — The efficiency budget (illustrative shape; locked at a runtime perf audit)

8.3 — Determinism where needed (mirrors the harness's declared-baseline discipline)

---

9. Buildability — how this lands on what already exists

Nothing here is greenfield; it extends artifacts on disk today.

Open items for Josh (flagged, not decided — the do-not-invent rule binds)

---

10. One-paragraph synthesis

The pervasive local-LLM layer ships safely because a *dumb* model, confined by an architectural wall to strategic-and-cosmetic output only (§1), performs surface texture over a truth store it cannot touch — the exact /dev/continuo bet (a lesser model wrapped in deterministic code that encodes its failure/reward patterns reaches near-perfect reliability), operationalized on our canon graph. The safety spine is five deterministic mechanisms: a context pack that freezes recall.py's all-canon-for-a-node query per entity so the prompt is always canon-true and the per-frame cost is zero graph work (§3); constrained decoding whose novel per-entity canon-vocabulary mask makes structurally *and* canonically invalid output impossible to sample, not merely caught (§4); a post-generation validator that is the runtime cousin of the harness G-gates, cite-or-drop against the same alias/hard-line sources (§5); an authored fallback floor that makes the model pure upside and never a dependency (§6); and a failure/reward discipline — the F1–F10 gates as runtime code, with a nightly first-try-pass-rate metric analogous to continuo's scorecard — that hardens the system from every caught miss while the deterministic scaffolding guarantees canon on day one (§7). All of it runs fully offline on consumer hardware, deterministic where reproducibility matters (§8), and lands on artifacts already on disk (§9). The life is real; the lies are structurally impossible.

Generated by harness/site/structure_site.py — the URL path is the repo path. review root