MnesOS 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/SKILL.md +42 -0
  2. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/bot_lore.md +25 -0
  3. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/prompt_directives.yaml +7 -0
  4. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/yare.yaml +102 -0
  5. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/architecture_analysis.md +40 -0
  6. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/cartridge-guide.md +135 -0
  7. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/combat_mechanics.md +123 -0
  8. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/yare-specification.md +195 -0
  9. mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/scripts/setup_cartridge.py +39 -0
  10. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/SKILL.md +60 -0
  11. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/architecture.md +87 -0
  12. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/architecture_analysis.md +40 -0
  13. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/cartridge-guide.md +135 -0
  14. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/combat_mechanics.md +123 -0
  15. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/yare-specification.md +195 -0
  16. mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/scripts/create_cartridge.py +32 -0
  17. mnesos-0.6.0/.github/workflows/ci-dev.yml +106 -0
  18. mnesos-0.6.0/.github/workflows/ci-main.yml +30 -0
  19. mnesos-0.6.0/.github/workflows/docs.yml +60 -0
  20. mnesos-0.6.0/.github/workflows/release.yml +124 -0
  21. mnesos-0.6.0/.gitignore +222 -0
  22. mnesos-0.6.0/PKG-INFO +108 -0
  23. mnesos-0.6.0/README.md +71 -0
  24. mnesos-0.6.0/artifacts/.gitkeep +0 -0
  25. mnesos-0.6.0/cartridges/dark-fantasy/bot_lore.md +9 -0
  26. mnesos-0.6.0/cartridges/dark-fantasy/play-test.py +67 -0
  27. mnesos-0.6.0/cartridges/dark-fantasy/prompt_directives.yaml +6 -0
  28. mnesos-0.6.0/cartridges/dark-fantasy/yare.yaml +62 -0
  29. mnesos-0.6.0/cartridges/generic-rpg/bot_lore.md +20 -0
  30. mnesos-0.6.0/cartridges/generic-rpg/prompt_directives.yaml +27 -0
  31. mnesos-0.6.0/cartridges/generic-rpg/tests/test_run.py +37 -0
  32. mnesos-0.6.0/cartridges/generic-rpg/yare.yaml +117 -0
  33. mnesos-0.6.0/configs/.gitkeep +0 -0
  34. mnesos-0.6.0/docs/api.md +3 -0
  35. mnesos-0.6.0/docs/cartridge-guide.md +135 -0
  36. mnesos-0.6.0/docs/design/0001-graph-architecture.md +40 -0
  37. mnesos-0.6.0/docs/design/0002-state-and-turn-flow.md +87 -0
  38. mnesos-0.6.0/docs/design/0003-npc-interaction-model.md +59 -0
  39. mnesos-0.6.0/docs/design/0004-shift-gears-to-stateless.md +75 -0
  40. mnesos-0.6.0/docs/design/0005-interfaces-and-contracts.md +261 -0
  41. mnesos-0.6.0/docs/guides/advanced-combat-mechanics.md +123 -0
  42. mnesos-0.6.0/docs/img/library_view.png +0 -0
  43. mnesos-0.6.0/docs/img/new_persona_modal.png +0 -0
  44. mnesos-0.6.0/docs/img/personas_view.png +0 -0
  45. mnesos-0.6.0/docs/img/play_view.png +0 -0
  46. mnesos-0.6.0/docs/img/play_view_debug_open.png +0 -0
  47. mnesos-0.6.0/docs/img/settings_modal.png +0 -0
  48. mnesos-0.6.0/docs/img/start_new_game_modal.png +0 -0
  49. mnesos-0.6.0/docs/index.md +13 -0
  50. mnesos-0.6.0/docs/play-test-win.md +244 -0
  51. mnesos-0.6.0/docs/play-test.md +503 -0
  52. mnesos-0.6.0/docs/yare-specification.md +195 -0
  53. mnesos-0.6.0/mkdocs.yml +63 -0
  54. mnesos-0.6.0/notebooks/.gitkeep +0 -0
  55. mnesos-0.6.0/notebooks/graph_showcase.ipynb +471 -0
  56. mnesos-0.6.0/notebooks/orchestrator_showcase.ipynb +187 -0
  57. mnesos-0.6.0/notebooks/test_notebook.ipynb +44 -0
  58. mnesos-0.6.0/pyproject.toml +75 -0
  59. mnesos-0.6.0/scripts/delete_db.py +29 -0
  60. mnesos-0.6.0/scripts/migrate_db.py +32 -0
  61. mnesos-0.6.0/scripts/setup_github_rules.py +312 -0
  62. mnesos-0.6.0/scripts/strip_notebook_outputs.py +98 -0
  63. mnesos-0.6.0/setup.cfg +4 -0
  64. mnesos-0.6.0/src/MnesOS/__init__.py +29 -0
  65. mnesos-0.6.0/src/MnesOS/_version.py +24 -0
  66. mnesos-0.6.0/src/MnesOS/api/__init__.py +6 -0
  67. mnesos-0.6.0/src/MnesOS/api/app.py +36 -0
  68. mnesos-0.6.0/src/MnesOS/api/cartridges.py +430 -0
  69. mnesos-0.6.0/src/MnesOS/api/deps.py +150 -0
  70. mnesos-0.6.0/src/MnesOS/api/instances.py +165 -0
  71. mnesos-0.6.0/src/MnesOS/api/personas.py +159 -0
  72. mnesos-0.6.0/src/MnesOS/api/routes.py +238 -0
  73. mnesos-0.6.0/src/MnesOS/api/saves.py +101 -0
  74. mnesos-0.6.0/src/MnesOS/api/schemas.py +255 -0
  75. mnesos-0.6.0/src/MnesOS/api/turns.py +79 -0
  76. mnesos-0.6.0/src/MnesOS/api/users.py +109 -0
  77. mnesos-0.6.0/src/MnesOS/cartridge.py +563 -0
  78. mnesos-0.6.0/src/MnesOS/context.py +78 -0
  79. mnesos-0.6.0/src/MnesOS/graph/__init__.py +32 -0
  80. mnesos-0.6.0/src/MnesOS/graph/factory.py +91 -0
  81. mnesos-0.6.0/src/MnesOS/graph/nodes/__init__.py +0 -0
  82. mnesos-0.6.0/src/MnesOS/graph/nodes/director.py +59 -0
  83. mnesos-0.6.0/src/MnesOS/graph/nodes/lore.py +50 -0
  84. mnesos-0.6.0/src/MnesOS/graph/nodes/narrator.py +58 -0
  85. mnesos-0.6.0/src/MnesOS/graph/nodes/system.py +84 -0
  86. mnesos-0.6.0/src/MnesOS/graph/state.py +68 -0
  87. mnesos-0.6.0/src/MnesOS/graph/tools/__init__.py +0 -0
  88. mnesos-0.6.0/src/MnesOS/graph/tools/npc.py +128 -0
  89. mnesos-0.6.0/src/MnesOS/graph/tools/time.py +16 -0
  90. mnesos-0.6.0/src/MnesOS/graph/tools/yare.py +88 -0
  91. mnesos-0.6.0/src/MnesOS/graph/utils/__init__.py +0 -0
  92. mnesos-0.6.0/src/MnesOS/graph/utils/messages.py +14 -0
  93. mnesos-0.6.0/src/MnesOS/graph/utils/persona.py +20 -0
  94. mnesos-0.6.0/src/MnesOS/graph/utils/time.py +62 -0
  95. mnesos-0.6.0/src/MnesOS/interpreter/__init__.py +102 -0
  96. mnesos-0.6.0/src/MnesOS/interpreter/actions/__init__.py +0 -0
  97. mnesos-0.6.0/src/MnesOS/interpreter/actions/core.py +167 -0
  98. mnesos-0.6.0/src/MnesOS/interpreter/evaluator.py +150 -0
  99. mnesos-0.6.0/src/MnesOS/interpreter/store.py +93 -0
  100. mnesos-0.6.0/src/MnesOS/orchestrator.py +349 -0
  101. mnesos-0.6.0/src/MnesOS/prompts.py +148 -0
  102. mnesos-0.6.0/src/MnesOS/storage/__init__.py +41 -0
  103. mnesos-0.6.0/src/MnesOS/storage/hydrator.py +116 -0
  104. mnesos-0.6.0/src/MnesOS/storage/interface.py +234 -0
  105. mnesos-0.6.0/src/MnesOS/storage/models.py +201 -0
  106. mnesos-0.6.0/src/MnesOS/storage/sqlite3_store.py +979 -0
  107. mnesos-0.6.0/src/MnesOS/utils/__init__.py +3 -0
  108. mnesos-0.6.0/src/MnesOS/utils/create_creator.py +127 -0
  109. mnesos-0.6.0/src/MnesOS/utils/ingest_cartridges.py +259 -0
  110. mnesos-0.6.0/src/MnesOS/utils/start_game.py +135 -0
  111. mnesos-0.6.0/src/MnesOS.egg-info/PKG-INFO +108 -0
  112. mnesos-0.6.0/src/MnesOS.egg-info/SOURCES.txt +185 -0
  113. mnesos-0.6.0/src/MnesOS.egg-info/dependency_links.txt +1 -0
  114. mnesos-0.6.0/src/MnesOS.egg-info/entry_points.txt +2 -0
  115. mnesos-0.6.0/src/MnesOS.egg-info/requires.txt +25 -0
  116. mnesos-0.6.0/src/MnesOS.egg-info/top_level.txt +1 -0
  117. mnesos-0.6.0/tests/__init__.py +1 -0
  118. mnesos-0.6.0/tests/conftest.py +94 -0
  119. mnesos-0.6.0/tests/integration/__init__.py +0 -0
  120. mnesos-0.6.0/tests/integration/test_graph.py +83 -0
  121. mnesos-0.6.0/tests/integration/test_orchestrator.py +295 -0
  122. mnesos-0.6.0/tests/integration/test_stateless_orchestrator.py +223 -0
  123. mnesos-0.6.0/tests/unit/__init__.py +0 -0
  124. mnesos-0.6.0/tests/unit/api/__init__.py +0 -0
  125. mnesos-0.6.0/tests/unit/api/test_cartridges.py +278 -0
  126. mnesos-0.6.0/tests/unit/api/test_deps.py +65 -0
  127. mnesos-0.6.0/tests/unit/api/test_entity_routers.py +360 -0
  128. mnesos-0.6.0/tests/unit/api/test_routes.py +342 -0
  129. mnesos-0.6.0/tests/unit/api/test_turns.py +100 -0
  130. mnesos-0.6.0/tests/unit/graph/__init__.py +0 -0
  131. mnesos-0.6.0/tests/unit/graph/shared.py +95 -0
  132. mnesos-0.6.0/tests/unit/graph/test_factory.py +46 -0
  133. mnesos-0.6.0/tests/unit/graph/test_nodes/__init__.py +0 -0
  134. mnesos-0.6.0/tests/unit/graph/test_nodes/test_director.py +133 -0
  135. mnesos-0.6.0/tests/unit/graph/test_nodes/test_lore.py +131 -0
  136. mnesos-0.6.0/tests/unit/graph/test_nodes/test_narrator.py +183 -0
  137. mnesos-0.6.0/tests/unit/graph/test_nodes/test_system.py +124 -0
  138. mnesos-0.6.0/tests/unit/graph/test_state.py +49 -0
  139. mnesos-0.6.0/tests/unit/graph/test_tools/__init__.py +0 -0
  140. mnesos-0.6.0/tests/unit/graph/test_tools/test_npc.py +392 -0
  141. mnesos-0.6.0/tests/unit/graph/test_tools/test_yare.py +110 -0
  142. mnesos-0.6.0/tests/unit/graph/test_utils/__init__.py +0 -0
  143. mnesos-0.6.0/tests/unit/graph/test_utils/test_time.py +104 -0
  144. mnesos-0.6.0/tests/unit/interpreter/__init__.py +0 -0
  145. mnesos-0.6.0/tests/unit/interpreter/shared.py +6 -0
  146. mnesos-0.6.0/tests/unit/interpreter/test_actions/__init__.py +0 -0
  147. mnesos-0.6.0/tests/unit/interpreter/test_actions/test_core.py +172 -0
  148. mnesos-0.6.0/tests/unit/interpreter/test_core_actions_edge_cases.py +112 -0
  149. mnesos-0.6.0/tests/unit/interpreter/test_evaluator.py +163 -0
  150. mnesos-0.6.0/tests/unit/interpreter/test_evaluator_edge_cases.py +75 -0
  151. mnesos-0.6.0/tests/unit/interpreter/test_interpreter.py +103 -0
  152. mnesos-0.6.0/tests/unit/interpreter/test_store.py +58 -0
  153. mnesos-0.6.0/tests/unit/storage/__init__.py +1 -0
  154. mnesos-0.6.0/tests/unit/storage/test_hydrator.py +232 -0
  155. mnesos-0.6.0/tests/unit/storage/test_storage.py +1056 -0
  156. mnesos-0.6.0/tests/unit/test_cartridge.py +603 -0
  157. mnesos-0.6.0/tests/unit/test_context.py +130 -0
  158. mnesos-0.6.0/tests/unit/test_orchestrator.py +258 -0
  159. mnesos-0.6.0/tests/unit/test_package.py +13 -0
  160. mnesos-0.6.0/web-client/.gitignore +24 -0
  161. mnesos-0.6.0/web-client/README.md +73 -0
  162. mnesos-0.6.0/web-client/eslint.config.js +23 -0
  163. mnesos-0.6.0/web-client/index.html +13 -0
  164. mnesos-0.6.0/web-client/package-lock.json +2983 -0
  165. mnesos-0.6.0/web-client/package.json +30 -0
  166. mnesos-0.6.0/web-client/public/favicon.svg +1 -0
  167. mnesos-0.6.0/web-client/public/icons.svg +24 -0
  168. mnesos-0.6.0/web-client/src/App.css +479 -0
  169. mnesos-0.6.0/web-client/src/App.tsx +170 -0
  170. mnesos-0.6.0/web-client/src/api/client.ts +343 -0
  171. mnesos-0.6.0/web-client/src/components/CartridgeLibrary.tsx +561 -0
  172. mnesos-0.6.0/web-client/src/components/ChatInput.tsx +52 -0
  173. mnesos-0.6.0/web-client/src/components/ChatPane.tsx +55 -0
  174. mnesos-0.6.0/web-client/src/components/GameInstanceManager.tsx +99 -0
  175. mnesos-0.6.0/web-client/src/components/PersonaManager.tsx +276 -0
  176. mnesos-0.6.0/web-client/src/components/SaveManager.tsx +113 -0
  177. mnesos-0.6.0/web-client/src/components/SettingsModal.tsx +92 -0
  178. mnesos-0.6.0/web-client/src/components/StartNewGameModal.tsx +188 -0
  179. mnesos-0.6.0/web-client/src/components/StateDebugger.tsx +78 -0
  180. mnesos-0.6.0/web-client/src/hooks/useGameSession.ts +279 -0
  181. mnesos-0.6.0/web-client/src/index.css +41 -0
  182. mnesos-0.6.0/web-client/src/main.tsx +10 -0
  183. mnesos-0.6.0/web-client/src/types/index.ts +177 -0
  184. mnesos-0.6.0/web-client/tsconfig.app.json +25 -0
  185. mnesos-0.6.0/web-client/tsconfig.json +7 -0
  186. mnesos-0.6.0/web-client/tsconfig.node.json +24 -0
  187. mnesos-0.6.0/web-client/vite.config.ts +15 -0
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: mnesos-cartridge-development
3
+ description: Develop, template, and design new RPG cartridges for the MnesOS engine. Includes boilerplate templates, guidelines, and validation tools.
4
+ license: MIT
5
+ metadata:
6
+ version: "1.0"
7
+ author: mnesos-team
8
+ ---
9
+
10
+ # MnesOS Cartridge Development Skill
11
+
12
+ This skill is designed to help you build standard Role-Playing Game cartridges for the MnesOS engine.
13
+
14
+ ## Overview of Cartridge Structure
15
+
16
+ A MnesOS cartridge divides game responsibilities across explicitly named files:
17
+
18
+ 1. **`bot_lore.md`**: Contains the world setting, factions, locations, character backgrounds, and items.
19
+ 2. **`prompt_directives.yaml`**: Contains instructions for the Director, NPC, and Narrator roles, steering their behavior and tone.
20
+ 3. **`yare.yaml`**: The deterministic logic layer handling character stats (HP, Mana, Gold, Status Effects) and state mutations through defined events (e.g., combat strikes, trading, exploring).
21
+
22
+ ## Best Practices
23
+
24
+ * **Logic goes in YARE**: Do not ask the LLM to calculate health drops or item prices. Make events in `yare.yaml` (e.g. `buy_item`, `take_damage`, `cast_spell`).
25
+ * **Keep Directives Lean**: Directives should focus on psychological behavior ("NPCs run away when health is critically low", or "The Director should spawn encounters in the wilderness").
26
+ * **Use the Macros**: Common mathematical calculations (e.g. combat rolls) should be offloaded to YARE macros for clean event structures.
27
+ * **Dice Notation**: When using `roll(...)` in YARE expressions, do **not** add quotes around the dice notation. Write `@ roll(1d20)` instead of `@ roll('1d20')`. The engine pre-processes this automatically.
28
+ * **Design for `MAX_ITERATIONS = 3`**: The engine allows at most 3 tool calls (YARE events or NPC queries) per turn. Each event should be self-contained — use `call` steps to chain sub-events internally rather than relying on the LLM to issue multiple tool calls. Keep complex flows inside a single event's `steps`.
29
+
30
+ ## Available Resources
31
+
32
+ * **Templates**: Found in `assets/`. You can copy these to begin a new generic RPG game.
33
+ * [assets/bot_lore.md](assets/bot_lore.md)
34
+ * [assets/prompt_directives.yaml](assets/prompt_directives.yaml)
35
+ * [assets/yare.yaml](assets/yare.yaml)
36
+ * **Documentation**:
37
+ * [references/cartridge-guide.md](references/cartridge-guide.md)
38
+ * [references/yare-specification.md](references/yare-specification.md)
39
+ * [references/architecture_analysis.md](references/architecture_analysis.md)
40
+ * [references/combat_mechanics.md](references/combat_mechanics.md)
41
+ * **Scripts**:
42
+ * [scripts/setup_cartridge.py](scripts/setup_cartridge.py): Use this python script to automatically scaffold a new cartridge using the assets in this skill. Usage: `python scripts/setup_cartridge.py <cartridge-name>`
@@ -0,0 +1,25 @@
1
+ # The Kingdom of Eldoria
2
+
3
+ Eldoria is a realm of high fantasy, featuring sprawling forests, towering mountains, and ancient ruins left behind by the precursor civilization known as the Aethel.
4
+
5
+ ## Player's Role
6
+
7
+ The player is a wandering adventurer arriving in the frontier town of Oakhaven. They seek fame, fortune, and the hidden truth behind the rising monster attacks along the borders.
8
+
9
+ ## Factions
10
+
11
+ * **The Silver Vanguard**: The royal knights of Eldoria. They are honorable, steadfast, and follow a strict code of chivalry.
12
+ * **The Shadow Weavers**: A secretive guild of rogues and spies operating out of the major cities. They deal in secrets and contraband but avoid unnecessary violence.
13
+ * **The Arcane Consortium**: Scholars and mages who dedicate their lives to studying the magical leylines that crisscross the land.
14
+
15
+ ## Enemies & Bestiary
16
+
17
+ * **Goblin Scavengers**: Weak, cowardly creatures that attack in packs. They prefer to ambush travelers and steal shiny objects.
18
+ * **Dire Wolves**: Large, vicious predators that roam the deep woods. They are territorial and highly aggressive if provoked.
19
+ * **Stone Golems**: Ancient constructs guarding forgotten Aethel ruins. They are slow but incredibly durable and hit with crushing force.
20
+
21
+ ## Locations
22
+
23
+ * **Oakhaven**: A bustling frontier town on the edge of the Whispering Woods. A safe haven for players to rest, trade, and pick up bounties.
24
+ * **The Whispering Woods**: A dense, old-growth forest populated by magical creatures and dangerous beasts.
25
+ * **Sunken Keep**: A ruined Aethel fortress partially submerged in a bog, rumored to hold great magical artifacts.
@@ -0,0 +1,7 @@
1
+ director: |
2
+ Your role is to act as the overarching game master. Process the player's intentions and trigger the corresponding events securely mapped in yare.yaml, such as `combat_strike`, `cast_spell`, or `rest_at_inn`. Keep the adventure moving forward fairly.
3
+ npc: |
4
+ You determine the behavior of NPCs and monsters in the realm of Eldoria. If an NPC is friendly, offer quests or trade. If it is a monster, act aggressively or defensively based on its lore. Trigger `combat_strike` when fighting the player.
5
+ narrator: |
6
+ You are the storyteller of Eldoria. Describe the environments, the results of the player's actions, and the heat of combat using the deterministic outputs provided in the event notes.
7
+ Your tone should be heroic, descriptive, and adventurous, fitting a classic high fantasy RPG. Do not assume player actions; wait for the player to act. Keep descriptions between 100-200 words.
@@ -0,0 +1,102 @@
1
+ version: "1.0"
2
+
3
+ state_schema:
4
+ player:
5
+ hp: { type: int, default: 100, min: 0, max: 100, visibility: public }
6
+ mana: { type: int, default: 50, min: 0, max: 50, visibility: public }
7
+ gold: { type: int, default: 20, min: 0, visibility: public }
8
+ status: { type: string, default: "healthy", visibility: public }
9
+ npc:
10
+ type: { type: string, default: "villager", visibility: private }
11
+ hp: { type: int, default: 30, min: 0, max: 100, visibility: public }
12
+ attack_power: { type: int, default: 5, min: 0, visibility: private }
13
+
14
+ macros:
15
+ player_attack_roll: "roll(1d20) + 5"
16
+ npc_attack_roll: "roll(1d20) + @state.npc.attack_power"
17
+ damage_roll: "roll(1d8) + 2"
18
+
19
+ events:
20
+ combat_strike:
21
+ inputs: [attacker]
22
+ steps:
23
+ - action: branch
24
+ if: "@ inputs.attacker == 'player'"
25
+ steps:
26
+ - action: set
27
+ var: temp.hit_roll
28
+ value: "@ macros.player_attack_roll"
29
+ - action: branch
30
+ if: "@ temp.hit_roll >= 12"
31
+ steps:
32
+ - action: set
33
+ var: temp.damage
34
+ value: "@ macros.damage_roll"
35
+ - action: mutate
36
+ var: state.npc.hp
37
+ op: sub
38
+ value: "@ temp.damage"
39
+ - action: note
40
+ message: "Player attacks! Roll: {temp.hit_roll}. HIT! Player deals {temp.damage} damage to the NPC. NPC HP is now {state.npc.hp}."
41
+ else:
42
+ - action: note
43
+ message: "Player attacks! Roll: {temp.hit_roll}. MISS! The attack fails to connect."
44
+ else:
45
+ - action: set
46
+ var: temp.hit_roll
47
+ value: "@ macros.npc_attack_roll"
48
+ - action: branch
49
+ if: "@ temp.hit_roll >= 10"
50
+ steps:
51
+ - action: set
52
+ var: temp.damage
53
+ value: "@ roll(1d6) + 1"
54
+ - action: mutate
55
+ var: state.player.hp
56
+ op: sub
57
+ value: "@ temp.damage"
58
+ - action: note
59
+ message: "NPC attacks! Roll: {temp.hit_roll}. HIT! Player takes {temp.damage} damage. Player HP is now {state.player.hp}."
60
+ else:
61
+ - action: note
62
+ message: "NPC attacks! Roll: {temp.hit_roll}. MISS! The player dodges the attack."
63
+
64
+ cast_heal:
65
+ steps:
66
+ - action: branch
67
+ if: "@ state.player.mana >= 10"
68
+ steps:
69
+ - action: mutate
70
+ var: state.player.mana
71
+ op: sub
72
+ value: 10
73
+ - action: mutate
74
+ var: state.player.hp
75
+ op: add
76
+ value: 20
77
+ - action: note
78
+ message: "Player casts Heal! Consumed 10 mana. Restored 20 HP. Player HP: {state.player.hp} | Mana: {state.player.mana}."
79
+ else:
80
+ - action: note
81
+ message: "Player tries to cast Heal but does not have enough mana! Requires 10 mana, has {state.player.mana}."
82
+
83
+ rest_at_inn:
84
+ steps:
85
+ - action: branch
86
+ if: "@ state.player.gold >= 5"
87
+ steps:
88
+ - action: mutate
89
+ var: state.player.gold
90
+ op: sub
91
+ value: 5
92
+ - action: set
93
+ var: state.player.hp
94
+ value: 100
95
+ - action: set
96
+ var: state.player.mana
97
+ value: 50
98
+ - action: note
99
+ message: "Player rents a room for 5 gold. Fully restored HP and Mana. Gold remaining: {state.player.gold}."
100
+ else:
101
+ - action: note
102
+ message: "Player tries to rest at the inn but doesn't have 5 gold."
@@ -0,0 +1,40 @@
1
+ # MnesOS Architectural Analysis: Two-Node Graph
2
+
3
+ This report addresses the architectural analysis of the `YARE interpreter` and the `Agentic Game Graph` found in the MnesOS core engine.
4
+
5
+ ## 1. YARE Interpreter Analysis
6
+
7
+ ### Completeness and Fit for Purpose
8
+ The YAML Agentic Rules Engine (YARE), defined in `src/MnesOS/interpreter.py`, is **not strictly Turing Complete** — and this is an optimal and intentional design choice.
9
+ - **What it lacks**: It does not support unbounded looping (there are no `while` or `for` loops within the DSL) and explicitly limits recursion (nested event calling depth is hard-capped at `max_call_depth = 10`).
10
+ - **What it provides**: It supports robust conditional branching (`if`/`else` condition logic via the `branch` action), sequential execution, arbitrary nested state mutation (`mutate`, `set`), deterministic RNG (`roll`), and nested state reading.
11
+ - **Why it fits**: In the context of an LLM-backed game engine, evaluating deterministic game logic *must always* halt in a predictable timeframe. Allowing Turing-complete boundless loops inside a configuration file would risk the game entering an infinite loop during a tool call, crashing the session entirely. By capping recursion and removing infinite loops, YARE guarantees safe, bounded execution (Total Turing completeness).
12
+
13
+ ### Influence on Narration
14
+ A recurring flaw in many LLM-run games is exposing literal dice rolls and rule variables mid-sentence (the "out-of-context numbers" problem). YARE solves this elegantly by air-gapping the system output from the narrative output:
15
+ - YARE modifies the state backstage and can emit structured logging details via the `note` action (e.g., `"Player rolled 12 on lockpicking, Chest unlocked"`).
16
+ - In `graph.py`, these interpreter logs are collected as `notes` and bundled into `system_notes`, which operates alongside the `agent_messages` history as invisible, LLM-only context.
17
+ - **The actual rendering mechanism**: Because the numbers never surface directly to the client's conversation history (`client_messages`), YARE does not spit out raw logs to the user. Instead, the final log of events (`system_notes`) and the `public_state` are routed to an independent **Narrator** LLM node. The Narrator acts as the "renderer", taking the raw deterministic outcomes and converting them flavorfully into high-quality prose.
18
+
19
+ ## 2. Agentic Graph Analysis
20
+
21
+ The engine leverages a LangGraph state machine (`src/MnesOS/graph.py`) structured across two primary LLM decision nodes: `Director` and `Narrator`. The third logical role, the **NPC Brain**, is implemented as a specialized intent-query tool.
22
+
23
+ ### Fit For Purpose vs. Turn-Based RPGs
24
+ The architecture mimics the phases of a classic Turn-Based RPG: `Player Input -> Mechanics Resolution -> NPC Intent -> Final Resolution -> Render Frame`.
25
+
26
+ MnesOS recreates this systematically:
27
+ 1. **Director Node**: The Orchestrator. It maps player intent, resolves mechanics via tools, and queries NPCs for their reactions mid-turn.
28
+ 2. **NPC Intent Tool (`query_npc_intent`)**: The Actor. It provides autonomous character dialogue and intent when queried by the Director, ensuring persona isolation without the overhead of a separate graph node.
29
+ 3. **Narrator Node**: The Storyteller. It takes the finalized outcomes and renders them into immersive prose.
30
+
31
+ ### Node Count: The Two-Node Sweet Spot
32
+ While previous iterations explored a 3-node graph, the current **two-node architecture with specialized tools** provides the optimal balance of isolation and performance.
33
+
34
+ - **Why separate Director & Narrator?**
35
+ Forcing a single agent to handle both strict mechanics resolution and flavorful prose writing leads to "prompt bleed". The Narrator is air-gapped from the raw system notes to ensure it only describes *outcomes*, not *calculations*.
36
+ - **Why toolize the NPC Brain?**
37
+ A separate graph node for NPCs creates high latency and redundant context passing (sending history to three agents). By making the NPC Brain a tool available to the Director, we preserve character autonomy and profile isolation while allowing the Director to manage the high-level turn flow in a single coordinated loop.
38
+
39
+ ### Conclusion
40
+ The graph structure implements an LLM-adapted **Model-View-Controller (MVC)** framework. The `Director` acts as the **Controller**, the YARE engine as the **Model**, and the `Narrator` as the **View**. This separation of concerns guarantees deterministic gameplay while allowing for highly variable, high-quality narrative output.
@@ -0,0 +1,135 @@
1
+ # Cartridge Guide
2
+
3
+ This guide describes how to build and validate a MnesOS cartridge.
4
+
5
+ ## Cartridge Contract
6
+
7
+ Create a cartridge under `cartridges/<your-game-name>/`.
8
+
9
+ Required files:
10
+
11
+ 1. `yare.yaml`
12
+ 2. `bot_lore.md`
13
+
14
+ Optional file:
15
+
16
+ 3. `prompt_directives.yaml`
17
+
18
+ `yare.yaml` stays procedural. Narrative steering belongs in `prompt_directives.yaml`, not in the rules file.
19
+
20
+ ## What Goes Where
21
+
22
+ ### `bot_lore.md`
23
+
24
+ Use this file for world facts, NPC descriptions, locations, factions, and items.
25
+
26
+ - structure content with markdown headers because the lore store chunks on `#`, `##`, and `###`
27
+ - keep mechanics out of lore text
28
+ - use descriptive section names so retrieval has strong anchors
29
+
30
+ Example:
31
+
32
+ ```markdown
33
+ # Crossroads
34
+ The crossroads is the first safe settlement outside the ruined highway.
35
+
36
+ ## Goblin Scout
37
+ A cautious scavenger who avoids direct combat unless cornered.
38
+ ```
39
+
40
+ ### `yare.yaml`
41
+
42
+ Use this file for deterministic state and event logic.
43
+
44
+ - `state_schema` defines tracked state and defaults
45
+ - `macros` define reusable `@` expressions
46
+ - `events` define executable steps
47
+ - supported actions are `set`, `mutate`, `branch`, `table_roll`, `call`, and `note`
48
+
49
+ Example:
50
+
51
+ ```yaml
52
+ state_schema:
53
+ player:
54
+ hp: { type: int, default: 100, min: 0, max: 100, visibility: public }
55
+ npc:
56
+ hp: { type: int, default: 20, min: 0, visibility: public }
57
+
58
+ events:
59
+ deal_damage:
60
+ steps:
61
+ - action: mutate
62
+ var: state.npc.hp
63
+ op: sub
64
+ value: 10
65
+ - action: note
66
+ message: "Player deals 10 damage."
67
+ ```
68
+
69
+ ### `prompt_directives.yaml`
70
+
71
+ Use this file only for short per-role LLM steering.
72
+
73
+ Allowed keys:
74
+
75
+ - `director`
76
+ - `npc`
77
+ - `narrator`
78
+
79
+ Example:
80
+
81
+ ```yaml
82
+ director: "Prefer explicit mechanical events over narration-only turns."
83
+ npc: "Escalate only when the NPC has a clear advantage."
84
+ narrator: "Keep the prose terse and grounded."
85
+ ```
86
+
87
+ At load time the cartridge loader validates:
88
+
89
+ - only those three keys are allowed
90
+ - each value must be a string
91
+ - each value has a length cap
92
+ - total directive size has a combined cap
93
+ - obvious prompt-injection patterns are rejected
94
+
95
+ If `prompt_directives` is placed inside `yare.yaml`, the loader rejects the cartridge.
96
+
97
+ ## State Visibility
98
+
99
+ Each schema field may define `visibility`.
100
+
101
+ ```yaml
102
+ state_schema:
103
+ player:
104
+ hp: { type: int, default: 100, min: 0, visibility: public }
105
+ mana: { type: int, default: 50, min: 0, visibility: public }
106
+ hidden_flag: { type: bool, default: false, visibility: private }
107
+ ```
108
+
109
+ Behavior:
110
+
111
+ - `public` fields are eligible for narrator context through `get_public_state`
112
+ - omitted `visibility` defaults to `private`
113
+ - direct `@ state...` access to private fields is blocked by the interpreter
114
+
115
+ This keeps hidden mechanics in deterministic state without leaking them into player-facing narration by default.
116
+
117
+ ## Conversion Checklist
118
+
119
+ 1. Extract all stats, resources, and flags into `state_schema`
120
+ 2. Move every deterministic rule into `events`
121
+ 3. Add bounds with `min` and `max` where needed
122
+ 4. Mark player-visible fields with `visibility: public`
123
+ 5. Move flavor text into `bot_lore.md`
124
+ 6. Move tone instructions into `prompt_directives.yaml`
125
+
126
+ ## Things Not To Rely On
127
+
128
+ - do not rely on hardcoded event-name matching in engine code
129
+ - do not rely on cartridge-specific NPC behavior embedded in Python nodes
130
+ - do not put prompt directives inside `yare.yaml`
131
+ - do not assume the engine persists state for you; the client must store and re-supply the returned game state each turn
132
+
133
+ ## Testing
134
+
135
+ Validate the cartridge by loading it through `CartridgeLoader`, then run the graph with the returned `yare_config`, `prompt_directives`, `lore_path`, and initial state.
@@ -0,0 +1,123 @@
1
+ # MnesOS Cartridge Mechanics: Logging and Counter-Play
2
+
3
+ This document 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 (like countering) can be implemented by cartridge developers.
4
+
5
+ ## 1. Ensuring Narrator Semantic Understanding
6
+
7
+ In MnesOS, the **Narrator** LLM needs to understand the numbers generated by YARE without the user seeing the raw math. To ensure the Narrator correctly interprets these numbers, cartridge developers must provide **semantic grounding** within the `note` event actions.
8
+
9
+ MnesOS supports variable interpolation inside `note` strings via the `{variable}` syntax. It is the cartridge developer's responsibility to format these strings as instructions or stage directions for the Narrator.
10
+
11
+ ### Bad Practice (Raw Numbers)
12
+ ```yaml
13
+ - action: mutate
14
+ var: "state.goblin.hp"
15
+ op: "sub"
16
+ value: 10
17
+ - action: note
18
+ message: "Goblin HP: {state.goblin.hp}. Damage: 10."
19
+ # Result: The Narrator LLM might get confused about who dealt what.
20
+ ```
21
+
22
+ ### Good Practice (Semantic Grounding)
23
+ ```yaml
24
+ - action: mutate
25
+ var: "state.goblin.hp"
26
+ op: "sub"
27
+ value: 10
28
+ - action: note
29
+ 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.]"
30
+ # Result: The Narrator understands exactly the mechanical outcome and receives direct staging instructions on how to render it.
31
+ ```
32
+ By convention, enclosing `system_notes` in a tag bracket (like `[SYSTEM LOG: ...]`) ensures the Narrator recognizes it as a backstage mechanic rather than spoken dialogue.
33
+
34
+ ---
35
+
36
+ ## 2. The Operational Domain of the 2-Node Graph
37
+
38
+ The execution graph flows iteratively between the Director and the ToolNode before finally reaching the Narrator:
39
+ `Director (Intent Analysis)` 🔁 `ToolNode (Mechanics & NPC Intent)` ➔ `Narrator (Render)`
40
+
41
+ The system natively supports **Asynchronous Turn-Based Games** (e.g., standard D&D, where actors fully resolve their action on their specific turn).
42
+
43
+ If an action is "immediate" (an attack calculates and subtracts HP instantly), reactive "counters" within the same turn require the Director to query the NPC before finalizing damage. To allow "Counters", cartridge developers must design their YARE events using **Phase-Based Intention** and **Telegraphing**.
44
+
45
+ ---
46
+
47
+ ## 3. Scenario A: Player attacks, NPC Counters (Shield Block)
48
+
49
+ If the player's attack immediately reduces HP during the Director resolution, the NPC has no chance to block. Instead, the Director should query the NPC intent *before* applying final damage.
50
+
51
+ **How it works:**
52
+ 1. Player says *"I attack the Goblin"*.
53
+ 2. Director queries NPC intent using the `query_npc_intent` tool.
54
+ 3. NPC Brain responds: *"I intend to raise my shield!"*
55
+ 4. Director resolves the attack mechanics (YARE), taking the shield block into account, and then summarizes for the Narrator.
56
+
57
+ Alternatively, for complex multi-turn logic:
58
+ 1. Director triggers `plan_player_attack`. This *does not* deal damage. It saves the value to a buffer.
59
+ 2. In the next turn iteration (or turn phase), the NPC intent is processed.
60
+ 3. Mechanics resolution clears the buffer and applies damage.
61
+
62
+ ---
63
+
64
+ ## 4. Scenario B: NPC Telegraphs, Player Counters
65
+
66
+ Because control is handed back to the player *after* the Narrator finishes, 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.
67
+
68
+ **How it works:**
69
+ 1. **Turn N (Mechanics Phase)**: Director triggers `npc_telegraph_attack`.
70
+ 2. **Turn N (Narrator)**: Narrator writes *"The Goblin raises his heavy club, preparing to smash it down!"*
71
+ 3. **Turn N+1 (Player Phase)**: Player says *"I raise my shield to brace."* Director triggers `player_resolve_incoming`.
72
+
73
+ ### Cartridge Example: Player Countering
74
+ ```yaml
75
+ state_schema:
76
+ combat:
77
+ incoming_attack_val: { type: "int", default: 0 }
78
+
79
+ events:
80
+ # Triggered by Director on Turn 1
81
+ npc_telegraph_attack:
82
+ steps:
83
+ - action: call
84
+ event: do_attack_roll
85
+ - action: set
86
+ var: "state.combat.incoming_attack_val"
87
+ value: "@temp.roll_result"
88
+ - action: note
89
+ message: "[SYSTEM LOG: The Goblin is telegraphing a heavy attack of power {temp.roll_result}. Player must react on their next turn.]"
90
+
91
+ # Triggered by Director on Turn 2
92
+ player_resolve_incoming:
93
+ inputs:
94
+ player_action: { type: "string", enum: ["dodge", "block", "ignore"] }
95
+ steps:
96
+ - action: branch
97
+ conditions:
98
+ - if: "@inputs.player_action == 'block'"
99
+ steps:
100
+ - action: mutate
101
+ var: "state.combat.incoming_attack_val"
102
+ op: "sub"
103
+ value: 5 # Shield reduces incoming damage by 5
104
+ - action: note
105
+ message: "[SYSTEM LOG: Player raised shield! Blocked 5 damage.]"
106
+
107
+ # Apply final damage
108
+ - action: branch
109
+ conditions:
110
+ - if: "@state.combat.incoming_attack_val > 0"
111
+ steps:
112
+ - action: mutate
113
+ var: "state.player.hp"
114
+ op: "sub"
115
+ value: "@state.combat.incoming_attack_val"
116
+ - action: note
117
+ message: "[SYSTEM LOG: Player takes {state.combat.incoming_attack_val} damage from the incoming attack!]"
118
+
119
+ # Clear telegraph
120
+ - action: set
121
+ var: "state.combat.incoming_attack_val"
122
+ value: 0
123
+ ```
@@ -0,0 +1,195 @@
1
+ # YARE Specification
2
+
3
+ YARE is the deterministic rules layer used by MnesOS. It is interpreted by `YAREInterpreter` and is intentionally narrower than general Python.
4
+
5
+ ## Top-Level Structure
6
+
7
+ ```yaml
8
+ version: "1.0"
9
+ state_schema:
10
+ domain:
11
+ field: { type: int|float|string|bool|list, default: 0, min: 0, max: 10, visibility: public }
12
+ macros:
13
+ macro_name: "@ expression"
14
+ events:
15
+ event_name:
16
+ inputs: [arg1, arg2]
17
+ steps: []
18
+ ```
19
+
20
+ ## Expressions
21
+
22
+ Expressions are strings starting with `@`.
23
+
24
+ Available roots:
25
+
26
+ - `state.*`
27
+ - `temp.*`
28
+ - `inputs.*`
29
+ - `macros.*`
30
+
31
+ Supported operators in the current interpreter:
32
+
33
+ - arithmetic: `+`, `-`, `*`, `/`, `//`, `%`
34
+ - comparison: `==`, `!=`, `<`, `<=`, `>`, `>=`
35
+ - boolean: `and`, `or`, `not`
36
+ - unary: unary `+` and unary `-`
37
+
38
+ Supported built-ins in the current interpreter:
39
+
40
+ - `roll(NdX)` (Note: Do **not** put quotes around the dice notation. Write `roll(1d20)` instead of `roll('1d20')`)
41
+ - `abs(value)`
42
+ - `timedelta(...)`
43
+ - `time_delta(timestamp_a, timestamp_b)` (returns `timestamp_b - timestamp_a` as a `timedelta`)
44
+
45
+ Not currently supported:
46
+
47
+ - `floor`, `ceil`, `round`
48
+ - `now()`
49
+ - arbitrary Python literals or function calls outside the whitelist
50
+
51
+ ## State Visibility
52
+
53
+ `visibility` is optional in `state_schema` and defaults to `private`.
54
+
55
+ - `public` fields can be exposed to the narrator context through `get_public_state`
56
+ - `private` fields are blocked from direct `@ state...` access inside interpreter expressions
57
+
58
+ ## Step Types
59
+
60
+ ### `set`
61
+
62
+ Assigns a literal or expression result to `state.*` or `temp.*`.
63
+
64
+ ### `mutate`
65
+
66
+ Applies one of `add`, `sub`, `mul`, or `div`, then clamps to schema `min` and `max` when present.
67
+
68
+ ### `branch`
69
+
70
+ Evaluates conditions in order and executes only the first matching branch.
71
+
72
+ Else branches are written as:
73
+
74
+ ```yaml
75
+ - else: true
76
+ steps:
77
+ - action: note
78
+ message: "Fallback branch"
79
+ ```
80
+
81
+ ### `table_roll`
82
+
83
+ Evaluates `roll`, then maps the result through a table using exact values, ranges like `1-5`, or open-ended ranges like `11+`.
84
+
85
+ ### `call`
86
+
87
+ Invokes another event. Calls are allowed, but execution depth is capped at 10.
88
+
89
+ ### `list_push`
90
+
91
+ Appends an item to an array stored at a `state.*` or `temp.*` path. Creates an empty list if the path does not yet exist.
92
+
93
+ ```yaml
94
+ - action: list_push
95
+ var: "state.player.inventory"
96
+ item: "'Health Potion'"
97
+ ```
98
+
99
+ The engine enforces a hard cap of `MAX_CONTAINER_SIZE` (100) items. Attempting to push beyond this limit raises an error.
100
+
101
+ ### `list_remove`
102
+
103
+ Removes an item from a list by **index** or by **value**. Exactly one of `index` or `value` must be provided. If the index is out of range, or the value is not found, the list is left unchanged.
104
+
105
+ ```yaml
106
+ # Remove by zero-based index
107
+ - action: list_remove
108
+ var: "state.player.inventory"
109
+ index: 0
110
+
111
+ # Remove by value
112
+ - action: list_remove
113
+ var: "state.player.inventory"
114
+ value: "'Health Potion'"
115
+ ```
116
+
117
+ ### `dict_set`
118
+
119
+ Sets a key-value pair on a dict stored at a `state.*` or `temp.*` path. Creates an empty dict if the path does not yet exist.
120
+
121
+ ```yaml
122
+ - action: dict_set
123
+ var: "state.world.flags"
124
+ key: "'bridge_repaired'"
125
+ value: true
126
+ ```
127
+
128
+ The engine enforces `MAX_CONTAINER_SIZE` (100) keys per dict and `MAX_DICT_DEPTH` (3) nesting levels. Either limit being exceeded raises an error.
129
+
130
+ ### `dict_delete`
131
+
132
+ Removes a key from a dict. If the key is absent, the action is a no-op.
133
+
134
+ ```yaml
135
+ - action: dict_delete
136
+ var: "state.world.flags"
137
+ key: "'bridge_repaired'"
138
+ ```
139
+
140
+ ### `foreach`
141
+
142
+ Iterates a list and executes nested steps once per item.
143
+
144
+ Example:
145
+
146
+ ```yaml
147
+ - action: foreach
148
+ array: "@ state.player.inventory"
149
+ item: item
150
+ index: idx
151
+ steps:
152
+ - action: note
153
+ message: "Item {inputs.idx}: {inputs.item}"
154
+ ```
155
+
156
+ ### `note`
157
+
158
+ Appends a string to the interpreter note buffer. `{...}` interpolation is supported and each expression inside braces is evaluated as YARE.
159
+
160
+ ## Execution Model
161
+
162
+ 1. The caller provides current state and event inputs
163
+ 2. Steps execute sequentially
164
+ 3. `temp` acts as event-local scratch state
165
+ 4. `mutate` respects schema bounds when defined
166
+ 5. `note` accumulates engine observations for later narration
167
+ 6. Events with `trigger_on: cycle_tick` are automatically executed once per turn cycle before Director logic
168
+
169
+ YARE is deterministic except where the rules explicitly use `roll(...)`.
170
+
171
+ ## LLM Tool Interface
172
+
173
+ YARE events are dynamically exposed to the Director LLM as individual LangChain structured tools.
174
+
175
+ For example, an event named `combat_strike` in `yare.yaml` with inputs for `attacker` and `defender` is automatically surfaced to the LLM as:
176
+
177
+ ```python
178
+ @tool
179
+ def combat_strike(attacker: str, defender: str) -> Command:
180
+ """Trigger the combat_strike event."""
181
+ ```
182
+
183
+ The underlying dynamically generated tool manages accessing the full `GameState` via `InjectedState`, running `YAREInterpreter.run_event(...)`, and returning a `Command` that updates `bot_memory_staging`, `system_notes`, and appends a `ToolMessage` with the event notes. The `post_tools_node` then commits the `bot_memory_staging` update.
184
+
185
+ This architecture removes the need to inject an "Available Events" textual list into the system prompt, as the native tool-calling features of the LLM automatically handle tool capability discovery and parameter validation.
186
+
187
+ New events are simply added in `yare.yaml`. No code changes are required to expose them — the engine dynamically translates them to LangChain tools at load time.
188
+
189
+ ## Time Sync Extensions
190
+
191
+ - `state.game_time` is automatically injected into Director, NPC Brain (when enabled), and Narrator prompt context when present.
192
+ - Narrator uses a structured tool call for end-of-turn engine actions:
193
+ - `end_of_narration(actions=[{"type":"advance_time","duration":"PT15M"}])`
194
+ - `end_of_narration(actions=[{"type":"set_game_time","value":"2026-04-10T10:00:00+00:00"}])`
195
+ - This replaces inline tag parsing and leaves player-facing narration content unchanged.