Town of agents: 50 characters, one burst

Date: 2026-09-19  ·  Code: town/town.mjs  ·  Page: http://localhost:3002 when the server runs  ·  Model: typesafe-ai/jev through the Vercel AI Gateway

1. What Jev decides

For each of 50 characters, what it does when an event is announced: ignore, investigate, join, flee, or warn. A second question scores urgency on five levels to set walking speed. All 50 calls fire at once with Promise.all. The state per character is its name, role, traits, energy, location, the event text, and the event's location. The characters and ten places were written once by anthropic/claude-sonnet-5 into town.json and are committed, so every run sees the same town.

2. The questions

The questions and the generation prompt, from the top of town.mjs. This is the block a reviewer must read; the rest of the file is plumbing.

// ---- Review these. Everything else is plumbing. ----
const QUESTIONS = {
  action: {
    type: "choice",
    instructions: "The character has just heard the event. What does this specific character, with these traits and this role, do next?",
    criteria: {
      ignore: "Carries on with the current task; the event does not concern this character",
      investigate: "Goes to see what is happening",
      join: "Takes part in it, or helps the people involved",
      flee: "Gets away from it to somewhere safe",
      warn: "Goes to tell other people about it",
    },
  },
  urgency: {
    type: "score",
    instructions: "How urgently does the character act on the event?",
    criteria: ["none: no change of pace", "low: when convenient", "medium: soon", "high: right away", "extreme: drops everything and runs"],
  },
};
const GEN_MODEL = "anthropic/claude-sonnet-5";
const GEN_PROMPT = `Invent a small medieval town for a simulation. Output only JSON, no prose, no code fence, with this shape:
{"places":[{"name":"bakery","x":20,"y":30}, ...], "characters":[{"name":"Mara","role":"baker","traits":"cautious, generous","place":"bakery","energy":0.8}, ...]}
Rules: 10 places (include bakery, mill, gate, well, market, tavern, forge, farm, chapel, river) with x and y from 5 to 95.
Exactly 50 characters with distinct names and varied roles (include exactly one baker, one miller, one gatekeeper, one priest, one blacksmith, a few children, a few elders, farmers, traders, guards, a thief, a healer).
Traits: two or three adjectives that differ a lot between characters (brave, cowardly, nosy, lazy, greedy, kind, suspicious, ...). place: one of the 10 place names. energy: 0.1 to 1.0.`;

3. What the page shows

A top-down map with the ten places as dark circles and each character as a coloured dot with its name. You type an event and pick a place. The dots go grey while the burst runs, then take the colour of their action: teal investigate, orange join, red flee, yellow warn, grey ignore. Each dot carries a small bar for the probability of its action. Dots walk toward or away from the event at a speed set by urgency. The side panel shows answered/total, burst wall time, decisions per second, median and max per call, cost, the action counts, and a list of every character sorted by urgency.

4. Run it

node --env-file=.env town/town.mjs            # http://localhost:3002
node --env-file=.env town/town.mjs --batch 10 # 10 calls per batch instead of one burst of 50
node --env-file=.env town/town.mjs --check
node --env-file=.env town/town.mjs --gen      # rewrite town.json (not needed; it is committed)

5. The check, verbatim

"Free bread at the bakery": 50/50 ok, burst 6876 ms, 7.3/s, median 515 ms, max 6873 ms, $0.0011, actions {"ignore":4,"investigate":36,"join":7,"flee":0,"warn":3}
"A wolf is at the gate": 50/50 ok, burst 9991 ms, 5/s, median 420 ms, max 9990 ms, $0.0011, actions {"ignore":0,"investigate":29,"join":0,"flee":3,"warn":18}
flee+warn: bread 3, wolf 21; join: bread 7, wolf 0; baker on bread → join (p 0.69, urgency 1.1)

Two earlier runs of the same two events, before the check was rewritten:

"Free bread at the bakery": 50/50 ok, burst 6857 ms, 7.3/s, median 512 ms, max 6853 ms, $0.0011, actions {"ignore":1,"investigate":41,"join":7,"flee":0,"warn":1}
"A wolf is at the gate": 50/50 ok, burst 2655 ms, 18.8/s, median 432 ms, max 2655 ms, $0.0011, actions {"ignore":0,"investigate":34,"join":0,"flee":3,"warn":13}

"Free bread at the bakery": 50/50 ok, burst 2775 ms, 18/s, median 643 ms, max 2771 ms, $0.0011, actions {"ignore":3,"investigate":36,"join":8,"flee":0,"warn":3}
"A wolf is at the gate": 50/50 ok, burst 6622 ms, 7.6/s, median 376 ms, max 6622 ms, $0.0011, actions {"ignore":0,"investigate":30,"join":0,"flee":3,"warn":17}

6. Findings

7. Cost and latency

One event: 50 calls with two questions each, $0.0011. An hour of continuous events at 5 calls per second would be about $0.50.

Every number above was measured from one machine on 2026-09-19 through the Vercel AI Gateway. The source videos show demos from machines close to the model; this page shows what the same idea costs from here.