A single docker-compose to deploy an agentic game development studio for use with opencode or similar.
  • Python 87.9%
  • Shell 4.9%
  • TypeScript 2.7%
  • Dockerfile 2.3%
  • Makefile 1.8%
  • Other 0.4%
Find a file
Chris Moriarty 8c78e2313e feat(index_qdrant): resumable ingest — skip already-embedded ids (default)
So restarting after an mcp redeploy re-embeds only what's missing (Qdrant persists
the rest). --force to re-embed all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 01:45:32 -04:00
.opencode feat(playtest): make run_love2d an async job (fixes MCP -32001 timeout) 2026-07-06 01:42:31 -04:00
audio-server feat(audio): M4.5 speech stack — Parakeet/faster-whisper/VAD/Chatterbox, no flags 2026-07-05 14:37:23 -04:00
comfyui feat: M3 — ComfyUI v0.27.0 (FLUX.2 klein + Z-Image arch support) 2026-07-04 16:25:54 -04:00
docs feat: M4 OpenCode wiring — two seats in the harness 2026-07-04 22:03:51 -04:00
graphify-out/cache docs: library is Qdrant semantic + reverse-image (find_similar_assets), 8 services 2026-07-06 01:00:29 -04:00
orchestrator feat(index_qdrant): resumable ingest — skip already-embedded ids (default) 2026-07-06 01:45:32 -04:00
playtest fix: playtest UX — wait rides on action steps; MCP errors carry detail 2026-07-03 13:43:25 -04:00
scripts fix(oga): skip zip members with unsupported compression instead of crashing 2026-07-06 00:48:41 -04:00
tests feat(assets): Qdrant vector search — semantic + reverse-image, retire SQLite 2026-07-06 00:46:07 -04:00
.env.example perf(director): q8_0 KV + 131072 context 2026-07-05 22:27:58 -04:00
.gitignore Added tests and updated docker compose to have separate image and build files. 2026-06-29 22:45:53 -04:00
AGENTS.md feat(playtest): make run_love2d an async job (fixes MCP -32001 timeout) 2026-07-06 01:42:31 -04:00
docker-compose.build.yml feat(audio): M4.5 speech stack — Parakeet/faster-whisper/VAD/Chatterbox, no flags 2026-07-05 14:37:23 -04:00
docker-compose.yml perf(assets): pin fastembed onnxruntime threads (stop cpuset affinity spam) 2026-07-06 00:53:01 -04:00
Makefile chore: update coder model id to trogdor-code and refine smoke test 2026-07-05 16:31:55 -04:00
opencode.jsonc feat(playtest): make run_love2d an async job (fixes MCP -32001 timeout) 2026-07-06 01:42:31 -04:00
openhands.config.toml.example Updated opencode config 2026-07-05 16:07:18 -04:00
pyproject.toml Added tests and updated docker compose to have separate image and build files. 2026-06-29 22:45:53 -04:00
README.md docs: library is Qdrant semantic + reverse-image (find_similar_assets), 8 services 2026-07-06 01:00:29 -04:00
requirements-dev.txt M3: playtest runner service + run_love2d MCP tool 2026-07-02 00:00:10 -04:00
ROADMAP.md feat(audio): M4.5 speech stack — Parakeet/faster-whisper/VAD/Chatterbox, no flags 2026-07-05 14:37:23 -04:00

Agentic Game Development Studio

A fully self-hosted, agent-driven game studio on one server.

What it is

A local game studio where an AI agent (driven by OpenCode) does the heavy lifting across planning, code, art, music, sound, and playtesting. Three principles shape the stack:

  1. The harness owns the loop. OpenCode drives; Trogdor just provides a model (the brain) and tools (the zoo, over MCP).
  2. Every generator has a critic. Images are judged by a vision model, music is scored against its brief, games are run and their screenshots reviewed.
  3. One compose stack. One docker-compose.yml, deployed as a Portainer git stack from Forgejo. Git is the single source of truth.
flowchart TD
    You["👤 You + OpenCode"]
    Brain["🧠 Brain<br/>writes the game, sees its own screenshots"]
    Zoo["🧰 The Zoo<br/>image · music · SFX · video · speech · playtest · CC0 library"]
    Judge["🎬 Director<br/>the only official critic — verifies every artifact"]

    You -->|"prompt + /new-game"| Brain
    Brain -->|"MCP tool calls"| Zoo
    Zoo -->|"artifacts + screenshots"| Judge
    Judge -->|"pass / fail / fix"| Brain
    Judge -->|"verdicts"| You

Build, deploy, operate

Everything is driven from a laptop; Docker commands run on Trogdor over SSH (DOCKER_HOST=ssh://…). The stack is a Portainer git stack pulling this repo from Forgejo. Config lives in .env (copy from .env.example).

make deploy      # build custom images on Trogdor → push to Forgejo → webhook redeploy
make redeploy    # config-only change: git push first, then poke the webhook
make warm        # after a brain redeploy: pays the one-time ~2 min CUDA init
make health      # gateway + all downstreams
make smoke       # every endpoint + model state
make ps / logs   # container status / tails (S=brain|director|comfyui|audio|nemo|playtest|mcp)
make test        # 92 laptop tests — FastAPI wiring, no Docker/GPU/weights

Building, pushing, and running all happen on the real amd64 + CUDA hardware over SSH — no repo or Docker needed on Trogdor. See the Makefile (and make help) for the full list.

Set up OpenCode to drive the studio

The studio has no built-in agent loop — point OpenCode at the brain and the tool gateway:

  • Model (brain): http://trogdor:8090/v1 (OpenAI-compatible)
  • Tools (zoo): http://trogdor:8080/mcp/ (trailing slash — /mcp 307-redirects and some clients won't follow on POST)

Game projects live in their own folders, where repo-scoped config wouldn't load. make opencode-setup symlinks the studio config into ~/.config/opencode/ — every OpenCode session on the machine gets the studio, and a git pull here updates them all:

make opencode-setup    # installs symlinks (AGENTS.md, agents/, plugins/, commands/) + verifies wiring
opencode               # in ANY folder; /mcp lists the 15 zoo tools

The installer never touches your global opencode.json[c] (permissions and theme are yours) — it verifies the trogdor provider/MCP blocks match the repo and tells you what to paste if they drift.

Serve mode (live session the director can post producer notes into):

make studio-up DIR=~/games/my-game   # opencode serve on :4096, session registered with gateway
opencode --hostname 127.0.0.1 --port 4096   # attach the TUI
make studio-down                     # kills ONLY the recorded pid

For OpenHands, use make openhands-setup (or copy the blocks manually from openhands.config.toml.example).

Prompting OpenCode

The brain is text-only — but it never works blind: it delegates image judging to the director and calls the zoo's vision tools directly. Quick reference:

Goal How to prompt
Start a new game /new-game <brief> — runs the full six-phase process (concept → storyboards → asset spec → asset QA → dev plan → build). Director authors design, then hands off to the coder at phase 6.
Resume a game In the game dir: opencode (or make studio-up DIR=<dir>). The committed docs/ is the handoff state — the coder picks up from the next unfinished milestone. /playtest to boot + critique screenshots.
Consult the director /director <question> — runs on the big vision model; looks at the actual artifacts before answering. Use mid-session for design questions or to audit a completion claim.
Playtest /playtest [seconds] — commits + pushes, then run_love2d(git_url=…); critiques each screenshot against the storyboard.
Make an image "Generate a 256px transparent sprite of a cloaked ranger, side view, idle pose, flat background." The agent calls generate_image(transparent=true, sprite_size=256), then critique_image to verify. Always describe: subject, style/medium, palette, perspective, intended use.
Make a spritesheet "Generate a 4-frame walk-cycle spritesheet of a knight walking right, side view." → generate_spritesheet(frames=4) returns an alpha strip + per-frame PNGs + Love2D quads JSON in one pass. Never generate frames separately — they won't match.
Make music "Generate a 30-second driving chiptune loop, action-battle mood, ~140 BPM." → generate_music (async: poll check_job ~every 30s), then analyze_music reports tempo and a CLAP intent score (>0.4 strong, <0.2 miss).
Make a sound effect "Generate a 1-second sword-swing whoosh SFX." → generate_sfx (async, ≤ ~47 s per clip), then analyze_music to verify.
Make speech / narration "Generate a TTS line: 'You shall not pass!', male, dramatic." → generate_tts (Chatterbox, default .ogg).
Transcribe audio "Transcribe this clip." → transcribe_audio(url) (Parakeet default; mode=fast or provider=faster_whisper for the alt). Speech only — hallucinates on music/SFX.
Make a short video "Generate a 4-second animated banner of a dragon breathing fire." → generate_video (LTX-Video, async, 26 s animated WEBP — experimental).
Read a reference image "Look at this mood board and describe its style and palette." → read_image(url). For a local file, the upload-bridge plugin auto-uploads it for you.
Find a library asset "Is there a wooden table texture in the library?" → search_local_assets("wooden table") (semantic — plain descriptions work). "Find library art that looks like this generated sprite." → find_similar_assets(url) (reverse-image). Results carry a license — credit CC-BY.

Briefing rules (the generators have no memory — each prompt stands alone):

  • Be specific: subject, style/medium, palette, perspective, intended use.
  • Search the library firstsearch_local_assets("knight sprite") is instant and license-free; restyle a near-miss with generate_image(init_url=…) written as an edit instruction ("Recolor this tileset to a lava theme.").
  • For sprites use the pixel pipeline: style="pixel", transparent=true, sprite_size=256 (high-res by default — small sizes are an opt-in retro aesthetic, not a space saving).
  • Slow generators (image, spritesheet, music, SFX, video) are async jobs: the tool returns {job_id} and you poll check_job ~every 30s while doing other work. Never resubmit while one runs.
  • Close the loop: verify:true (auto-rerolls up to 3× on FAIL) or critique_image after — don't ship an asset you haven't checked.

The full operating manual lives in AGENTS.md — auto-loaded by OpenCode into every session.

Services

One compose file, eight services. The brain/director use the stock llama.cpp:server-cuda image and qdrant the stock qdrant/qdrant; the five custom images are built on Trogdor and pushed to Forgejo. The asset gateway (mcp) is the only one the agent talks to directly — it routes every other service.

Service Container · port · GPU Version / model Capability How to prompt from OpenCode
brain (coder) trogdor-coder · 8090 · GPU 0+1 Qwen3.6-27B + vision tower (UD-Q4_K_XL, 131K ctx q8 KV) Writes the game; sees its own screenshots while debugging. Self-checks never count as verification. The default build agent — every normal turn. Provide a code/test/fix task; tools=true.
director trogdor-director · 8094 · GPU 2 + NUMA-0 RAM Qwen3.5-122B-A10B MoE (UD-Q4_K_XL) The studio's ONLY official judge: critiques, verifies, reviews playtest screenshots, audits claims. /director <question> (mid-session consult) or the director primary agent (Tab). Default during /new-game design phases.
mcp (gateway) trogdor-mcp · 8080 · CPU n/a (orchestrator) 15 MCP tools + REST + /files + /library. Routes every other service; serializes GPU3 tenants; runs async jobs. Every generate_*, critique_image, read_image, search_local_assets, run_love2d, *audio* tool call goes here.
comfyui gen-comfyui · 8188 · GPU 3 FLUX.2 klein 9B (Q6 GGUF) · LTX-Video 2B · BiRefNet matting Image, spritesheet, and video generation. Style is a prompt phrase; transparent:true → real alpha. Indirect — generate_image, generate_spritesheet, generate_video route here.
audio gen-audio · 8092 · GPU 3 ACE-Step · Stable Audio Open · CLAP + librosa · faster-whisper large-v3 · Chatterbox Music, SFX, audio analysis (tempo/loop intent score), alt ASR, TTS. generate_music, generate_sfx, analyze_music, generate_tts, transcribe_audio(mode=fast).
nemo gen-nemo · 8096 · CPU Parakeet TDT 0.6B v3 · MarbleNet v2.0 VAD Default ASR (multilingual) + speech-segment detection. Isolated image (NeMo pulls numpy≥2). transcribe_audio (default), detect_speech. CPU-pinned so never contends on GPU3.
playtest trogdor-playtest · 8093 · CPU LÖVE under Xvfb + ffmpeg Headless game run + screenshots; one endpoint per engine (/love2d today). run_love2d(git_url=…, script=[…]) — interactive playtests with labeled expect screenshots the director critiques.
library (open-license, mounted into mcp) /library Kenney + game-icons + ambientCG + OpenGameArt · ~90k files · Qdrant (semantic + reverse-image) Game-ready sprites, tiles, UI, icons, textures, audio, fonts — CC0 / CC-BY. Search-first sourcing workflow. search_local_assets("<query>") (semantic) · find_similar_assets(url) (reverse-image / "more like this").
qdrant (vector store) trogdor-qdrant · 6333 · CPU n/a CLIP dense + BM25 sparse (hybrid) over the library; scalar-quantized. The gateway embeds via fastembed (ONNX). (internal — the library tools query it)

REST surface (curl / debugging + batch scripts)

Method Path Purpose
GET /health Liveness + all downstream reachability
GET /files/{path} Fetch any generated asset
GET /library/{path} Fetch any CC0 library asset
POST /assets/image | /music | /sfx | /tts One-shot generation → JSON record ("raw": true streams bytes; image also takes lora, init_url, denoise)
POST /assets/upload Multipart file → /files URL (bridge local files to vision)
POST /describe | /critique REST twins of read_image / critique_image — vision access for batch scripts
POST /transcribe Multipart audio → ASR; provider/mode route to Parakeet (default) or faster-whisper

Big batches (hundreds of similar assets) go through a repo-local script that POSTs the REST API one item at a time — see the "Batch asset generation" section in AGENTS.md.

Hardware (Trogdor)

Component Specification
Server Dell T640
CPU 2× Intel Xeon Silver 4214 — 24 cores / 48 threads, 2 NUMA nodes
GPU 4× NVIDIA Quadro RTX 5000 — 16 GB each (Turing, two NVLink pairs)
Memory 384 GB DDR4-2400 — 192 GB per NUMA node
Storage 4× 1 TB SAS SSD, RAID 10 (~2 TB usable)