8.9 KiB
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,sortare 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.promptfromprompts/.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 withname,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
- 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 tomods/prompts/. - Diff against
original_prompts/to see what the upstream baseline says. - Make a
.backupnext to the file before significant edits — SkyrimNet itself does this convention with epoch-stamped backups; we use the simpler.backupsuffix. - Hot-reload picks up changes — no need to restart Skyrim for prompt edits, but model behavior may take effect on the next agent firing.
- Test against logs — watch
openrouter_input.logto see what prompt text the model actually receives after your edit.