ALEX GATES-SHANNON · ENGINEER · ASHBURN

I build agentic systems that don't hallucinate their own state.

Most "AI agents" are a chat log with a goal and a prayer. I write about the other kind.

// CONTACT
email
alex@alexgs.dev
github
@alexgs
linkedin
in/alexgs99
rss
/feed.xml
// STATS
1
POSTS
1
PROJECTS
0
DICE FAKED
// TOOL
// from apps/zoltar-be/src/session/session.tools.ts
// What Claude returns each turn.
const submitGmResponseSchema = z.object({
  playerText:    z.string(),                // narration shown to the player
  stateChanges:  z.object({ /* deltas */ }).optional(),
  diceRequests:  z.array(z.object({ /* notation, purpose */ })).optional(),
  gmUpdates:     z.object({ /* npc state, notes, canon */ }).optional(),
  adventureMode: z.enum(['freeform', 'initiative']).nullable().optional(),
});
▸ NOW SHIPPING
ZOLTAR/

An AI game master where Claude runs tabletop RPG sessions without fabricating dice rolls or losing the plot.

[01] TOOL

Dice

The model can ask for a roll. It cannot fake one. The dice happen at the table or in the backend, never in the prompt.

[02] HIDDEN

State

Game state lives server-side and is never in the model's context. Claude proposes changes via tool calls; the validator decides what's real.

[03] RAG

Rules

Rulebooks are ingested as hash-pinned chunks in pgvector. The model retrieves passages by query; nothing about the rules lives in the prompt.

[04] BOUNDED

Drift

Every turn produces a structured delta that goes through one validator pass. If it's invalid, Claude gets one re-prompt to fix it — no further retries.

Writing
1 OF 1 · REVERSE CHRONOLOGICAL
001
2026.04.29
Your Agent's Context Window Is State, Not Input

When you include the complete message history in the context window, the model will treat it as implicit state, and implicit state drifts. Instead, aggressively prune the message history and provide the model with an authoritative state.

6m