BuildAgent World
Agent
World
A pixel-art town that runs on my real MCP stack. Each NPC is a service I use — Asana, Notion, n8n, Linear, Arc, Google Drive, Excalidraw, Granola, Claude Code — and when a task lands or a workflow fires, the matching NPC wakes up and walks to its quarter.
The whole town9 agent types
- Phaser 3
- TypeScript
- WebSocket
- Express
- Canvas 2D
- Vite
- Specialized Agents
- 0
- Procedural Buildings
- 0
- Agent.ts Lines
- 0
- State Machine States
- 0
Live dashboard
The town, running
Click to interact.
The mapping
9 sources → 9 archetypes
Every tool in my stack maps to one NPC archetype. Real activity lands — a task, a page, a workflow run — and the matching agent wakes up and walks to its quarter.
Asana
Gildenläufer
guild runner
task events
Notion
Bibliothekar
librarian
page events
n8n
Ingenieur
engineer
scenario runs
Claude Code
Magier
mage
thinking / response
Google Drive
Schatzwächter
treasure keeper
files
Excalidraw
Kartograph
cartographer
sketches
Linear
Stratege
strategist
issues
Arc
Späher
scout
tabs, bookmarks
Granola
Wirt
innkeeper
meeting notes
How it's wired
From MCP to NPC
The town sits on top of my live MCP and automation stack. Work that happens in a tool runs through the adapter layer and shows up in the world as an NPC reaction.
MCP servers + Claude Code
notion, asana, make, second-brain — the layer where I actually work.
Activity lands in external tools
A new Asana task, a Notion page edit, a Drive upload, a Claude Code session.
AdapterManager polls every 30s
Express + ws. Up to 6 live integrations (Asana, Notion, n8n, Linear, plus file watchers for Excalidraw and Claude Code). Mocks fall back when env tokens are missing.
WebSocket broadcast to the game
Typed events only — source, type, data, timestamp. No game logic on the server.
EventDispatcher wakes up an NPC
SOURCE_TO_AGENT maps the event to an idle agent of matching type. assignTask() fires.
State machine
Four states per agent
Every agent cycles through idle, alert, walking, and working — a real event turns into a roughly five-second scene you can watch. All four transitions sit in one Agent.ts class, 429 lines, no framework.
idle
alert
walking
working
type AgentState = 'idle' | 'walking' | 'working' | 'alert'; // Event arrives → alert → walk → work → back to idleassignTask(taskName: string, duration: number, destX: number, destY: number) { this.pendingTask = { name: taskName, duration }; this.showAlert(); // state: 'alert' setTimeout(() => this.walkTo(destX, destY), 600); // state: 'walking' // On arrival: startWork() → state: 'working' + progress bar + bubble // On finish: completeWork() → state: 'idle' + green particle burst}Decisions
Calls I'd defend
Phaser 3 over raw Canvas
It ships a sprite system, physics, and scene management. On raw Canvas I'd have rebuilt half of that myself.
The agents walk before they work
An event could flip a tile straight to "done" — but then there's nothing to see. The detour makes an Asana card closing something you watch happen, not a number that ticks up.
EventDispatcher is one source-to-archetype dictionary
9 sources map to 9 archetypes in one dictionary. The game scene never hears Asana or Notion — only Gildenläufer and Magier — so a new tool is one adapter and one line in the map.
Polling by default, webhooks when I want them
AdapterManager polls every 30s, so the server runs locally without tunnels. A
POST /api/webhookendpoint gives n8n near-instant NPC reactions when I want them.



















