# Prompt Template System ## Template language: Inja, NOT Jinja2 `[verified]` from `prompts/agent_prompt_helper.prompt:138-310`, which contains the canonical syntax reference embedded into the in-game prompt editor. Inja is a C++ Jinja2-inspired template engine. It looks similar but is **strictly more limited**. ### Key differences from Jinja2 worth knowing - **Limited filter set.** Standard Jinja filters like `default`, `upper`, `length`, `join`, `sort` are present; less common ones may not be. - **`{% set %}` only affects render context** — does NOT mutate input data. - **Array manipulation functions return new arrays** — `append`, `extend`, etc. don't modify originals. - **Template inheritance is limited:** `{% extends "..." %}` + `{% block name %}...{% endblock %}` works; multiple inheritance does not. - **No macros** in the Jinja2 sense. - **Backslashed paths in includes:** `render_template("components\\event_history_compact")` — the `\\` is the path separator in include calls. ### SkyrimNet-specific Inja extensions **Section markers** split a single `.prompt` file into multiple chat messages: ``` [ system ] ... system message content ... [ end system ] [ user ] ... user message content ... [ end user ] [ assistant ] ... assistant message content (rare) ... [ end assistant ] ``` **Special blocks:** - `[ raw ] ... [ end raw ]` — prevents Inja parsing of `{{...}}` etc. Used for embedded examples that contain template syntax literally. - `[ cache ] ... [ end cache ]` — marks a block as cacheable for prompt caching across requests. **Include functions:** - `render_template("path\\with\\backslashes")` — include another `.prompt` from `prompts/`. - `render_subcomponent("subdir", render_mode)` — render numbered submodules from a subdir in load order. - `render_character_profile("mode", uuid)` — render an NPC's bio with a render mode (`full`, `target`, `transform`, `thoughts`, `short_inline`, `interject_inline`). **Decorators** (DLL-registered functions callable from templates): - `decnpc(uuid)` — universal NPC info accessor; returns object with `name`, `firstName`, `lastName`, `race`, `gender`, pronouns (`subjectivePronoun`, `objectivePronoun`, `possessivePronoun`, `reflexivePronoun`), `level`, `health`, `magicka`, `stamina`, `faction[]`, `isInCombat`, `isHostile`, `isDead`, `isBusy`, all skill values. - 30+ other decorators observed in trace dump: `is_in_faction`, `is_player`, `get_arousal_state`, `format_event`, `short_time`, `get_name`, `get_nearby_npc_list`, `get_recent_events`, `get_quest_stage`, `is_narration_enabled`, `get_relevant_memories`, `get_scene_context`, `get_world_knowledge`, `papyrus_util`, `outfit_context`, etc. `[unknown]` Full decorator list with signatures — dynamically generated by the DLL via `prompts/documentation/main.prompt`. Would require dumping the in-game web UI's `/api/documentation` endpoint to enumerate. --- ## The three-layer override architecture `[verified]` Load priority **high → low** (later layers shadow earlier ones for the same path): | # | Path | Purpose | Notes | |---|---|---|---| | 1 | `overwrite/SKSE/Plugins/SkyrimNet/prompts/` | **Runtime overrides.** Edits via the in-game prompt editor land here. | Selectively populated. Contains epoch-stamped backups (`agent_chat.prompt.backup.1776372078`). Also hosts runtime-generated content: `dynamic_character_bio.prompt` (~173KB regenerated per session), `_saves/`, `characters/` (per-actor profiles). | | 2 | `mods/SkyrimNet/SKSE/Plugins/SkyrimNet/prompts/` | **Shipped baseline + dev/user edits.** | Where our `gamemaster_action_selector.prompt` fix lives. The `.backup` from the fix is here too: `gamemaster_action_selector.prompt.backup`. | | 3 | `mods/SkyrimNet/SKSE/Plugins/SkyrimNet/original_prompts/` | **Pristine upstream baseline.** Useful for diffing to detect upstream changes. | `[hypothesis]` Only `characters/` and `submodules/system_head/` are populated; the rest of upstream prompts may be assumed copied to `prompts/` at install time, or this directory only holds pristine *overrideable* defaults. | **Practical implication:** before editing any prompt, check if a runtime override exists in `overwrite/prompts/`. If it does, *that* is the active version — editing in `mods/prompts/` will be silently shadowed. `[verified]` overlay pattern is the same shape as Linux `/etc/foo.conf` + `/etc/foo.conf.d/` + package defaults. --- ## Submodule numbering convention `[verified]` Submodules in `prompts/submodules//` are loaded in numerical filename order (e.g. `0010_*.prompt` before `0750_*.prompt`). The numbering carves out conventional ranges: | Range | Conventional purpose | Examples | |---|---|---| | `0010_…0099_` | Meta/header content (instructions, scene context bootstrap) | `0010_instructions.prompt`, `0250_omnisight.prompt` | | `0100_…0499_` | Content sections (summary, background, personality, appearance, equipment) | character bio sections | | `0500_…0799_` | Guidelines and behavioral rules | `0750_embedded_actions.prompt` (the ACTION: format spec) | | `0800_…0999_` | Late instructions and special toggles | direct narration, recent state changes | | `7000_…7999_` | Memories and progression | NPC memory blocks | | `9990_` | Speech style — rendered last to override earlier voice instructions | speech style submodules | Earlier numbers establish baseline; higher numbers customize/override. Fits the late-binding-wins pattern. --- ## Subdirectory map of `prompts/` `[verified]` | Dir | Purpose | Sample contents | |---|---|---| | `prompts/` (root) | Top-level entry-point prompts (one per agent type) | `dialogue_response.prompt`, `gamemaster_action_selector.prompt`, `native_action_selector.prompt`, `agent_chat.prompt`, `gamemaster_scene_planner.prompt`, `player_dialogue.prompt`, `player_thoughts.prompt` | | `prompts/components/` | Reusable building blocks for `render_template("components\\X")` | `event_history.prompt`, `event_history_compact.prompt`, `event_history_verbose.prompt`, `agent_tools_base.prompt`, `memory_access.prompt`, character_bio variants | | `prompts/components/context/` | Context-specific component variants | `scene_context.prompt`, `component_npc_state_summary.prompt` | | `prompts/submodules/system_head/` | System-prompt prelude pieces | `0010_instructions.prompt`, `0250_omnisight.prompt`, … | | `prompts/submodules/user_final_instructions/` | User-message tail pieces | `0750_embedded_actions.prompt` (ACTION: format), audio tag rules, recent state changes | | `prompts/submodules/guidelines/` | Cross-cutting style rules | `0900_response_format.prompt` (asterisk narration rules, length limits) | | `prompts/submodules/character_bio/` | Per-actor bio submodules merged into `dynamic_character_bio.prompt` | 17 numbered files: header, summary, personality, equipment, etc. | | `prompts/submodules/omnisight_*/` | Vision-model description templates (one dir per target type: actor, scene, item, location, furniture, default) | matched 1:1 by `prompts/omnisight/describe_*.prompt` | | `prompts/submodules/test_decorators/` | Test-data harness for the in-game template debugger | enabled by MCM toggle | | `prompts/target_selectors/` | Meta classifier prompts for "who speaks next" | `dialogue_speaker_selector.prompt`, `player_dialogue_target_selector.prompt` | | `prompts/transformers/` | Text→text transformations | `native_dialogue_transformer.prompt`, `universal_translator.prompt` | | `prompts/memory/` | Memory generation, ranking, mood evaluation | `generate_memory.prompt`, `memory_ranker.prompt`, `mood_evaluator.prompt` | | `prompts/helpers/` | Standalone classifier/profile generation prompts | `evaluate_mood.prompt`, `generate_profile.prompt`, `generate_search_query.prompt` | | `prompts/omnisight/` | Vision-model prompts (one per target type) | `describe_actor.prompt`, `describe_scene.prompt`, `describe_item.prompt`, `describe_location.prompt`, `describe_furniture.prompt` | | `prompts/dev/` | Developer test prompts | `mcm_test.prompt` | | `prompts/documentation/` | Auto-generated decorator documentation rendered into the in-game web UI | `main.prompt`, `category.prompt` | | `prompts/web/` | Web-UI bundled base templates | `bundled_base.prompt` | | `prompts/translation/{generic,unique}/` | Localization CSVs | `00_SkyrimNet_generic.csv`, `00_SkyrimNet_unique.csv` | --- ## Editing prompts safely — checklist 1. **Is there an override in `overwrite/prompts/`?** If yes, that's the active version. Edit it (the in-game UI does this) or delete it to fall back to `mods/prompts/`. 2. **Diff against `original_prompts/`** to see what the upstream baseline says. 3. **Make a `.backup` next to the file** before significant edits — SkyrimNet itself does this convention with epoch-stamped backups; we use the simpler `.backup` suffix. 4. **Hot-reload picks up changes** — no need to restart Skyrim for prompt edits, but model behavior may take effect on the next agent firing. 5. **Test against logs** — watch `openrouter_input.log` to see what prompt text the model actually receives after your edit.