Post 3 of 8 in the game-factory series.
The only agent that talks back
Every agent in this pipeline reads files and writes files. The Designer is the exception. You describe a theme — "Norse mythology slots" — and it asks you things. What's the tone: whimsical or dark? Should the high-value symbols be characters or objects? Are there specific win phrases that should feel right for the setting? It consults the original casino's design constraints, checks a worked example, and runs back and forth with you until it has enough to produce a single JSON file.
That file is the contract. It doesn't deploy anything. It doesn't write any code. It describes the game — symbols with weights and prompts, a color palette, winning patterns, UI strings, fonts, sounds, a background prompt — and every later stage reads it. Image-Gen, Background-Gen, Builder, Tester, Deployer: they all start from that file.
That sounds like a narrow job. It turned out to be the most consequential one in the pipeline.
What the agent is
The Designer uses the Bedrock Converse API, the conversational format where you maintain a message history and pass it back on every turn. No framework. A Python class, the API, and three tools.
The tools define the job more precisely than the system prompt does.
generate_game_spec is the terminal tool. When the model calls it, the conversation ends and the spec is ready for human approval. Its input schema is the contract: theme_id, theme_name, symbols (each with a name, a weight from 1 to 10, a category, and an icon_prompt), color_palette, win_conditions, patterns, ui_strings, fonts, sound_theme, background_prompt. I defined that schema once and every downstream agent reads against it.
The other two tools are read-only. read_blueprint lets the model inspect the original casino's design constraints without those constraints being inlined in the system prompt. read_example_spec returns a worked example so the model can see what a correct, complete spec looks like. Both tools exist so the model can look things up on demand. Without them, the system prompt would need to carry several thousand words of context on every single turn — expensive, and stale the moment anything in the casino changes.
The conversation follows a short arc. You describe a theme. The model asks clarifying questions for anything underspecified. It reads the blueprint and example as needed. When it has enough, it calls generate_game_spec. The proposed spec appears, you read it, and you approve, reject, or ask for changes. Approved, it goes to disk as specs/<theme_id>-spec.json. Everything else reads from there.
What the spec is actually doing
The file is a few kilobytes of JSON. It doesn't feel load-bearing until you watch what depends on it.
Image-Gen reads symbols[].icon_prompt to generate each reel icon. If those prompts are vague — "a cool wizard thing" instead of "an ornate golden wizard hat against a dark purple sky, fantasy illustration style" — the icons come out generic. Builder reads color_palette and patches the casino's CSS variables. It reads ui_strings to replace the displayed text. It reads patterns to configure which symbol combinations win. Deployer reads theme_id to name the stack. The whole pipeline is downstream of whatever the Designer committed to disk.
A bad spec is quiet for a long time. The Designer doesn't know whether the prompts it wrote will produce good icons. Builder doesn't know whether the colors will look right in the browser. Tester is the first automated stage that catches it — and by then you're four stages in.
I reduced the risk slightly by making the example spec a tool result rather than a fixed block in the system prompt. The model can compare its proposed output against a well-formed reference before calling generate_game_spec. That helped. It didn't eliminate the problem. Reviewing the spec carefully before approving it — actually reading each symbol prompt, not just skimming the palette — is the single most valuable thing you can do in the whole pipeline.
What went wrong
Three things.
The recursion problem. The Designer was the first agent I wrote, and I wrote it with a recursive response handler. When the model calls a tool, _process_response feeds the result back by calling itself:
# Original Designer: recurse on every tool call
def _process_response(self, response):
if tool_name == "generate_game_spec":
return tool_input # done
result = self._call_model(append_tool_result(response))
return self._process_response(result) # recurse
# All later agents: flat while loop
while True:
response = self._call_model(messages)
if stop_reason == "end_turn":
break
# dispatch tool, append result, keep going
The recursion only goes a few levels deep in practice — the model rarely chains more than two or three tool calls in one turn. It never actually crashed. But it made the code harder to reason about, harder to log each step, and harder to interrupt cleanly. Builder, Tester, and Deployer all use the flat while loop because it's obvious where the iteration happens. The Designer is the one I wrote first and never went back to fix.
The first agent you build teaches you the shape. You just have to notice the lesson before you ship the next five.
Garbage in, garbage out — delayed. The spec is the contract, so the weakest spec produces the weakest game. A symbol with "icon_prompt": "mystical thing" gets an icon that looks like clip art. A win condition with no context gets an awkward label in the UI. Neither of those failures shows up until three or four stages later, and the root is back in the spec file. There is no automated check between "Designer approved" and "game looks bad" because the intermediate artifacts — icon files, styled code — are all valid even when the prompts that generated them were weak. The only mitigation I found was discipline at the approval gate.
The approval gate confused the web UI. The pipeline originally ran in the terminal. Human-in-the-loop meant input(). When I later put it behind a web UI, every Designer turn became a form in the browser — and the Designer's loop has two distinct kinds of turns: conversational turns where the model is asking a clarifying question, and approval turns where it has produced a spec. In the terminal, context makes these obvious. In the web UI, both appeared as the same text box, and users kept trying to approve a question as if it were a finished spec. I had to go back and tag each turn type explicitly — CONVERSATION vs APPROVAL — so the UI could render them differently and route the response correctly. The lesson: "a human in the loop" has a shape, and input() hides that shape until you try to put it somewhere real.
What to take from this
Two things, both concrete.
Make the boundary artifact explicit and inspectable. The spec file is not a convenience — it's the reason failures are diagnosable. When a game comes out wrong, I open the spec and the problem is usually visible. If the Designer had handed off implicitly — passing state in memory, writing values into a database you'd need tooling to query — inspection becomes a project instead of a glance. A flat file on disk is not sophisticated, and that's the point. When each stage's output is a file, you can look at it, diff it, and hand it to someone else without explaining an API.
Use a flat loop for anything conversational. Recursion reads naturally when you're writing it. A while loop is easier to reason about, has no depth limit, is easier to add logging to, and is easier to interrupt cleanly. I reached for the while loop on every agent after the first. Go back and apply the lesson to the first one.
Previous: the factory overview. Next: the image agents.
Top comments (0)