Skip to content

Vault Conventions

File structure and frontmatter standards for campaign vaults.


Overview

The vault is an Obsidian-compatible markdown directory that stores all campaign state. It's the source of truth for the DM agent - everything the agent knows comes from vault files.

campaigns/
├── seagate/                    # Campaign directory
│   ├── canon/                  # Current game state (agent reads this)
│   │   ├── pcs/               # Player characters
│   │   ├── npcs/              # Non-player characters
│   │   ├── locations/         # Places
│   │   ├── items/             # Notable items and artifacts
│   │   ├── factions/          # Organizations
│   │   ├── threads/           # Individual plot threads (detailed)
│   │   ├── arcs/              # Story arcs (narrative structure)
│   │   ├── quests/            # Specific quests/missions
│   │   ├── open-threads.md    # Active storylines summary
│   │   ├── timeline.md        # Event history
│   │   └── temporal-index.json # Current day/time
│   ├── sessions/              # Session logs
│   └── _gm/                   # GM-only secrets (not injected)
└── ironvale/                   # Another campaign

Frontmatter Standards

All entity files use YAML frontmatter. The frontmatter is parsed for filtering, context building, and RAG retrieval.

Common Fields

Field Type Description
type string Entity type: pc, npc, location, item, faction, index
status string active, inactive, deceased, destroyed
triggers list Keywords for RAG retrieval
last_updated date ISO date of last modification

PC Fields

---
type: pc
status: active
triggers: ["Jake", "the wizard", "human mage"]
player: Jake
last_updated: 2025-12-25

# Mutable state (tools update this)
level: 3
class: Wizard
subclass: Abjuration
hp_current: 13
hp_max: 13
gold: 15
location: the-salty-sigil
conditions: []  # e.g., ["exhaustion 1", "poisoned"]

inventory:
  equipped: [...]
  carried: [...]
  stored: [...]

spell_slots:
  1st: {current: 4, max: 4}
  2nd: {current: 2, max: 2}

allies:
  - {name: Elara, relation: "Landlord, ally"}
  - {name: Marlena, relation: "Intimate ally"}
---

NPC Fields

---
type: npc
status: active
triggers: ["Marlena", "component dealer", "The Salty Sigil"]
disposition: intimate-ally
location: the-salty-sigil
last_updated: 2025-12-25
---

Disposition values: - intimate-ally - Physical affection, genuine concern, vulnerability - ally - Trust, cooperation, professional warmth - neutral - Transactional, cautious - wary - Suspicious, requires proof of intent - hostile - Active opposition, may attack/betray

Status values: - active - Currently in play - inactive - Exists but not currently relevant - rescued - Recently saved/recovered - deceased - Dead - unknown - Status uncertain

Location Fields

---
type: location
status: active
triggers: ["Gilded Quill", "the bookshop", "Elara's shop"]
location_type: building
parent: seagate
last_updated: 2025-12-24
---

Location types: - city - Major settlement - district - Area within a city - building - Structure (shop, inn, tower) - wilderness - Outdoor area - dungeon - Underground/enclosed dangerous area

Item Fields

---
type: item
status: active
triggers: ["brass token", "V-7 token", "courier token"]
rarity: uncommon
magical: true
owner: jake
location: carried
last_updated: 2025-12-26
---

Index Files

Thread Description Convention

Critical Pattern: Thread descriptions must include current state, not just the problem.

The DM agent infers missing information. If a thread description is ambiguous, the agent will guess—often incorrectly. Thread descriptions are injected into every session context, making them the primary source of truth for ongoing storylines.

Anti-pattern (causes hallucination):

Gareth & Jorah Recovery
Description: Both miners are traumatized
Next step: Reunite the partners
→ Agent infers Jorah might be missing, hallucinates dialogue about "finding" him.

Correct pattern:

Gareth & Jorah Recovery
Description: Gareth recovering at Salty Sigil. Jorah under Elara's ward at Gilded Quill. Both traumatized.
Next step: Bring Jorah to see Gareth
→ Agent knows exactly where everyone is. No inference needed.

Rule: When calling update_thread, always include: 1. WHO - which entities are involved 2. WHERE - their current locations 3. WHAT - their current state 4. WHY - what's at stake (if not obvious)

Thread next_step = action to take. Thread description = canonical situation.


open-threads.md (Legacy)

Note: For DatabaseVault, threads are stored in rpg.threads table and injected directly. The open-threads.md file is used only by file-based vaults.

Summary of active storylines, organized by priority.

---
title: Open Threads
type: index
priority: high
triggers: ["threads", "quests", "priorities"]
---

# Open Threads

## High Priority

### The Fading Muffle
- **Situation:** Token with scrying anchor, muffled for ~36 hours
- **Stakes:** If muffle fades, caster can locate Jake
- **Next step:** Execute infiltration before deadline

## Medium Priority

### Marlena's Favors
- **Situation:** Jake owes TWO unspecified favors
- **Stakes:** Favors will be called in at dramatic moments
- **Next step:** Wait for her to call them in

## Completed/Resolved

### ~~Rescue Gareth~~ (Session 5)
- **Resolved:** Jake found Gareth in Sunfall Hills

timeline.md

Chronological event history.

---
title: Campaign Timeline
type: index
priority: high
triggers: ["timeline", "history", "what happened"]
---

# Timeline

## Day 1 (Session 01 - 2025-12-23)

### Morning
- Jake confronted by Elara over rent
- Pip attempts to steal coin purse; caught

### Afternoon
- Alliance seed planted
- Jake accepts mission

## Day 2 (Session 02 - 2025-12-24)
...

temporal-index.json

Current temporal state (updated by tools).

{
  "current_day": 5,
  "current_time": "morning",
  "session": 5
}

Content Sections

Entity files follow consistent section patterns.

NPCs

# Marlena

## Role
Proprietor of The Salty Sigil curio shop...

## Appearance
- Tall and thin, willowy movements
- Jet-black hair that looks perpetually damp

## Personality
- Speaks in a slow, measured drawl
- Views herself as a "gardener"
- Has a vulnerable side

## What Jake Knows
- Former supplier to Lorcan Mourn
- Knows about the Silent Circle

## Current Relationship
**Intimate alliance** - evolved from transactional...

## Connections
- Lorcan Mourn (former client, deceased)
- The Silent Circle (aware of them)
- **Jake** - intimate ally

Locations

# The Gilded Quill

## Overview
Bookshop in Seagate owned by Elara...

## Features
- Main shop floor with books and scrolls
- Back room / office
- Jake's lodging (upstairs)

## Current Occupants
- **Elara** - Proprietor
- **Jake** - Tenant

## Security
- Arcane Lock on doors
- Chest of Security for dangerous items

Tool Integration

Reading Entities

from lib.vault import Vault
from lib.models import EntityType

vault = Vault("/campaigns/seagate")

# Get specific entity
npc = vault.get_entity(EntityType.NPC, "marlena")
print(npc.frontmatter)  # {'type': 'npc', 'disposition': 'intimate-ally', ...}
print(npc.content)      # Markdown content after frontmatter

# List entities
npcs = vault.list_entities(EntityType.NPC)
for npc in npcs:
    if npc.frontmatter.get("location") == "the-salty-sigil":
        print(f"At Salty Sigil: {npc.id}")

Updating PC State

The update_pc tool modifies frontmatter fields:

# Tool call from DM agent
update_pc(hp_current=10, location="gilded-quill", conditions=["exhaustion 1"])

# Vault.update_pc() rewrites the frontmatter section

Adding Threads

vault.add_thread(
    name="New Mystery",
    priority="medium",
    description="Something strange in the harbor..."
)

Triggers for RAG

The triggers field enables keyword-based retrieval:

triggers: ["Marlena", "component dealer", "The Salty Sigil", "gardener"]

When a player mentions "the component dealer", the RAG system can retrieve Marlena's full document even if they don't use her name.


GM Secrets

The _gm/ directory is never injected into context. Use it for: - Future plot reveals - NPC secret motivations - Encounter plans - Session notes

_gm/
├── secrets/
│   └── marlena-true-agenda.md
├── encounters/
│   └── salon-confrontation.md
└── notes/
    └── session-6-plan.md