Skip to content

Context Injection

How the DM agent receives campaign state before each message.


Overview

The DM agent doesn't have persistent memory. Before each message, we inject relevant campaign state from the vault files. This keeps the agent grounded in canon.

User message → Context Builder → Inject into prompt → DM Agent → Response

Context Types

Full Context (~2-3KB)

Injected on session start or when user sends a resume command (e.g., "continue", "resume", "start session").

## SESSION CONTEXT: Seagate
**CITY: SEAGATE** (This is the setting - do not substitute other location names)

**Day 5** - morning

### Player Character
**Jake** (Wizard)
- HP: 13/13
- Location: the-salty-sigil
- Gold: 15 gp

### Current Location
**The Salty Sigil**
A curio shop in the Dredgeworks dealing in rare magical components...

### NPCs Present
- **Marlena** [active] - intimate-ally
  *Voice: slow measured drawl, drops when emotional; Shows vulnerability with Jake*
- **Gareth** [rescued] - ally

### Active Storylines
- **[URGENT]** The Fading Muffle
  Token scrying anchor muffled ~36 hours, fading...
  → Next: Execute infiltration before it fails
- **[URGENT]** Silent Circle Salon (TONIGHT)
  Infiltrate as Courier Valen...

### Recent Events
- Jake rescued Gareth from Sunfall Hills gully
- Token muffling performed by Marlena
---

Light Context (~60-80 chars)

Injected on follow-up messages within an active conversation.

[Day 5 | Jake HP 13/13 | at the-salty-sigil | with Marlena, Gareth]

This is enough for the agent to maintain continuity without bloating the context window. The full message history provides additional grounding.


Context Sources

Context Element Source File Notes
Campaign name Directory name seagate/ → "Seagate"
Day/time canon/temporal-index.json {"current_day": 5, "current_time": "morning"}
PC state canon/pcs/*.md frontmatter HP, location, conditions, gold, inventory
Current location canon/locations/*.md Matched by PC's location field
NPCs present canon/npcs/*.md Filtered by NPC's location field
Active threads canon/open-threads.md Parsed by priority level
Recent events canon/timeline.md Last 5 events from current day

Context Builder

Located at api/context/builder.py.

from api.context.builder import ContextBuilder
from lib.vault import Vault

vault = Vault("/campaigns/seagate")
builder = ContextBuilder(vault)

# Build context from vault files
ctx = builder.build()

# Format for injection
full_context = builder.format_full(ctx)   # Session start
light_context = builder.format_light(ctx)  # Follow-up

Key Classes

@dataclass
class SessionContext:
    campaign_name: str
    pc_name: str
    pc_class: str
    hp_current: int
    hp_max: int
    location: str
    conditions: list[str]
    gold: int
    current_day: int
    current_time: str
    current_location: LocationContext | None
    npcs_present: list[NPCContext]
    threads_high: list[ThreadContext]
    threads_medium: list[ThreadContext]
    recent_events: list[str]

@dataclass
class NPCContext:
    id: str
    name: str
    role: str
    status: str
    disposition: str
    location: str
    personality: str  # Extracted from ## Personality section
    voice: str        # Extracted from voice/speech patterns

Resume Command Detection

The system distinguishes between: - Resume commands: User starting/continuing a session → Full context - Normal messages: Mid-conversation actions → Light context

def is_resume_command(message: str) -> bool:
    resume_commands = {
        'continue', 'resume', 'go', 'start', 'begin', 'proceed',
        'what now', 'whats next', "what's next", 'next',
        'start session', 'resume session', 'continue session',
    }
    normalized = message.lower().strip().rstrip('?!.')
    return normalized in resume_commands

Short messages like "1", "yes", "attack" use light context and rely on message history.


NPC Personality Extraction

For NPCs at the current location, the builder extracts personality and voice info:

## Personality
- Speaks in a slow, measured drawl (drops when emotional)
- Views herself as a "gardener" - supplies ingredients, doesn't judge

Becomes:

- **Marlena** [active] - intimate-ally
  *Voice: slow measured drawl, drops when emotional; Views herself as a "gardener"*

This helps the DM maintain consistent NPC voices.


Debugging Context

The /campaigns/{id}/context endpoint returns the full context for debugging:

curl http://localhost:8000/campaigns/seagate/context

Context is also stored with each message in the injected_context column of rpg.chat_messages.


Known Limitations

  1. Location changes: Context isn't re-injected when player moves mid-conversation. The system prompt instructs the DM to handle location changes, but NPC lists may be stale.

  2. NPC limits: Only 5 NPCs are included to avoid context bloat. Crowded locations may miss some characters.

  3. Thread parsing: Markdown parsing is regex-based and may miss unusual formatting.

  4. No RAG yet: Entity mentions in player messages don't trigger retrieval. Planned for Phase 3.