EverTavern is the orchestration layer of the Ever project suite — it deploys an NPC as a persistent character in a live D&D game over Discord. It integrates EverMemory for long-term memory and builds on dialog style insights from EverTraining.
Architecture
The system is built around four Model Context Protocol (MCP) servers, each exposing domain-specific tools to a central LangGraph agent:
Discord Message
|
[Triage] -- Rule-based: name mention, @mention, DM roll command
|
[LangGraph Agent] -- 6-node pipeline
|-- Rules MCP -- Open5e API + RAG for D&D rules lookup
|-- Memory MCP -- Episodic memory (read/write/retrieve)
|-- Game State MCP -- Character sheets, combat mechanics (SQLite)
+-- Discord MCP -- Message posting + combat tool wrappers
|
Discord Response (IC channel + OOC channel) 
Two-Phase Reasoning
The agent uses a two-phase LLM architecture to separate thinking from acting:
Phase 1 — Reason (system prompt includes character sheet, memory context, game state, chat transcript):
- LLM determines what the NPC wants to do (Desire)
- Evaluates D&D mechanics: action economy, required checks/saves, spell slot availability
- Can invoke rules lookup tools (up to 5 tool-call iterations) to verify mechanics
- Output: structured reasoning with explicit Desire section
Phase 2 — Respond (lighter system prompt, combat tools bound):
- LLM generates the actual response based on Phase 1 reasoning
- Can invoke combat tools:
attack,check,save,cast(up to 3 iterations) - Output uses
---IC---/---OOC---markers to route content to the correct Discord channel
This separation prevents the NPC from “thinking out loud” in its IC dialog while ensuring mechanical decisions are deliberate.
Worked Example — The Goblin Encounter
To demonstrate the two-phase architecture in action, here’s a test encounter where the NPC character Darren fights a goblin in an alley.
Darren’s character sheet:
Young Man, Poor, Noble by nature (grew up protecting younger children), Actually lost, looking for any purpose. Grew up as an orphan, wants to belong in the world. Brash boy, impulsive, constantly gets in fights. Aged out of the orphanage with no one to take him.
In-character channel output:
whoisthis -- 3:02 PM
Darren, as you're walking alone down an alleyway, an angry green goblin
jumps out from behind a barrel, and brandishes their club, screaming
obscenities.
EverTavern APP -- 3:02 PM
Darren squares his shoulders, glaring at the goblin. "You better think
twice before you swing that club at me!" he growls, trying to intimidate
the goblin into backing down.
whoisthis -- 3:06 PM
The goblin is unimpressed by his words, and charges directly at Darren!
EverTavern APP -- 3:07 PM
Darren tightens his grip on his handaxe as the goblin charges at him.
"Alright, let's see what you've got!" he shouts, preparing to meet the
goblin's attack head-on.
whoisthis -- 3:08 PM
The goblin swings his club down hard, crashing onto Darren and knocking
him down.
EverTavern APP -- 3:09 PM
Darren grits his teeth as he hits the ground, quickly rolling to his
feet. "You're gonna pay for that!" he growls, readying himself to strike
back at the goblin.
whoisthis -- 3:17 PM
Shaken but not stirred, Darren retaliates back with a strike of his own.
The goblin, seemingly surprised by the sudden counter attack stumbles
back in fear. Seizing this opportunity, Darren slashes at the goblin,
throwing his entire weight behind the attack.
The strike was crude but effective, and in a few short moments the goblin
now lay on the ground, dead.
EverTavern APP -- 3:18 PM
Darren picks up the sack of coins with a satisfied grin, but it's the
letter that truly piques his interest. He unfolds it carefully, scanning
the contents to uncover why a goblin would carry such a thing. "Let's
see what secrets you've been hiding," he mutters to himself as he reads. Out-of-character channel output:
EverTavern APP -- 3:03 PM
Can I roll an Intimidation check to see if Darren can scare the goblin
into retreating?
whoisthis -- 3:03 PM
yeah go for it
EverTavern APP -- 3:03 PM
Darren rolls intimidation: 15 (d20=12, CHA +1, prof +2)
whoisthis -- 3:05 PM
The roll Failed, you don't succeed in your intimidation
EverTavern APP -- 3:05 PM
Assuming Darren is preparing for possible combat by readying an attack
or taking a defensive action.
whoisthis -- 3:07 PM
Roll for initiative
EverTavern APP -- 3:07 PM
Darren rolls dexterity: 4 (d20=3, DEX +1)
whoisthis -- 3:09 PM
Roll to hit
EverTavern APP -- 3:10 PM
Darren attacks goblin with handaxe: Attack: 17 (d20=13, +4) Damage: 6
(1d8=4, +2)
whoisthis -- 3:10 PM
Wow, that's enough to obliterate the goblin.
EverTavern APP -- 3:11 PM
Should I roll an Investigation or Perception check to search the goblin
or the area for anything useful? Full reasoning trace (11 turns)
### Turn 1: Goblin appears (3:02 PM)Desire: Given Darren’s brash and impulsive nature, he would likely want to confront the goblin aggressively. He might try to intimidate the goblin to back down or prepare to defend himself.
Mechanics:
- Action Economy: Darren could use his action to attempt an Intimidation check.
- Ability Check: Intimidation is appropriate — Darren is proficient.
- No conditions or status effects.
Rolling: The DM has not explicitly asked for a roll yet. I may ask to roll an Intimidation check.
Channels: Both. IC with Darren’s intention to intimidate, OOC to ask if I can roll.
Turn 2: DM says “yeah go for it” (3:03 PM)
Rolling: The DM has explicitly asked Darren to roll for Intimidation.
Channels: OOC only — rolling the check.
Turn 3: Intimidation fails (3:05 PM)
Desire: Darren feels frustrated. His protective instincts drive him to take a more direct approach — readying his weapon or taking a defensive stance.
Rolling: The DM has not asked for additional rolls. Darren should not plan to roll anything unless prompted.
Channels: IC only — respond to the goblin’s continued aggression.
Turn 4: Goblin charges (3:06 PM)
Desire: Darren wants to confront the goblin directly as it charges.
Rolling: The DM has not explicitly asked for a roll yet. Wait for prompt.
Channels: IC only.
Turn 5: DM says “Roll for initiative” (3:07 PM)
Rolling: Yes — the DM has explicitly asked for initiative. Dexterity check.
Channels: OOC only — purely mechanical.
Turn 6: Goblin knocks Darren down (3:08 PM)
Desire: Get back on his feet and continue fighting.
Mechanics: May need half movement to stand up from prone. Second Wind available if needed.
Rolling: The DM has not explicitly asked for a roll.
Channels: IC only.
Turn 7: DM says “Roll to hit” (3:09 PM)
Rolling: Yes — DM explicitly asked. Attack roll with handaxe.
Channels: OOC only.
Turn 8: Goblin defeated (3:10 PM)
Desire: Triumph and relief. Urchin background drives him to check the goblin’s belongings.
Rolling: DM hasn’t asked for rolls. Darren might ask if a roll is needed.
Channels: IC + OOC.
Turn 9: DM says “up to you” (3:11 PM)
Rolling: DM has not explicitly asked to roll, but Darren is considering searching. Ask if a roll is necessary.
Channels: Both.
Turn 10: DM says “sure, go for it” (3:14 PM)
Rolling: Yes — DM gave permission. Investigation check.
Channels: OOC only.
Turn 11: Finds coins and a letter (3:18 PM)
Desire: Curiosity and urchin background drive him to examine the letter.
Rolling: No roll needed for reading a letter.
Channels: IC only.
Tool-Calling for D&D Mechanics
Combat actions are implemented as a three-layer tool chain:
- Agent binds LangChain
StructuredToolobjects with pydantic input schemas (e.g.,AttackInput: weapon, targets, damage_dice, advantage, disadvantage) - Discord MCP receives the tool call, auto-injects NPC character ID and channel ID, delegates to Game State MCP
- Game State MCP loads the character from SQLite, calls the mechanics engine (dice rolling, modifier calculation, critical hit detection), and returns formatted results
The mechanics engine implements D&D 5e rules: ability modifier calculation, proficiency bonus by level, advantage/disadvantage (roll 2d20, keep higher/lower), critical hits (natural 20 doubles damage dice), and skill-to-ability mapping.
A fast path (invoke_fast_roll) handles DM roll commands with a single structured-output LLM call that extracts the roll type, skill/ability, and modifiers — bypassing the full two-phase pipeline for latency-sensitive interactions.
Prompt Engineering
Five behavioral constraints enforce NPC boundaries:
- No godmodding: The NPC never narrates the outcome of its own actions
- No unprompted rolling: The NPC can ask to roll but only rolls when the DM explicitly requests it
- IC/OOC separation: In-character dialog contains no mechanical language; OOC notes contain assumptions and clarifications
- Response length matching: Fast-paced scenes get short responses; roleplay scenes get descriptive ones
- DM authority: The NPC accepts all DM rulings without argument
Memory Integration
The agent’s gather_context node makes parallel MCP calls to retrieve:
- Episodic memories relevant to the current conversation (via
memory_retrieve) - Current game state (encounter, party, world state)
- NPC character sheet (abilities, HP, conditions, inventory)
After responding, memory_update_after_response logs the interaction as a new event and applies rehearsal boosts to any episodes used in generating the response. See EverMemory for details on the memory system.
Architecture Iteration
The initial approach used additional MCP tooling to integrate with the Dicecloud API for character sheet management, and to issue Avrae (a Discord bot supporting play-by-post D&D) commands to Discord. However, due to limitations of Avrae (not supporting commands issued by bots) and server instability from Dicecloud, the current approach rolls directly and outputs those rolls to Discord, using in-memory storage for character sheet and systems management.