YARE Specification¶
YARE stands for YARE Agentic Rules Engine. YARE is the deterministic rules layer used by MnesOS. It is interpreted by YAREInterpreter and is intentionally narrower than general Python.
Top-Level Structure¶
version: "1.0"
state_schema:
domain:
field: { type: int|float|string|bool|list, default: 0, min: 0, max: 10, visibility: public }
# NOTE: It is strongly recommended to use flat snake_case variables (e.g. `domain_field_name`)
# rather than nesting dictionaries deeply, to guarantee deterministic updates and state overrides in the engine.
macros:
macro_name: "@ expression"
events:
event_name:
description: "Optional description for LLM tool discovery"
inputs:
arg1: { type: string, description: "...", default: "...", enum: ["a", "b"] }
arg2: string # Shorthand for { type: string }
steps: []
Note: inputs can still be a simple list of strings [arg1, arg2] for legacy or simple events.
Expressions¶
Expressions are strings starting with @.
Available roots:
state.*temp.*inputs.*macros.*
Supported operators in the current interpreter:
- arithmetic:
+,-,*,/,//,%(Note:+supports string concatenation even if one operand is a number, e.g.'npc_' + 1->'npc_1') - comparison:
==,!=,<,<=,>,>=,in,not in - boolean:
and,or,not - unary: unary
+and unary-
Supported structures:
- Dictionary literals:
@ {'key': 'value', 'num': 42} - List literals:
@ ['item1', 'item2'] - Attribute access:
@ dict_var.key(natively evaluates dictionary key access) - Bracket subscription:
@ list_var[inputs.index]or@ dict_var[inputs.key] - Ternary Operator:
@ 'yes' if state.flag else 'no'
Supported built-ins in the current interpreter:
roll(NdX)(Note: Do not put quotes around the dice notation. Writeroll(1d20)instead ofroll('1d20'))abs(value)timedelta(...)time_delta(timestamp_a, timestamp_b)(returnstimestamp_b - timestamp_aas atimedelta)
Not currently supported:
floor,ceil,roundnow()(usestate.game_timeif available)- arbitrary Python literals or function calls outside the whitelist
State Visibility¶
visibility is optional in state_schema and defaults to private.
publicfields can be exposed to the narrator context throughget_public_stateprivatefields (the default) are excluded from the narrator context, but remain fully accessible to the YARE interpreter for logic and mutations.
State Paths¶
State paths (e.g. state.Player.HP) are case-insensitive. The interpreter will resolve them to the existing key in the state dictionary regardless of casing.
Step Types¶
set¶
Assigns a literal or expression result to state.* or temp.*.
mutate¶
Applies one of add, sub, mul, or div, then clamps to schema min and max when present.
branch¶
Evaluates conditions in order and executes only the first matching branch.
Else branches are written as:
table_roll¶
Evaluates roll, then maps the result through a table using exact values, ranges like 1-5, or open-ended ranges like 11+.
call¶
Invokes another event. Calls are allowed, but execution depth is capped at 10.
list_push¶
Appends an item to an array stored at a state.* or temp.* path. Creates an empty list if the path does not yet exist.
- action: list_push
var: "state.player.inventory"
item: "'Health Potion'" # can also use 'value' as an alias for 'item'
The engine enforces a hard cap of MAX_CONTAINER_SIZE (100) items. Attempting to push beyond this limit raises an error.
list_remove¶
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.
# Remove by zero-based index
- action: list_remove
var: "state.player.inventory"
index: 0
# Remove by value
- action: list_remove
var: "state.player.inventory"
value: "'Health Potion'"
dict_set¶
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.
The engine enforces MAX_CONTAINER_SIZE (100) keys per dict and MAX_DICT_DEPTH (3) nesting levels. Either limit being exceeded raises an error.
dict_delete¶
Removes a key from a dict. If the key is absent, the action is a no-op.
foreach¶
Iterates a list and executes nested steps once per item.
Example:
- action: foreach
array: "@ state.player.inventory" # can also be a direct path without @: "state.player.inventory"
item: item
index: idx
steps:
- action: note
message: "Item {inputs.idx}: {inputs.item}"
note¶
Appends a string to the interpreter note buffer. {...} interpolation is supported and each expression inside braces is evaluated as YARE.
Execution Model¶
- The caller provides current state and event inputs
- Steps execute sequentially
tempacts as event-local scratch statemutaterespects schema bounds when definednoteaccumulates engine observations for later narration- Events with
trigger_on: cycle_tickare automatically executed once per turn cycle before Director logic
YARE is deterministic except where the rules explicitly use roll(...).
LLM Tool Interface¶
YARE events are dynamically exposed to the Director LLM as individual LangChain structured tools.
For example, an event named combat_strike in yare.yaml with inputs for attacker and defender is automatically surfaced to the LLM as:
@tool
def combat_strike(attacker: str, defender: str) -> Command:
"""Trigger the combat_strike event."""
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.
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.
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.
Time Sync Extensions¶
state.game_timeis automatically injected into Director, NPC Intent tool, and Narrator prompt context when present.- Narrator uses a structured tool call for end-of-turn engine actions:
end_of_narration(actions=[{"type":"advance_time","duration":"PT15M"}])end_of_narration(actions=[{"type":"set_game_time","value":"2026-04-10T10:00:00+00:00"}])- This replaces inline tag parsing and leaves player-facing narration content unchanged.