Cartridge Developer Guide¶
This guide describes how to build and validate a MnesOS cartridge, alongside implementing complex mechanics like countering and phase-based intention.
Cartridge Contract & Structure¶
Create a cartridge under cartridges/<your-game-name>/.
Required files:
1. yare.yaml
2. bot_lore.md
Optional file:
3. prompt_directives.yaml
yare.yaml stays procedural. Narrative steering belongs in prompt_directives.yaml, not in the rules file.
bot_lore.md¶
Use this file for world facts, NPC descriptions, locations, factions, and items.
- structure content with markdown headers because the lore store chunks on #, ##, and ###
- keep mechanics out of lore text
- use descriptive section names so retrieval has strong anchors
prompt_directives.yaml¶
Use this file only for short per-role LLM steering.
Important Note on State Access: When instructing the LLMs to check the game state, you MUST refer to it as bot_memory (e.g., If bot_memory['player']['hp'] < 10...). The state. prefix is only used inside yare.yaml.
Allowed keys: director, npc, narrator.
If prompt_directives is placed inside yare.yaml, the loader rejects the cartridge.
yare.yaml Integration¶
Use this file for deterministic state and event logic.
- state_schema defines tracked state and defaults
- macros define reusable @ expressions
- events define executable steps
For full details on supported expressions, operators, step types (like set, mutate, branch, call, table_roll, etc.), please refer to the complete YARE Specification.
State Visibility¶
Each schema field may define visibility (public or private).
state_schema:
player:
hp: { type: int, default: 100, min: 0, visibility: public }
hidden_flag: { type: bool, default: false, visibility: private }
publicfields are eligible for narrator context.- omitted
visibilitydefaults toprivate. privatefields are excluded from the narrator context to keep hidden mechanics deterministic without leaking them into player-facing narration, but they remain fully accessible to the YARE interpreter for rules and logic.
This keeps hidden mechanics deterministic without leaking them into player-facing narration.
Advanced Mechanics & Counter-Play¶
This section outlines the operational domain of the MnesOS 2-node graph, including how system_notes are safely passed to the Narrator and how complex combat systems can be implemented.
1. Semantic Grounding (Ensuring Narrator Understanding)¶
The Narrator LLM needs to understand the numbers generated by YARE without the user seeing the raw math. Cartridge developers must provide semantic grounding within note event actions.
Use variable interpolation ({variable}) inside note strings and format them as instructions or stage directions for the Narrator. Enclosing them in tags (like [SYSTEM LOG: ...]) ensures the Narrator recognizes it as backstage instruction.
Good Practice (Semantic Grounding):
- action: mutate
var: "state.goblin.hp"
op: "sub"
value: 10
- action: note
message: "[SYSTEM LOG: Player successfully landed an attack on the Goblin. Base damage was 10. The Goblin's remaining HP is {state.goblin.hp}. Describe the strike connecting and the Goblin reeling.]"
2. The Operational Domain & Turn Iterations¶
The execution graph flows iteratively between the Director and the ToolNode before finally reaching the Narrator:
Director (Intent Analysis) 🔁 ToolNode (Mechanics & NPC Intent) ➔ Narrator (Render)
If an action is "immediate", reactive "counters" within the same turn require the Director to query the NPC before finalizing damage. Complex rules require designing YARE events using Phase-Based Intention and Telegraphing.
3. Scenario A: Player attacks, NPC Counters (Shield Block)¶
If a player attacks, the Director should query the NPC intent before applying final damage.
How it works:
1. Player says "I attack the Goblin".
2. Director queries NPC intent using query_npc_intent.
3. NPC Intent tool responds: "I intend to raise my shield!"
4. Director resolves the attack mechanics (YARE), taking the block into account, and summarizes.
For complex logic: Trigger plan_player_attack to save values to a buffer, query NPC, and in the next loop, run mechanics resolution to clear the buffer and apply damage.
4. Scenario B: NPC Telegraphs, Player Counters¶
The Player cannot physically interrupt an active Turn resolution. To allow a Player to counter an NPC, the NPC must telegraph an attack on Turn 1, so the Player can respond on Turn 2.
How it works:
1. Turn N (Mechanics Phase): Director triggers npc_telegraph_attack.
2. Turn N (Narrator): Narrator writes "The Goblin raises his heavy club, preparing to smash it down!"
3. Turn N+1 (Player Phase): Player says "I raise my shield to brace." Director triggers player_resolve_incoming.
(Refer to earlier documentation or YARE design patterns for specific implementation details on buffering incoming_attack_val and clearing it upon resolution).
Conversion Checklist & Things Not To Rely On¶
Conversion Checklist¶
- Extract all stats, resources, and flags into
state_schema. - Move every deterministic rule into
events. - Add bounds with
minandmaxwhere needed. - Mark player-visible fields with
visibility: public. - Move flavor text into
bot_lore.md. - Move tone instructions into
prompt_directives.yaml.
Anti-Patterns (Do Not Do These)¶
- do not rely on hardcoded event-name matching in engine code
- do not rely on cartridge-specific NPC behavior embedded in Python nodes
- do not put prompt directives inside
yare.yaml - do not assume the engine persists state for you; the client must store and re-supply the returned game state each turn