Skip to main content
summer-router is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. AI characters for games on Summer Games: Summer Router replies, tools, speech, images.

When your agent uses it

  • A talking NPC (shopkeeper, guard, companion) players chat with, in a single-player or multiplayer game on Summer Games
  • A game should speak a line in a character’s voice, or make an image, while people play
  • About to put a model name, a provider SDK or a provider key into a game project
  • Choosing between Summer.client.router and Summer.authority.router in a single-player or multiplayer game
  • Handling Summer Router refusals (limit_reached, credits_required, router_disabled, not_on_summer_games, unavailable) or changing its caps

Install

summer setup installs every skill for your agent. To add only this one:
Swap claude-code for your agent, such as codex or cursor. See Skills for every option.

The skill

A shopkeeper who haggles, a guard who opens the gate when convinced, a narrator who says the player’s name. With Summer Router, a game calls AI while people play. The game names a character and sends what the player said. Summer holds the instructions, the model, the voice, the caps and the provider keys, and meters each call from the creator’s paid Summer credits. Players never pay, and nothing secret is in the build.

What works today (early preview)

In the editor and in Local Play, every call answers unavailable, so the game uses its offline lines. Test the character with the test conversation (step 2) and play the real thing on Summer Games.

1. Plan: who calls

Ask the person one question if it is not clear: is the game single-player or multiplayer? In a multiplayer game, the server owns the world. Anything a character does that changes the game (sell an item, give gold, open a shared door) runs on the server. A player’s game may also talk to a character itself and tell the server what happened; then the server checks the exchange with verify_exchange before it acts (step 4).

2. Make the character

A character lives on Summer, not in the build. Make it in Summer Studio (the game’s Grow > Summer Router tab), or with the Summer MCP:
  • Over MCP, call it with summer_call({ op: "router.save_character", input: {...} }), or from a shell: summer router save-character --input '<json>'. Find the game id with games.list. On a game that is live, the person approves the change with one link.
  • Test it before you write game code: router.test_character({ gameId, characterId: "shopkeeper", messages: ["Do you have anything for my wounds?", "Fine, 25 gold for the health potion."], toolOutputs: { sell_item: { ok: true } }, expectTools: ["sell_item"] }). Test turns count toward a developer cap of their own.
  • router.models lists the text models, voices and the full character schema (include: ["schema"]). Pick model and voice.id from it, or leave them out for the game’s defaults.
  • The tool name in the character is the name your game handles. Tool arguments come from the model: check every one in game code.
The character, field by field:

3. Single-player: call from the player’s game

  • tools maps each tool name to a Callable. The SDK runs the handler, sends what it returns back to the character, and the character goes on, up to 8 rounds in one call. Return {"error": "..."} when the action fails (not enough gold, out of stock); the character then says so.
  • Shop.sell checks the price against your own price list, the stock and the gold. The model’s word is never the price.
  • conversation_id: Summer keeps the history. Send the one the last result named. Without it, each call starts a new conversation.
  • For a whole conversation in one node, add a SummerRouterCharacter, set Character ID, turn on Show UI, and give it tools. It shows a bar with the character’s words and a text field.
Speech and images from the player’s game:
Voice only when the character has one (the has_voice of each entry in status().characters), and consider voicing on tap: speech costs more than text. await Summer.client.router.status() asks Summer what this player can use now: available (and reason, an error tag, when it is not), characters (each id, name, has_voice, client_tools), kinds (the capabilities Summer serves) and voice (realtime voice, false during the early preview). report(exchange_id, reason) answers report_id and status; it takes one of this player’s replies from the last 30 days.

4. Multiplayer: call from the server

The player’s game sends what the player typed to the server as a Command (the multiplayer-state skill sets up Commands and shared State). The server answers the Command at once, asks the character for that player’s session, runs the tool calls itself, and publishes the line in shared State so every player sees it.
  • The server has reply(session, character, text, options), speak(session, text, {"character": id}) and generate(session, "image", {...}). Options are the client’s without tools: the server reads r.tool_calls and answers them with tool_results on the next call, with the same conversation_id.
  • speak and generate on the server return data and mime_type. Audio and images are too big for shared State: to voice a line, each player’s game calls Summer.client.router.speak(line, {"character": "shopkeeper"}) for the line it shows (it counts toward that player’s caps).
  • Server calls count toward the game’s caps, not the player’s. Keep your own pace per player on the server: one line in flight per player, a short gap between lines, and a length cap on the text, so one player cannot spend the whole game’s daily cap.
  • Shop.sell_to on the server is the only place an item or gold changes hands. Never apply an effect a client reports.
A client talked, the server acts. When the player’s game called Summer.client.router itself and tells the server “the shopkeeper agreed to sell”, the server checks the exchange first:

5. Every refusal keeps the game running

Every call returns a SummerRouterResult. Read ok first. On a refusal, error is a stable tag, fix says what to do (for you), and message is a plain line you may show players. Show message or your own offline line, and go on. Treat a tag you do not know as unavailable. retryable is true when the same call can work later (unavailable, limit_reached, provider_unavailable, rate_limited). Never retry in a loop: a cap is the product working.

6. Caps and money

Every game starts on, with caps on from day one:
  • The creator changes them per game in Grow > Summer Router, or an agent asks with router.set_settings (the person approves with one link). router.set_account_limit changes the account cap. router.usage shows calls, units, how much of each cap is used and which calls a cap stopped.
  • A character can carry its own daily caps per player and per game (limits in the character).
  • Calls are paid from the creator’s paid Summer credits (plan credits and top-ups), the same balance as the Summer API. A result never carries a price.

7. Ship it safely

  • No provider key, provider SDK, model id or character instructions in the project. The build holds character ids only.
  • Every character keeps an offline line set that is good on its own. Most refusals happen in normal play.
  • Label AI lines as AI in the game (a small tag on the bubble), and offer a report button: Summer.client.router.report(exchange_id, "inappropriate").
  • Never ask players for personal details, and keep characters off romance, self-harm and medical advice. Write it into the instructions.
  • In a multiplayer game, a reply never changes shared state on a client. The server applies every effect and checks every tool argument.
  • Summer.client.router.is_available() says, without a network call, whether this run can call Summer Router. status() asks Summer which characters and kinds it serves for this player.

Verify

  • grep the project for provider names, model ids and anything key-shaped: none.
  • In the editor every character answers with its offline line, and no error is shown.
  • router.test_character with expectTools passes for each tool the game handles.
  • Every tool handler refuses bad arguments (an unknown item, a wrong price, not enough gold).
  • In multiplayer, the server is the only code that sells, gives or opens anything, and it checks a relayed exchange with verify_exchange.
  • On Summer Games, signed in, the character answers, its tool works, and router.usage shows the calls.

Common mistakes

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.
  • design-npc: Design enemy, NPC, boss or companion behavior: perception, state machine, telegraphs, script stub.
  • multiplayer-state: Authority-owned state in Summer multiplayer: shared and private State groups, Commands, Events.
  • summer-api: Summer API game assets: models, estimate, one job or a batch, download the files.

Source: library/skills/summer-router/SKILL.md, rendered from summer-engine@4.8.0 on npm.