# 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/<category>/` 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.
