Development instance — data may be reset at any time

Agent prompts

How to prompt an in-game AI agent. Its system prompt holds who it is and its standing rules — written once, and cached. The messages of each send hold the moment — the player's input, the scene, the dice. `compaction.summarizationPrompt` says what a compaction keeps — and, with topics, what makes a topic relevant. Covers the prompt cache, persona versus situation, game facts and one-reply notes, structured agents, prompting an agent with topics, and what a custom summarization prompt must and must not say.

An agent reads three things the game writes, and each has its place:

| Where | Holds | Changes | | --- | --- | --- | | systemPrompt | Who the agent is, its standing rules, how it writes | Never in play | | The messages of each send | The moment: the player's input, the scene, the facts the game decided | Every send | | compaction.summarizationPrompt | What a compaction keeps — and, with topics, what makes a topic relevant | Never in play |

The system prompt is permanent

The system prompt leads every request, so the provider caches it with the history behind it. Changing it (io.updateAgent) makes the next send re-read the agent's whole history at full price. So write it once, and keep everything that varies out of it: no game state, turn numbers, scores or dates interpolated into it.

  • Persona and rules, not the situation. Who the character is, how they speak and treat people, what they never do ("never speak or act for the player"), how long a reply runs. Not "you are in the kitchen" or "tonight she's upset" — the game moves on and the prompt quietly goes stale. See multi-character-scenes on timeless personas.
  • Rules the agent applies over the whole game belong here too: how it judges an action, the tone of the world, and — with topics — when it should open one.

The moment goes in the messages

Everything that varies is a message. The agent knows only what its history holds, so tell it what the game decided when it matters to the reply:

ts
import type { UserMessage } from '@omorashi/sdk'

// The roll is the game's; the agent narrates it.
const user: UserMessage = { role: 'user', content: `${input}\n\n[System] The lock-pick roll: 14 of 20 — success.` }
const reply = await gm.send(`reply-${turn}`, { messages: [user] })
  • Keep what should be remembered, as above. A note for one reply only goes in a transient send, keeping just the player's message and the reply — see hinting.
  • Exact numbers live in game state. Hit points, inventory counts, the secret — the reducer owns them; say the current value in the message when the reply needs it, rather than trusting the agent to have tracked it.
  • Structured agents keep a fixed schema and get the variable options in the message, enforced with .refine() — see structured-output.

With topics

An agent with topics has its compaction summary divided into named topics; the compactor writes them and decides at each compaction which stay open, and the agent opens closed ones with open_topic. The platform's wording is generic, so the game says what a topic means here — in two places:

  • The system prompt says when the agent should open a topic — "when its subject comes into play: a person who arrives or is talked about, a place the story moves to, a thing that's handled". The agent reads it on every send.
  • The summarization prompt says what makes a topic relevant — per kind of subject, when it becomes relevant and when it stops — which subjects deserve a topic, and how long a topic's summary should run. The compactor reads it right where it decides: tested, a rule there kept clearly more of the right topics open than the same rule in the system prompt. agent-topics has a tested rule for a story RPG, and one for poker.

The summarization prompt

At a compaction the agent is sent its history and then one closing instruction: compaction.summarizationPrompt, followed by the platform's own lines — how to hand the answer in, and with topics, the open topics it must answer for, the relevance rule and the budget. The default asks for about 200 words covering the key facts, decisions and context needed to continue. Write your own to change what is kept or how much — and, with topics, to say what makes a topic relevant:

ts
// What makes a topic relevant in this game — agent-topics has a tested rule.
const RELEVANCE = 'A topic is relevant while the story is still with it: …'

io.createAgent('dm', {
  systemPrompt: DM_PROMPT,
  topics: {},
  compaction: {
    type: 'summarize',
    summarizationPrompt: 'Write a general summary of the messages above, including the previous general summary, in about 300 words: where the story stands, what the player is trying to do, and the promises made either way. What belongs to a topic goes in that topic rather than the general summary.\n\n' + RELEVANCE,
  },
})

It says what to summarize, not how to hand it in:

  • Include the previous summary in what it summarizes ("including the previous general summary"). The earlier summary is replaced by the new one, so what it leaves out is lost.
  • Give a length. The summary is read on every send until the next compaction.
  • Don't describe the mechanics — the compact call, topics, open or closed, the budget. The platform's lines say all that, and a second version would only conflict.
  • With topics, it is the general summary. Don't say the summary is all that survives: topics survive on their own, and what belongs to a topic goes in that topic, not the general summary. (The default with topics says exactly this.) Then add your relevance rule — see above.

See also