Architecture¶
TheAct orchestrates text RPG turns through code. The LLM never decides what to do next — it only responds to specific, narrow tasks. This section covers how the pieces fit together.
For terminology used throughout, see Concepts.
Turn Pipeline¶
sequenceDiagram
participant P as Player
participant E as Engine
participant N as Narrator
participant C as Characters
participant M as Memory Agents
participant G as Game State Agent
participant D as Disk + Git
P->>E: Player input
E->>N: Context (world, chapter, summary, history)
N-->>E: Narration + responding_characters + mood
loop For each responding character
E->>C: Context (character def, memory, history, prior responses)
C-->>E: Dialogue/action (50-150 words)
end
par Post-turn (parallel)
E->>M: Current turn events + character memory
M-->>E: Updated memory (add/remove/update facts + summary)
E->>G: Chapter beats + current turn
G-->>E: Beat progress + completion check
end
E->>D: Write all files + git commit The narrator streams first, producing narration text and deciding which characters should respond. Characters then respond sequentially — each sees all prior responses in the same turn, enabling coherent multi-character dialogue. After all responses are collected, memory updates (one per character) and the game state check run in parallel via asyncio.gather(), since they are independent of each other. Finally, all state is written to disk and committed to git.
For the full turn pipeline diagram in context, see index.md.
Module Map¶
graph TB
subgraph Foundations
models["models/\nPydantic data models"]
llm["llm/\nLLM client, streaming,\nparsing, tokens"]
io["io/\nYAML I/O, save manager,\nsettings store"]
end
subgraph Core
engine["engine/\nTurn orchestration,\ncontext assembly"]
agents["agents/\nNarrator, character,\nmemory, game state"]
versioning["versioning/\nGit operations"]
commands["commands/\nShared command logic"]
end
subgraph Frontends
cli["cli/\nRich terminal UI"]
web["web/\nNiceGUI browser UI"]
playtest["playtest/\nAutonomous testing"]
creator["creator/\nGame generation"]
debugger_mod["debugger/\nTurn debugger"]
end
models --> engine
models --> io
llm --> agents
io --> engine
agents --> engine
versioning --> engine
versioning --> commands
commands --> cli
commands --> web
engine --> cli
engine --> web
engine --> playtest
engine --> debugger_mod
models --> creator Dependency flow is strictly layered. Foundations have no business logic — they handle data shapes, LLM communication, and file I/O. Core builds on foundations to implement turn orchestration, agent logic, and shared command logic. The commands/ module contains pure functions (no UI imports) that both CLI and web frontends call. Frontends consume the engine and commands, and never talk to each other.
Concurrency Model¶
- Characters respond sequentially. Each character agent sees all prior character responses from the same turn. This produces coherent multi-party dialogue instead of characters talking past each other.
- Post-turn agents run in parallel. Memory updates (one per character) and the game state check are independent tasks. They execute concurrently via
asyncio.gather(). - All LLM calls are async. The client wraps
AsyncOpenAI, so the event loop is never blocked during inference.
Streaming¶
LLM responses stream token-by-token through a layered callback system:
- The LLM client yields
StreamChunkobjects as tokens arrive. - Each agent accepts an
on_tokencallback and invokes it per chunk. run_turn()wires agent callbacks to aStreamCallbacktype provided by the frontend.- The frontend implements
StreamCallback— the CLI renders tokens via Rich, the web UI pushes them over WebSockets.
The engine has zero knowledge of UI. It only knows that someone handed it a callable to receive tokens.
Frontends¶
Both the CLI and web UI consume the same run_turn() interface. The engine does not distinguish between them.
| Frontend | Location | Transport |
|---|---|---|
| CLI | src/theact/cli/ | Rich terminal with inline streaming |
| Web UI | src/theact/web/ | NiceGUI with WebSocket streaming |
| Playtest | src/theact/playtest/ | Headless — collects output for scoring |
| Turn Debugger | src/theact/debugger/ | Interactive — step through agents one at a time |
The playtest framework and turn debugger also consume run_turn(), making them first-class frontends rather than special-cased tools.
Web UI Architecture¶
The web UI uses a modular architecture with separated concerns:
GameSessionState(state.py) — shared observable state. All components read from it and register listeners for automatic updates. Components never mutate state directly.TurnRunner(turn_runner.py) — wrapsrun_turn(), returnsTurnResultwithout handling UI.StreamRenderer(streaming.py) — routes streaming tokens from the engine to UI blocks.CommandRouter(command_router.py) — dispatches slash commands to shared logic insrc/theact/commands/and renders results.components/— reusable UI components (turn cards, message blocks, thinking panels, dialogs, HTML utilities).
Feature modules extend the core:
| Module | Description |
|---|---|
toolbar.py | Quick-action button bar (undo, retry, save-as, history) |
sidebar.py | Collapsible right panel with character cards, chapter progress, memory |
history.py | Turn history timeline with peek and diff viewers |
creator_wizard.py | Multi-step game creation stepper |
settings.py | LLM and display configuration page |
playtest_dashboard.py | Launch, monitor, and review playtests |
diagnostics_viewer.py | Call log browser, token charts, error viewer |
safety.py | Multi-tab file locking and session recovery |
Command logic is shared between CLI and web via src/theact/commands/logic.py, which contains pure functions with no UI imports. Both frontends are thin rendering layers over this shared logic.
See Also¶
- Concepts for terminology
- Agents for individual agent details
- Data Model for file structures