Eposyne Director

Writing requests

The format is simple. Getting good decisions out of it is a craft. This page collects the habits that work.

Your engine decides, Director advises

The reply is a set of suggested changes. Director never touches your game. Design the integration so that a discarded reply falls back to scripted behavior and never to a broken state. An empty changes list is a valid answer, and so is a failed call.

Director shapes the reply, and you check again

A model can say anything. Before a reply reaches you, Director runs a fixed set of rules on it. A change is dropped when:

Extra members the model invents are removed, a rationale is removed unless the request asked for one with rationale=true, and a rationale you asked for is cut to 200 characters. These rules are deterministic and do not depend on the model. If your replies come back thinner than you expect, one of these rules is usually the reason. The most common cause is a wording or case mismatch against your allowed_values list, so prefer short lowercase tokens.

Check again in the game

The rules protect the shape of the reply, not your game. Your game must still check a suggestion against its own state before it applies it. The world may have moved on while the call was in flight, and a valid value can still be wrong right now.

Write a good Ask

Choose Variables with care

Pass history yourself

Director keeps no memory of earlier calls between your requests. A decision knows only what is in the request. If the decision depends on what happened before, put it in the request: a few variables such as last_player_action or recent_events, or a short summary in the ask. Keep it brief. Include what changes the answer and leave out the rest.

Keep the mutable set small

Every variable under mutable is a lever you hand to the model. Declare the minimum. If a value should almost never move, leave it out and change it with your own rules.

Enum first

When the game has a fixed set of outcomes, use allowed_values with exact tokens. It is the safest form, because the reply can only be one of your options. Include a middle option such as foreshadow between locked and available. A list with only two extremes gets extreme answers.

free_text for prose

Use "free_text": true for generated lines, descriptions, and option text. Pair every free-text line with a closed action list, so the character can only do what your game implements:

A mutable variable whose name contains line, dialogue, response, speech, or option makes Director add rules for spoken text: the character speaks only words and cannot promise an action that no action variable allows. This helps for dialogue. If a variable such as response_time has one of these words by accident, rename it.

For player-facing generated text, also turn on validation, which corrects a line that slips into promises the game cannot keep.

Ranges for numbers, lengths for text

Use range for a bounded number such as trust or morale. A value outside the range is dropped, and your engine falls back to its own rule. Prefer absolute values with the current value visible over delta variables. If you do want a delta, declare it as its own variable with a small range, for example trust_change from -10 to 10.

Use length to bound a text variable by its number of characters. The two rules are separate: a range on a string variable, or a length on a number, is refused with value_type, so a ticket number you keep as the string "42" gets a length, never a range.

Worked examples

A choice among authored lines

The game has three greetings and wants the right one. Note that the variable is named greeting_choice and not greeting_option, so no dialogue rules are added.

Request
{
  "schema": "eposyne.director.decision-request/v2",
  "ask": "The player returns to the blacksmith after breaking a promise to deliver ore. Which greeting fits the blacksmith's current attitude?",
  "variables": {
    "npc_trust": 20,
    "npc_mood": "irritated",
    "promise_broken": true,
    "days_since_last_visit": 3
  },
  "mutable": [
    {"name": "greeting_choice", "allowed_values": ["warm_welcome", "neutral_nod", "cold_dismissal"]}
  ]
}
Response (example)
{
  "schema": "eposyne.director.decision-response/v2",
  "changes": [
    {"name": "greeting_choice", "value": "cold_dismissal", "rationale": "Trust is low and the player broke a promise."}
  ]
}

A bounded number

The current trust is in variables as a number, and the range keeps the result between 0 and 100.

Request
{
  "schema": "eposyne.director.decision-request/v2",
  "ask": "The player just defended the innkeeper from a drunk patron, but also broke a table. How should the innkeeper's trust in the player change?",
  "variables": {
    "npc_trust": 45,
    "npc_temperament": "grateful but frugal",
    "table_cost": "minor"
  },
  "mutable": [
    {"name": "npc_trust", "range": {"min": 0, "max": 100}}
  ]
}

A generated line with every guardrail

Free text for the words, a closed list for the actions, limits in the variables, and validation on. Expect roughly double the latency of the other examples.

Request
{
  "schema": "eposyne.director.decision-request/v2",
  "ask": "The player asks the shopkeeper Mara for a healing potion but cannot afford it. Write Mara's reply.",
  "variables": {
    "npc_name": "Mara",
    "npc_personality": "kind, practical, protective of her shop",
    "npc_capabilities": "can_speak, can_sell_items, can_offer_discount",
    "npc_limitations": "cannot_give_items_free, cannot_leave_shop, cannot_lend_money",
    "potion_price": 50,
    "player_gold": 12
  },
  "mutable": [
    {"name": "npc_line", "free_text": true},
    {"name": "npc_action", "allowed_values": ["none", "offer_discount"]}
  ],
  "validation": true
}

Checklist

You wantDo this
A choice among authored optionsallowed_values with the exact tokens
A bounded numberrange, and the current value in variables as a number
Bounded textlength, with the bounds in characters
Generated text"free_text": true, on a name like npc_line
Safe NPC speechA free-text line, a closed action list, limits in variables, and "validation": true
An engine function the model may callDeclare it under operations; read calls in the reply
"Leave it unchanged"Nothing to declare. The model leaves the variable out.
Context from earlierPut it in the request. Director does not remember.