DM Orchestration
DM orchestration modules for model gateway, tool registry, and session coordination.
DM Orchestration
DM orchestration modules in engine/dm/ provide the application-facing assistant wiring. This subsystem implements a lightweight agent architecture for a "Dungeon Master copilot" — an LLM-powered assistant that can answer rules questions, evaluate encounters, and provide actionable DM guidance during game sessions.
Components
model_gateway.py: model/provider abstraction boundary. Defines aModelGatewayProtocol with a singlecomplete(prompt, *, system)method, plus a concreteOpenAIModelGatewayimplementation. The protocol pattern allows tests to inject stubs without touching HTTP, and makes it straightforward to add other providers (Anthropic, local models) by implementing the same interface.tool_registry.py: callable tool inventory and dispatch definitions. TheToolRegistryis a name→handler dict where each handler is aCallable[[dict], dict]. Tools are registered imperatively (registry.register("lookup_rule", handler)) rather than declaratively, giving callers full control over which tools are available in a given session.session_orchestrator.py: session lifecycle and orchestration state. TheSessionOrchestratorcomposes aModelGatewayand aToolRegistry, builds a system prompt that advertises available tools, and routes user input through the model. Currently a single-turn request/response loop — it does not yet implement tool-call parsing or multi-turn agentic execution.tools/: tool implementations such as rules lookup (rules_lookup.py) and encounter balance. Each tool is a standalone function with adict → dictsignature, making tools trivially unit-testable in isolation.
Design Rationale
The DM orchestration layer is deliberately minimal — a thin shell around a model call with an extensible tool registry. This reflects several design choices:
- Protocol-based model abstraction — rather than coupling to a specific SDK, the
ModelGatewayProtocol decouples the orchestration logic from any particular LLM provider. This means the sameSessionOrchestratorcan run against OpenAI, a local model, or a test stub, with zero code changes in the orchestration layer. - Imperative tool registration — tools are registered at runtime rather than discovered via decorators or metaclasses. This makes the tool set fully deterministic and testable: you construct a
ToolRegistry, register exactly the tools you want, and pass it in. No hidden global state. - Single-turn simplicity — the current implementation intentionally avoids multi-turn agentic loops, tool-call parsing, or conversation memory. The
run_turnmethod takes user input and context, builds one prompt, gets one completion, and returns. This keeps the surface area small while the tool and model abstractions stabilize.
An alternative — adopting a framework like LangChain or building a full ReAct loop — was deferred because the tool set is still small and the primary value is in getting the abstraction boundaries right before adding complexity.
Assumptions & Constraints
- Stateless turns. The orchestrator does not maintain conversation history. Each call to
run_turnis independent. Session continuity (if needed) must be managed by the caller, passing relevant context via thecontextdict. - Tools are not yet invoked automatically. The orchestrator advertises tool names in the system prompt but does not parse the model's output to detect tool-call requests. Tool execution is a planned extension, not a current capability.
- Placeholder model wiring.
OpenAIModelGateway.complete()currently returns a truncated echo of the prompt rather than making a real API call. This is flagged as a placeholder in the source — production wiring requires injecting an actual OpenAI client viaclient_factory. - No authentication or rate limiting. The orchestration layer assumes a trusted caller. Access control and usage limits are expected to be handled at the API route level in Server.
- Mem0 as memory backend. The prototype memory layer will use Mem0 through an adapter boundary. App-db should still store raw turns and memory-operation audit rows so campaign history is inspectable.
- Deterministic combat boundary. Combat legality and resolution belong in engine code. The model can propose structured NPC moves, but the engine validates and applies all state changes.
Conceptual Model
The DM orchestration subsystem follows a classic gateway + tools + orchestrator pattern:
User input + context
↓
SessionOrchestrator
├── builds prompt (system prompt + tool list + context + input)
├── calls ModelGateway.complete()
└── returns response dict
↓
API route (in server/)
The ToolRegistry is injected but not yet invoked in the main loop — it currently serves only to populate the tool list in the prompt. The planned extension is straightforward: parse the model response for tool-call markers, dispatch through ToolRegistry.run(), and feed results back into a follow-up completion.
The key boundary is between orchestration (which knows about turns, prompts, and tool dispatch) and tools (which know about domain logic like rules lookup). Tools never see the model or the session — they receive a payload dict and return a result dict. This keeps tools portable and testable without any orchestration infrastructure.
Integration Notes
- This subsystem depends on stable schema shapes from Engine and Data Pipeline.
- Route-level exposure is handled by Server as API endpoints are added.
- The playable prototype path is tracked in MVP Prototype Backlog, which prioritizes real model calls, campaign/session persistence, rules lookup, and a narrow DM chat loop before additional combat dataset work.
- Decision: Mem0 Memory and Deterministic Combat Boundary records the Mem0 memory and deterministic combat ownership boundary.