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.
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/SKILL.md +42 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/bot_lore.md +25 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/prompt_directives.yaml +7 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/assets/yare.yaml +102 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/architecture_analysis.md +40 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/cartridge-guide.md +135 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/combat_mechanics.md +123 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/references/yare-specification.md +195 -0
- mnesos-0.6.0/.agents/skills/mnesos-cartridge-development/scripts/setup_cartridge.py +39 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/SKILL.md +60 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/architecture.md +87 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/architecture_analysis.md +40 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/cartridge-guide.md +135 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/combat_mechanics.md +123 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/references/yare-specification.md +195 -0
- mnesos-0.6.0/.agents/skills/mnesos-rpg-engine/scripts/create_cartridge.py +32 -0
- mnesos-0.6.0/.github/workflows/ci-dev.yml +106 -0
- mnesos-0.6.0/.github/workflows/ci-main.yml +30 -0
- mnesos-0.6.0/.github/workflows/docs.yml +60 -0
- mnesos-0.6.0/.github/workflows/release.yml +124 -0
- mnesos-0.6.0/.gitignore +222 -0
- mnesos-0.6.0/PKG-INFO +108 -0
- mnesos-0.6.0/README.md +71 -0
- mnesos-0.6.0/artifacts/.gitkeep +0 -0
- mnesos-0.6.0/cartridges/dark-fantasy/bot_lore.md +9 -0
- mnesos-0.6.0/cartridges/dark-fantasy/play-test.py +67 -0
- mnesos-0.6.0/cartridges/dark-fantasy/prompt_directives.yaml +6 -0
- mnesos-0.6.0/cartridges/dark-fantasy/yare.yaml +62 -0
- mnesos-0.6.0/cartridges/generic-rpg/bot_lore.md +20 -0
- mnesos-0.6.0/cartridges/generic-rpg/prompt_directives.yaml +27 -0
- mnesos-0.6.0/cartridges/generic-rpg/tests/test_run.py +37 -0
- mnesos-0.6.0/cartridges/generic-rpg/yare.yaml +117 -0
- mnesos-0.6.0/configs/.gitkeep +0 -0
- mnesos-0.6.0/docs/api.md +3 -0
- mnesos-0.6.0/docs/cartridge-guide.md +135 -0
- mnesos-0.6.0/docs/design/0001-graph-architecture.md +40 -0
- mnesos-0.6.0/docs/design/0002-state-and-turn-flow.md +87 -0
- mnesos-0.6.0/docs/design/0003-npc-interaction-model.md +59 -0
- mnesos-0.6.0/docs/design/0004-shift-gears-to-stateless.md +75 -0
- mnesos-0.6.0/docs/design/0005-interfaces-and-contracts.md +261 -0
- mnesos-0.6.0/docs/guides/advanced-combat-mechanics.md +123 -0
- mnesos-0.6.0/docs/img/library_view.png +0 -0
- mnesos-0.6.0/docs/img/new_persona_modal.png +0 -0
- mnesos-0.6.0/docs/img/personas_view.png +0 -0
- mnesos-0.6.0/docs/img/play_view.png +0 -0
- mnesos-0.6.0/docs/img/play_view_debug_open.png +0 -0
- mnesos-0.6.0/docs/img/settings_modal.png +0 -0
- mnesos-0.6.0/docs/img/start_new_game_modal.png +0 -0
- mnesos-0.6.0/docs/index.md +13 -0
- mnesos-0.6.0/docs/play-test-win.md +244 -0
- mnesos-0.6.0/docs/play-test.md +503 -0
- mnesos-0.6.0/docs/yare-specification.md +195 -0
- mnesos-0.6.0/mkdocs.yml +63 -0
- mnesos-0.6.0/notebooks/.gitkeep +0 -0
- mnesos-0.6.0/notebooks/graph_showcase.ipynb +471 -0
- mnesos-0.6.0/notebooks/orchestrator_showcase.ipynb +187 -0
- mnesos-0.6.0/notebooks/test_notebook.ipynb +44 -0
- mnesos-0.6.0/pyproject.toml +75 -0
- mnesos-0.6.0/scripts/delete_db.py +29 -0
- mnesos-0.6.0/scripts/migrate_db.py +32 -0
- mnesos-0.6.0/scripts/setup_github_rules.py +312 -0
- mnesos-0.6.0/scripts/strip_notebook_outputs.py +98 -0
- mnesos-0.6.0/setup.cfg +4 -0
- mnesos-0.6.0/src/MnesOS/__init__.py +29 -0
- mnesos-0.6.0/src/MnesOS/_version.py +24 -0
- mnesos-0.6.0/src/MnesOS/api/__init__.py +6 -0
- mnesos-0.6.0/src/MnesOS/api/app.py +36 -0
- mnesos-0.6.0/src/MnesOS/api/cartridges.py +430 -0
- mnesos-0.6.0/src/MnesOS/api/deps.py +150 -0
- mnesos-0.6.0/src/MnesOS/api/instances.py +165 -0
- mnesos-0.6.0/src/MnesOS/api/personas.py +159 -0
- mnesos-0.6.0/src/MnesOS/api/routes.py +238 -0
- mnesos-0.6.0/src/MnesOS/api/saves.py +101 -0
- mnesos-0.6.0/src/MnesOS/api/schemas.py +255 -0
- mnesos-0.6.0/src/MnesOS/api/turns.py +79 -0
- mnesos-0.6.0/src/MnesOS/api/users.py +109 -0
- mnesos-0.6.0/src/MnesOS/cartridge.py +563 -0
- mnesos-0.6.0/src/MnesOS/context.py +78 -0
- mnesos-0.6.0/src/MnesOS/graph/__init__.py +32 -0
- mnesos-0.6.0/src/MnesOS/graph/factory.py +91 -0
- mnesos-0.6.0/src/MnesOS/graph/nodes/__init__.py +0 -0
- mnesos-0.6.0/src/MnesOS/graph/nodes/director.py +59 -0
- mnesos-0.6.0/src/MnesOS/graph/nodes/lore.py +50 -0
- mnesos-0.6.0/src/MnesOS/graph/nodes/narrator.py +58 -0
- mnesos-0.6.0/src/MnesOS/graph/nodes/system.py +84 -0
- mnesos-0.6.0/src/MnesOS/graph/state.py +68 -0
- mnesos-0.6.0/src/MnesOS/graph/tools/__init__.py +0 -0
- mnesos-0.6.0/src/MnesOS/graph/tools/npc.py +128 -0
- mnesos-0.6.0/src/MnesOS/graph/tools/time.py +16 -0
- mnesos-0.6.0/src/MnesOS/graph/tools/yare.py +88 -0
- mnesos-0.6.0/src/MnesOS/graph/utils/__init__.py +0 -0
- mnesos-0.6.0/src/MnesOS/graph/utils/messages.py +14 -0
- mnesos-0.6.0/src/MnesOS/graph/utils/persona.py +20 -0
- mnesos-0.6.0/src/MnesOS/graph/utils/time.py +62 -0
- mnesos-0.6.0/src/MnesOS/interpreter/__init__.py +102 -0
- mnesos-0.6.0/src/MnesOS/interpreter/actions/__init__.py +0 -0
- mnesos-0.6.0/src/MnesOS/interpreter/actions/core.py +167 -0
- mnesos-0.6.0/src/MnesOS/interpreter/evaluator.py +150 -0
- mnesos-0.6.0/src/MnesOS/interpreter/store.py +93 -0
- mnesos-0.6.0/src/MnesOS/orchestrator.py +349 -0
- mnesos-0.6.0/src/MnesOS/prompts.py +148 -0
- mnesos-0.6.0/src/MnesOS/storage/__init__.py +41 -0
- mnesos-0.6.0/src/MnesOS/storage/hydrator.py +116 -0
- mnesos-0.6.0/src/MnesOS/storage/interface.py +234 -0
- mnesos-0.6.0/src/MnesOS/storage/models.py +201 -0
- mnesos-0.6.0/src/MnesOS/storage/sqlite3_store.py +979 -0
- mnesos-0.6.0/src/MnesOS/utils/__init__.py +3 -0
- mnesos-0.6.0/src/MnesOS/utils/create_creator.py +127 -0
- mnesos-0.6.0/src/MnesOS/utils/ingest_cartridges.py +259 -0
- mnesos-0.6.0/src/MnesOS/utils/start_game.py +135 -0
- mnesos-0.6.0/src/MnesOS.egg-info/PKG-INFO +108 -0
- mnesos-0.6.0/src/MnesOS.egg-info/SOURCES.txt +185 -0
- mnesos-0.6.0/src/MnesOS.egg-info/dependency_links.txt +1 -0
- mnesos-0.6.0/src/MnesOS.egg-info/entry_points.txt +2 -0
- mnesos-0.6.0/src/MnesOS.egg-info/requires.txt +25 -0
- mnesos-0.6.0/src/MnesOS.egg-info/top_level.txt +1 -0
- mnesos-0.6.0/tests/__init__.py +1 -0
- mnesos-0.6.0/tests/conftest.py +94 -0
- mnesos-0.6.0/tests/integration/__init__.py +0 -0
- mnesos-0.6.0/tests/integration/test_graph.py +83 -0
- mnesos-0.6.0/tests/integration/test_orchestrator.py +295 -0
- mnesos-0.6.0/tests/integration/test_stateless_orchestrator.py +223 -0
- mnesos-0.6.0/tests/unit/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/api/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/api/test_cartridges.py +278 -0
- mnesos-0.6.0/tests/unit/api/test_deps.py +65 -0
- mnesos-0.6.0/tests/unit/api/test_entity_routers.py +360 -0
- mnesos-0.6.0/tests/unit/api/test_routes.py +342 -0
- mnesos-0.6.0/tests/unit/api/test_turns.py +100 -0
- mnesos-0.6.0/tests/unit/graph/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/graph/shared.py +95 -0
- mnesos-0.6.0/tests/unit/graph/test_factory.py +46 -0
- mnesos-0.6.0/tests/unit/graph/test_nodes/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/graph/test_nodes/test_director.py +133 -0
- mnesos-0.6.0/tests/unit/graph/test_nodes/test_lore.py +131 -0
- mnesos-0.6.0/tests/unit/graph/test_nodes/test_narrator.py +183 -0
- mnesos-0.6.0/tests/unit/graph/test_nodes/test_system.py +124 -0
- mnesos-0.6.0/tests/unit/graph/test_state.py +49 -0
- mnesos-0.6.0/tests/unit/graph/test_tools/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/graph/test_tools/test_npc.py +392 -0
- mnesos-0.6.0/tests/unit/graph/test_tools/test_yare.py +110 -0
- mnesos-0.6.0/tests/unit/graph/test_utils/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/graph/test_utils/test_time.py +104 -0
- mnesos-0.6.0/tests/unit/interpreter/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/interpreter/shared.py +6 -0
- mnesos-0.6.0/tests/unit/interpreter/test_actions/__init__.py +0 -0
- mnesos-0.6.0/tests/unit/interpreter/test_actions/test_core.py +172 -0
- mnesos-0.6.0/tests/unit/interpreter/test_core_actions_edge_cases.py +112 -0
- mnesos-0.6.0/tests/unit/interpreter/test_evaluator.py +163 -0
- mnesos-0.6.0/tests/unit/interpreter/test_evaluator_edge_cases.py +75 -0
- mnesos-0.6.0/tests/unit/interpreter/test_interpreter.py +103 -0
- mnesos-0.6.0/tests/unit/interpreter/test_store.py +58 -0
- mnesos-0.6.0/tests/unit/storage/__init__.py +1 -0
- mnesos-0.6.0/tests/unit/storage/test_hydrator.py +232 -0
- mnesos-0.6.0/tests/unit/storage/test_storage.py +1056 -0
- mnesos-0.6.0/tests/unit/test_cartridge.py +603 -0
- mnesos-0.6.0/tests/unit/test_context.py +130 -0
- mnesos-0.6.0/tests/unit/test_orchestrator.py +258 -0
- mnesos-0.6.0/tests/unit/test_package.py +13 -0
- mnesos-0.6.0/web-client/.gitignore +24 -0
- mnesos-0.6.0/web-client/README.md +73 -0
- mnesos-0.6.0/web-client/eslint.config.js +23 -0
- mnesos-0.6.0/web-client/index.html +13 -0
- mnesos-0.6.0/web-client/package-lock.json +2983 -0
- mnesos-0.6.0/web-client/package.json +30 -0
- mnesos-0.6.0/web-client/public/favicon.svg +1 -0
- mnesos-0.6.0/web-client/public/icons.svg +24 -0
- mnesos-0.6.0/web-client/src/App.css +479 -0
- mnesos-0.6.0/web-client/src/App.tsx +170 -0
- mnesos-0.6.0/web-client/src/api/client.ts +343 -0
- mnesos-0.6.0/web-client/src/components/CartridgeLibrary.tsx +561 -0
- mnesos-0.6.0/web-client/src/components/ChatInput.tsx +52 -0
- mnesos-0.6.0/web-client/src/components/ChatPane.tsx +55 -0
- mnesos-0.6.0/web-client/src/components/GameInstanceManager.tsx +99 -0
- mnesos-0.6.0/web-client/src/components/PersonaManager.tsx +276 -0
- mnesos-0.6.0/web-client/src/components/SaveManager.tsx +113 -0
- mnesos-0.6.0/web-client/src/components/SettingsModal.tsx +92 -0
- mnesos-0.6.0/web-client/src/components/StartNewGameModal.tsx +188 -0
- mnesos-0.6.0/web-client/src/components/StateDebugger.tsx +78 -0
- mnesos-0.6.0/web-client/src/hooks/useGameSession.ts +279 -0
- mnesos-0.6.0/web-client/src/index.css +41 -0
- mnesos-0.6.0/web-client/src/main.tsx +10 -0
- mnesos-0.6.0/web-client/src/types/index.ts +177 -0
- mnesos-0.6.0/web-client/tsconfig.app.json +25 -0
- mnesos-0.6.0/web-client/tsconfig.json +7 -0
- mnesos-0.6.0/web-client/tsconfig.node.json +24 -0
- 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.
|