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:
- it names a variable you did not declare under
mutable, - its value is not in the
allowed_valueslist you gave (case and type matter:Holddoes not matchhold, and"1"does not match1), - its number is outside the
range, or its text is outside thelengthbounds, - it has no value, or it repeats a variable that already has a change.
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.
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
- One decision per request. "Should the guards pursue or hold?" is good. A request that asks for three unrelated things splits the model's attention. Send three requests.
- Name the tension. "Pursue the thief or hold position to protect the dignitary" gives the model something to weigh.
- Describe the choice, not the plumbing. The model already sees your variable names. Do not list them in the Ask.
Choose Variables with care
- Send only what bears on this decision. Ten focused variables beat sixty ambient ones, for quality and for cost. Your prompt is also capped at 30,000 input tokens.
- Name variables for their meaning.
guard_moralebeatsgm_v2. The model reads the names. - Values are JSON scalars and keep their type: a number stays a number,
truestays a boolean. Use units and words in strings where they help:"50m","low". Give a mutable variable the type its rule expects, a number for arangeand a string forallowed_valuesof strings; a mismatch is refused withvalue_type. - Put the current value of every mutable variable in
variablestoo. The model adjusts from real state and does not invent an absolute. - For NPC speech, pass what the character can and cannot do, for example
npc_limitations. It keeps dialogue grounded. - Do not send personal data about your players.
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.
- Every mutable variable needs exactly one rule:
allowed_values,free_text,range, orlength. An entry with two rules, none, an empty list, or a broken range is refused with 422 before any model runs. - The model is told to leave unchanged variables out. A reply that changes one of your three variables is working as designed.
- Treat the mutable list as your renderable vocabulary: the animations you have, the moods you implemented, the actions your game supports. The model can only choose among things you can show.
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:
npc_linewithfree_textfor the words,npc_actionwithallowed_values["none", "offer_discount"]for the deeds.
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.
{
"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"]}
]
}{
"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.
{
"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.
{
"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 want | Do this |
|---|---|
| A choice among authored options | allowed_values with the exact tokens |
| A bounded number | range, and the current value in variables as a number |
| Bounded text | length, with the bounds in characters |
| Generated text | "free_text": true, on a name like npc_line |
| Safe NPC speech | A free-text line, a closed action list, limits in variables, and "validation": true |
| An engine function the model may call | Declare it under operations; read calls in the reply |
| "Leave it unchanged" | Nothing to declare. The model leaves the variable out. |
| Context from earlier | Put it in the request. Director does not remember. |