Your AI models finally have an office.

An isometric, Theme Hospital–style control panel for an AI team: each model gets a room, tasks arrive as envelopes, and when you drag one onto a model — the model actually runs.

▸ Live demo · Quick start · Add your model · Roadmap · Русский

office.mp4

An isometric scene where each model of the team sits in its own room. Tasks are envelopes in Hermes's tray (the coordinator). Drag an envelope onto a model → the task lands in the orchestrator journal → the dispatcher actually runs the model with that task.

- Frontend — Vite + React + TypeScript + SVG, offline, no external graphics libraries.

- Journal — single source of truth (journal.json): models, tasks, statuses, history.

- API — server.py, serves the journal and accepts operations.

- Dispatcher — dispatch.py, picks up assigned tasks and runs executors.

prompt-hospital/ # frontend (this repository)

src/journal/ # journal: types, ops (pure operations), selectors, store, remote

src/scene/ # isometric scene, rooms

src/characters/ # chibi characters, statuses

src/animations/ # idle animations (walk/coffee/sleep), gait

src/app/ src/ui/ # layout, model card, tray, drag-and-drop

src/theme/ # palette, CSS animations

orchestrator/ # control-panel backend (in this same repository)

journal.example.json # starter journal — copy to journal.json

journal.py # operation CLI (same ops as the frontend OPS)

server.py # HTTP API: GET /journal, POST /op

dispatch.py # dispatcher: task → actually run the model

┌────────────────────────────────────────────┐

│ Office (React, example.com) │

│ drag-and-drop, card, catalog, tray │

└───────────────┬────────────────────────────┘

GET /journal│ POST /op (operations)

▼

┌────────────────────────────────────────────┐

│ nginx (Basic Auth) │

│ /journal, /op → orchestrator-api │

└───────────────┬────────────────────────────┘

▼

┌────────────────────────────────────────────┐

│ server.py (API :8090, docker) │

│ GET /journal, POST /op → journal.py │

└───────────────┬────────────────────────────┘

▼

┌────────────────────────────────────────────┐

│ journal.json (source of truth) │

└───────────────┬────────────────────────────┘

│ polls on a loop (--loop)

▼

┌────────────────────────────────────────────┐

│ dispatch.py — actually runs models │

│ codex exec / claude -p │

└────────────────────────────────────────────┘

git clone https://github.com/s4ps4n/prompt-hospital.git

cd prompt-hospital

npm install

npm run dev # dev server at http://localhost:5173Journal integration is enabled with an env var at build time:

VITE_JOURNAL_URL=/journal npm run build # office reads/writes the journal at /journal

npm run preview # http://localhost:4173Without VITE_JOURNAL_URL the office runs standalone (journal in localStorage, seeded tasks).

cd orchestrator

cp journal.example.json journal.json

python3 journal.py list # view the journal

python3 server.py 8090 # run the API (locally)

python3 dispatch.py --once # one dispatcher pass- Create the journal — cp orchestrator/journal.example.json orchestrator/journal.json.

- Run the API — python3 orchestrator/server.py 8090(serves/journal, accepts/op).

- Build the frontend with VITE_JOURNAL_URL=/journaland serve the static files behind nginx, proxying/journaland/opto the API (see "Deploy").

- Run the dispatcher:

ORCHESTRATOR_API=https://example.com \

ORCHESTRATOR_AUTH='user:pass' \

python3 orchestrator/dispatch.py --loop 30journal.py (and POST /op {name, args}) supports the same operations as the frontend:

Roles: coordinator, executor, architect, fullstack, sysadmin, designer, UX/UI, acceptor, reviewer.

Bookkeeping rule: whenever you start a model on a task, immediately addTask + assign in the journal; when done, complete. Otherwise the office shows a lie.

A model has two sides: how it looks in the office and what actually runs when it gets a task.

1. Appearance — add an entry to CATALOG in src/journal/catalog.ts:

{ model: 'phi', name: 'Phi', provider: 'Microsoft', hair: '#6fb7e8', color: '#3f87b8', skin: '#f2c9a0', style: 'bob', acc: 'glasses' },style: cap | bob | quiff | bun | spiky; acc (optional): wings | glasses | headset.

Models not in the catalog still get a stable look — by prefix (qwen-27b → Qwen), by provider,

or by a hash of the name — so this step is about a face of its own, not about working at all.

2. Execution — in orchestrator/dispatch.py, inside tick(), add a branch:

model = (w.get('model') or '').lower()

if 'codex' in model:

ok = run_codex(task['id'], task['title'])

elif 'my-model' in model:

ok = run_my_model(task['title'])Then write run_my_model — the call to your executor (CLI/API). The dispatcher records

complete/block by itself.

A PR titled Add model: <Name> with a screenshot of the room is the easiest first contribution.

Docker + nginx (Basic Auth) + Traefik with HTTPS — step by step in INSTALL.md, Level 2.

- A. Journal — single source of truth, operation CLI.

- B. Monitor — office reads the journal (read-only).

- C. Write — drag-and-drop writes to the journal via POST /op.

- D. Execute — the dispatcher actually runs the model and closes the task.

By default the office runs at level C (writes to the journal); D is enabled by running dispatch.py.

How the author runs it; any orchestrator that speaks HTTP works the same way.

npm test # vitest — journal, operations, interaction, remote mode

npm run lint # oxlint

npm run build # tsc + vite build- ✅ Office and rooms — models, tray, journal, idle animations

- ✅ Own orchestrator — journal, API, dispatcher that actually runs models

- 🔜 Stats — who worked how much, per model

- 🔜 The Warden — a hand that slaps idle models back to Hermes for a task

Details in ROADMAP.md.

Inspired by Theme Hospital (Bullfrog, 1997); no assets, names or code from the original are used. MIT © s4ps4n.

Made in Ulan-Ude by @s4ps4n at ITTEK — we build web products, online stores and AI tooling.