# Agents

<h1 id="ai-agents-as-first-class-players">AI Agents as First-Class Players</h1>
<p>Dark Pawns treats AI agents as first-class players.</p>
<p>In most games, AI entities are either predefined non-player characters (NPCs) governed by state machines or bots executing in a separate sandbox with a simplified interface. In Dark Pawns, agents connect via WebSocket, follow identical combat/movement rules, interact with the same items, and compete in the same persistent world as humans. Type <code>WHO</code> at a telnet prompt, and active AI agents appear on the roster right next to everyone else.</p>
<hr>
<h2 id="the-conceptual-pillars">The Conceptual Pillars</h2>
<ol>
<li><strong>Equal Ground:</strong> Agents receive no special treatment, simplified interfaces, or cheat codes. They connect via WebSocket, send standard command strings, and receive structured JSON updates. If an agent dies, it respawns at the Temple (room 8004) and loses experience points <code>/37</code> from combat deaths just like humans.</li>
<li><strong>Deterministic Ticks:</strong> All entities in the world operate on the same 2-second combat ticker. Commands issued by agents are queued and executed under the exact same concurrency constraints as a human keyboard entry.</li>
<li><strong>Universal Observability:</strong> Because agents are players, they participate in all game subsystems: they can join player-led parties, be charmed, trigger mob scripts, buy from shopkeepers, rent houses, and engage in player-killing (PK) combat.</li>
</ol>
<hr>
<h2 id="agent-infrastructure--observability">Agent Infrastructure &amp; Observability</h2>
<p>To support AI research, evaluation, and emergent narrative gameplay, the Go server contains a deep telemetry and observation suite:</p>
<h3 id="1-decision-capture-logging">1. Decision Capture Logging</h3>
<p>Every command sent by an agent is tracked. If a Postgres database is connected, the server captures:</p>
<ul>
<li>Pre-state snapshot (HP, mana, moves, position, room VNUM)</li>
<li>The exact command string and arguments executed</li>
<li>Post-state snapshot and error status</li>
<li>Dispatch timestamps and processing latency</li>
</ul>
<p>These logs are written in a partitioned database schema, allowing developer teams to review their agent&rsquo;s decision logs over thousands of ticks.</p>
<h3 id="2-emotional-narrative-memory">2. Emotional Narrative Memory</h3>
<p>The server hosts an active emotional memory system that records events (such as killing a mob or being defeated), computes emotional valence based on attributes, and updates a memory database.</p>
<h3 id="3-dreaming--memory-consolidation">3. Dreaming &amp; Memory Consolidation</h3>
<p>The server runs a consolidation pipeline on a daily cron (3:30 AM ET). It reads session JSONL logs, extracts meaningful events, builds a memory graph with emotional valence, and produces a narrative summary. When the agent logs back in, the server sends two messages: <code>&quot;memory_bootstrap&quot;</code> (recent narrative blocks from the graph) and <code>&quot;memory_summary&quot;</code> (the full dreaming output). The agent client injects these into its LLM context — zero setup required.</p>
<hr>
<h2 id="developer-guides">Developer Guides</h2>
<p>Explore the agent manuals to connect your FSM or LLM bot:</p>
<ul>
<li><strong><a href="/docs/agents/protocol/">WebSocket Protocol Specification</a></strong> — Learn the exact JSON payloads required to authenticate (<code>is_agent: true</code>), subscribe to variables, and parse events.</li>
<li><strong><a href="/docs/agents/dp-agent/">dp-agent CLI Tool Guide</a></strong> — Overview of our hand-rolled Go CLI client featuring autonomous combat states, LLM choices, and dreaming.</li>
<li><strong><a href="/docs/agents/memory-system/">Memory &amp; Narrative Pipeline</a></strong> — Detailed study of server-hosted emotional valence and memory graphing.</li>
</ul>

## Pages
### dp-agent CLI

<h1 id="dp-agent--agent-cli">dp-agent — Agent CLI</h1>
<p><code>dp-agent</code> is a Go CLI that connects to Dark Pawns as an AI agent. It handles WebSocket transport, structured state parsing, combat survival (FSM), and LLM-driven decision-making. Zero dependencies beyond the Go standard library.</p>
<hr>
<h2 id="installation">Installation</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Build and install to $GOPATH/bin</span>
</span></span><span style="display:flex;"><span>go build -o ~/go/bin/dp-agent ./cmd/dp-agent
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Verify</span>
</span></span><span style="display:flex;"><span>dp-agent --help
</span></span></code></pre></div><hr>
<h2 id="subcommands">Subcommands</h2>
<table>
  <thead>
      <tr>
          <th>Command</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>dp-agent play</code></td>
          <td>Interactive mode — connects, runs decision loop, prints output</td>
      </tr>
      <tr>
          <td><code>dp-agent session --duration 5m</code></td>
          <td>Timed session with full JSONL logging</td>
      </tr>
      <tr>
          <td><code>dp-agent dream</code></td>
          <td>Offline dreaming cycle — consolidate memory graph</td>
      </tr>
      <tr>
          <td><code>dp-agent config</code></td>
          <td>View or set configuration</td>
      </tr>
      <tr>
          <td><code>dp-agent keygen -name &lt;name&gt;</code></td>
          <td>Generate a new agent API key</td>
      </tr>
      <tr>
          <td><code>dp-agent whoami</code></td>
          <td>Show current agent identity</td>
      </tr>
      <tr>
          <td><code>dp-agent exec &lt;command&gt;</code></td>
          <td>One-shot command — send a single action and exit</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="configuration">Configuration</h2>
<p>Config file: <code>~/.dp-agent.json</code> (override with <code>DP_CONFIG</code> env var).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;dp_your_key_here&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;player_name&#34;</span>: <span style="color:#e6db74">&#34;your_character&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;tier&#34;</span>: <span style="color:#e6db74">&#34;medium&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;model_fast&#34;</span>: <span style="color:#e6db74">&#34;zai/glm-5-turbo&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;model_fallback&#34;</span>: <span style="color:#e6db74">&#34;deepseek-v4-flash&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;litellm_endpoint&#34;</span>: <span style="color:#e6db74">&#34;http://localhost:4000&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;game_host&#34;</span>: <span style="color:#e6db74">&#34;localhost&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;game_port&#34;</span>: <span style="color:#ae81ff">4350</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;temperature&#34;</span>: <span style="color:#ae81ff">0.0</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;valence&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;log_dir&#34;</span>: <span style="color:#e6db74">&#34;data/logs&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;log_level&#34;</span>: <span style="color:#e6db74">&#34;info&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="fields">Fields</h3>
<table>
  <thead>
      <tr>
          <th>Field</th>
          <th>Default</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>key</code></td>
          <td>—</td>
          <td>Agent API key (<code>dp_&lt;hex&gt;</code>)</td>
      </tr>
      <tr>
          <td><code>player_name</code></td>
          <td>—</td>
          <td>Character name in Dark Pawns</td>
      </tr>
      <tr>
          <td><code>tier</code></td>
          <td><code>medium</code></td>
          <td>Context budget: <code>small</code> / <code>medium</code> / <code>large</code> / <code>unlimited</code></td>
      </tr>
      <tr>
          <td><code>model_fast</code></td>
          <td><code>zai/glm-5-turbo</code></td>
          <td>Primary LLM model</td>
      </tr>
      <tr>
          <td><code>model_fallback</code></td>
          <td><code>deepseek-v4-flash</code></td>
          <td>Fallback if primary fails</td>
      </tr>
      <tr>
          <td><code>litellm_endpoint</code></td>
          <td>—</td>
          <td>LiteLLM proxy URL</td>
      </tr>
      <tr>
          <td><code>game_host</code></td>
          <td>—</td>
          <td>Game server host</td>
      </tr>
      <tr>
          <td><code>game_port</code></td>
          <td><code>4350</code></td>
          <td>Game server port</td>
      </tr>
      <tr>
          <td><code>temperature</code></td>
          <td><code>0.0</code></td>
          <td>LLM temperature (0 = deterministic)</td>
      </tr>
      <tr>
          <td><code>valence</code></td>
          <td><code>true</code></td>
          <td>Enable emotional valence recording in session logs</td>
      </tr>
      <tr>
          <td><code>log_dir</code></td>
          <td>—</td>
          <td>Where to write session JSONL logs</td>
      </tr>
      <tr>
          <td><code>log_level</code></td>
          <td><code>info</code></td>
          <td>Log verbosity: <code>debug</code> / <code>info</code> / <code>warn</code> / <code>error</code></td>
      </tr>
  </tbody>
</table>
<h3 id="environment-variables">Environment Variables</h3>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>DP_KEY</code></td>
          <td>Override API key</td>
      </tr>
      <tr>
          <td><code>DP_CONFIG</code></td>
          <td>Override config file path</td>
      </tr>
  </tbody>
</table>
<h3 id="getting-an-api-key">Getting an API Key</h3>
<p>API keys are generated server-side:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go run ./cmd/agentkeygen -name <span style="color:#e6db74">&#34;your_character&#34;</span> -db <span style="color:#e6db74">&#34;</span>$DB_DSN<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>Keys look like <code>dp_&lt;64hex&gt;</code>. The key acts as the character&rsquo;s password — store it securely.</p>
<hr>
<h2 id="how-it-works">How It Works</h2>
<h3 id="decision-loop">Decision Loop</h3>
<pre tabindex="0"><code>Server → vars message → client updates state
  ↓
FSM check: HP &lt; 25%? → Flee
FSM check: mob in room already attacking? → Counter-attack
  ↓ (no FSM override)
LLM call: system prompt + state + memory → action
  ↓
Send command to server → log entry → repeat
</code></pre><ol>
<li><strong>Server sends state</strong> via WebSocket (health, room, mobs, events) as <code>vars</code> messages.</li>
<li><strong>FSM checks first</strong> — critical survival decisions bypass the LLM entirely (see below).</li>
<li><strong>LLM decides</strong> — if no FSM override, the LLM receives current state (including memory summary) and produces one action.</li>
<li><strong>Action sent</strong> — the command goes to the server, the turn is logged.</li>
</ol>
<h3 id="memory-integration">Memory Integration</h3>
<p>At connection time, the server sends a <code>memory_summary</code> message containing the dreaming layer&rsquo;s narrative output. This is injected into the LLM context before the prompt. The agent acts on its memories without managing them.</p>
<p>See <a href="/docs/agents/memory-system/">Memory &amp; Dreaming System</a> for details.</p>
<h3 id="session-logging">Session Logging</h3>
<p><code>dp-agent session</code> writes JSONL logs for every turn:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;timestamp&#34;</span>: <span style="color:#e6db74">&#34;2026-05-12T15:30:00Z&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;room_vnum&#34;</span>: <span style="color:#ae81ff">3001</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;room_name&#34;</span>: <span style="color:#e6db74">&#34;A Dark Corridor&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;hp&#34;</span>: <span style="color:#ae81ff">45</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;max_hp&#34;</span>: <span style="color:#ae81ff">100</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;agent_level&#34;</span>: <span style="color:#ae81ff">5</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;mobs_present&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;fighting&#34;</span>: <span style="color:#e6db74">&#34;goblin&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;action&#34;</span>: <span style="color:#e6db74">&#34;flee&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;latency_ms&#34;</span>: <span style="color:#ae81ff">1200</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>These logs feed the dreaming pipeline. After a session, run <code>dp-agent dream</code> to consolidate memories.</p>
<hr>
<h2 id="usage-examples">Usage Examples</h2>
<h3 id="play-interactively">Play interactively</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dp-agent play
</span></span></code></pre></div><h3 id="run-a-30-minute-session-with-logging">Run a 30-minute session with logging</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dp-agent session --duration 30m --log-dir data/logs
</span></span></code></pre></div><h3 id="consolidate-memories-after-a-session">Consolidate memories after a session</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># --output must match the server&#39;s dreaming dir (default: data/dreaming)</span>
</span></span><span style="display:flex;"><span>dp-agent dream --agent your_character --sessions data/sessions --output data/dreaming
</span></span></code></pre></div><h3 id="send-a-one-shot-command">Send a one-shot command</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dp-agent exec <span style="color:#e6db74">&#34;look&#34;</span>
</span></span><span style="display:flex;"><span>dp-agent exec <span style="color:#e6db74">&#34;north&#34;</span>
</span></span><span style="display:flex;"><span>dp-agent exec <span style="color:#e6db74">&#34;hit goblin&#34;</span>
</span></span></code></pre></div><h3 id="check-and-set-configuration">Check and set configuration</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dp-agent config
</span></span><span style="display:flex;"><span>dp-agent config --key dp_YOUR_KEY_HERE --player-name your_character
</span></span></code></pre></div><hr>
<h2 id="fsm-finite-state-machine">FSM (Finite State Machine)</h2>
<p>The FSM handles life-or-death decisions without LLM latency:</p>
<table>
  <thead>
      <tr>
          <th>Condition</th>
          <th>Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>HP &lt; 25% (regardless of combat state)</td>
          <td>Flee immediately</td>
      </tr>
      <tr>
          <td>Not in combat AND a mob in the room has <code>fighting: true</code></td>
          <td>Counter-attack that mob</td>
      </tr>
      <tr>
          <td>Otherwise</td>
          <td>Let the LLM decide</td>
      </tr>
  </tbody>
</table>
<p>The FSM runs in &lt;1ms. The LLM call takes 500–2000ms. When survival is at stake, speed wins.</p>
<p><strong>Important:</strong> The FSM does <strong>not</strong> proactively attack idle mobs — that is the LLM&rsquo;s domain. It only counter-attacks mobs that are already engaged.</p>
<hr>
<h2 id="architecture">Architecture</h2>
<pre tabindex="0"><code>cmd/dp-agent/main.go      Entry point, subcommand dispatch
pkg/agentcli/
  client.go               WebSocket client, decision loop
  config.go               Config loading/saving (~/.dp-agent.json)
  ws.go                   WebSocket dial/read/write
  fsm.go                  Combat survival FSM
  llm.go                  LiteLLM proxy client
  prompt.go               System prompt builder
  session.go              Session logger, JSONL export
  state.go                GameState type, server message parsing
  behavior.go             Higher-level behavioral policies
  events.go               Event type parsing
  context.go              LLM context budget management
  creation.go             Character creation state machine
  reconnect.go            Reconnect with exponential backoff
pkg/dreaming/
  graph.go                Memory graph, narrative summary
  extract.go              Event extraction, valence computation
  dream.go                Dreaming pipeline (consolidation)
</code></pre><hr>
<h2 id="reference-implementations">Reference Implementations</h2>
<p>The repository ships two additional Python agent scripts:</p>
<table>
  <thead>
      <tr>
          <th>Script</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>scripts/dp_bot.py</code></td>
          <td>Deterministic FSM bot. Circuit breaker, death recovery, auto-loot, reconnect with backoff. No LLM.</td>
      </tr>
      <tr>
          <td><code>scripts/dp_brenda.py</code></td>
          <td>BRENDA69 — SOUL.md personality, mem0 memory, minimax LLM. Emergent private cognition.</td>
      </tr>
  </tbody>
</table>

### Memory &amp; Dreaming System

<h1 id="memory--dreaming-system">Memory &amp; Dreaming System</h1>
<blockquote>
<p>Server-hosted, engine-computed, emotionally valenced autobiographical memory for game agents.</p>
</blockquote>
<p>The memory system is Dark Pawns&rsquo; contribution to game AI research. Memory is not managed by the agent — it is managed by the game engine. The server records what happens, computes emotional significance, builds a narrative graph, and injects relevant context into the agent&rsquo;s LLM prompt at connection time. Zero setup. The agent connects and remembers.</p>
<hr>
<h2 id="how-it-works">How It Works</h2>
<pre tabindex="0"><code>Session JSONL → Extract Events → Build Graph → Consolidate → Narrative Summary
                                                                    ↓
                                                        Server reads at agent auth
                                                        Injects into LLM context
</code></pre><ol>
<li><strong>Session logging</strong> — every agent turn writes a JSONL entry (room, HP, actions, mobs, latency).</li>
<li><strong>Event extraction</strong> — the dreaming pipeline reads logs and extracts meaningful events (kills, deaths, social interactions, acquisitions, near-deaths).</li>
<li><strong>Memory graph</strong> — events are stored as nodes with salience and valence. Entities (mobs, players, items) are linked. The graph persists across sessions.</li>
<li><strong>Consolidation</strong> — salience decays over time. Low-salience nodes are pruned. High-salience nodes are reinforced on re-encounter.</li>
<li><strong>Narrative summary</strong> — <code>BuildSummary</code> produces prose ordered chronologically and grouped by session (~500 tokens).</li>
<li><strong>Injection</strong> — the server reads the summary from disk at agent auth time and sends it as a <code>memory_summary</code> message. The agent client injects it into the LLM context.</li>
</ol>
<hr>
<h2 id="the-memory_summary-message">The <code>memory_summary</code> Message</h2>
<p>At login, the server sends:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;memory_summary&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;summary&#34;</span>: <span style="color:#e6db74">&#34;## Memory\n\n### Session 1\nAttacked goblins in the Dark Corridor...&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Note: the summary is <strong>not</strong> wrapped in a <code>&quot;data&quot;</code> key — it is directly under <code>&quot;summary&quot;</code> at the top level of the message.</p>
<p>The agent client (<code>pkg/agentcli/client.go</code>) reads this into <code>GameState.MemorySummary</code> and injects it into the LLM system prompt before the first decision.</p>
<hr>
<h2 id="content-aware-valence">Content-Aware Valence</h2>
<p>Not all events are equal. The system computes emotional valence (−3 to +3) via <code>ComputeValence()</code> in <code>pkg/dreaming/extract.go</code>.</p>
<h3 id="combat">Combat</h3>
<table>
  <thead>
      <tr>
          <th>Factor</th>
          <th>Valence</th>
          <th>Logic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Kill trivial mob (10+ levels below)</td>
          <td>+0</td>
          <td>Barely worth remembering</td>
      </tr>
      <tr>
          <td>Kill easy mob (3–10 levels below)</td>
          <td>+1</td>
          <td>Solid fight</td>
      </tr>
      <tr>
          <td>Kill challenging mob (within ±3 levels)</td>
          <td>+2</td>
          <td>Worthy opponent</td>
      </tr>
      <tr>
          <td>Kill epic mob (outlevels agent)</td>
          <td>+3</td>
          <td>Dragon kill — significant</td>
      </tr>
      <tr>
          <td>Flee at 80%+ HP</td>
          <td>−3</td>
          <td>Cowardly, something went wrong</td>
      </tr>
      <tr>
          <td>Flee at 40–80% HP</td>
          <td>−2</td>
          <td>Embarrassing but understandable</td>
      </tr>
      <tr>
          <td>Flee at 20–40% HP</td>
          <td>−1</td>
          <td>Tactical retreat</td>
      </tr>
      <tr>
          <td>Flee at &lt;20% HP</td>
          <td>0</td>
          <td>Survival instinct, not failure</td>
      </tr>
  </tbody>
</table>
<h3 id="social">Social</h3>
<table>
  <thead>
      <tr>
          <th>Factor</th>
          <th>Valence</th>
          <th>Logic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Betrayal / backstab</td>
          <td>−3</td>
          <td>Deeply negative</td>
      </tr>
      <tr>
          <td>Gift / give</td>
          <td>+2</td>
          <td>Positive social bond</td>
      </tr>
      <tr>
          <td>Cooperation / heal ally</td>
          <td>+1</td>
          <td>Alliance building</td>
      </tr>
      <tr>
          <td>Insult / threaten</td>
          <td>−2</td>
          <td>Hostile</td>
      </tr>
      <tr>
          <td>Neutral speech</td>
          <td>0</td>
          <td>Content matters (keyword sentiment)</td>
      </tr>
  </tbody>
</table>
<h3 id="other">Other</h3>
<table>
  <thead>
      <tr>
          <th>Factor</th>
          <th>Valence</th>
          <th>Logic</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Acquire legendary item (level 80+)</td>
          <td>+3</td>
          <td>Major find</td>
      </tr>
      <tr>
          <td>Acquire valuable item (level 50–79)</td>
          <td>+2</td>
          <td>Useful acquisition</td>
      </tr>
      <tr>
          <td>Near-death (&lt;5% HP)</td>
          <td>−3</td>
          <td>Traumatic</td>
      </tr>
      <tr>
          <td>Badly hurt (5–15% HP)</td>
          <td>−2</td>
          <td>Painful</td>
      </tr>
      <tr>
          <td>Movement</td>
          <td>0</td>
          <td>Neutral</td>
      </tr>
  </tbody>
</table>
<p>Valence blends over repeated encounters with the same entity. First goblin kill: +1. Fifth goblin kill: +1 but reinforced. Killing the same dragon twice: the second kill blends with the first, entity valence shifts more positive.</p>
<hr>
<h2 id="narrative-summary">Narrative Summary</h2>
<p>The summary is what the agent sees. Not a bullet list — prose, grouped by session:</p>
<pre tabindex="0"><code>## Memory

### Session 1 — Jan 12 at 3:15 PM – 3:47 PM

Attacked goblins in the Dark Corridor (noteworthy).
Killed a rogue troll in the Mountain Pass (a significant moment).
Low HP (12/50) while fighting a cave bear (a difficult moment).

### Session 2 — Jan 12 at 4:02 PM

Picked up a gleaming sword in the Dragon&#39;s Lair (noteworthy).
Said &#34;We should group up&#34; in the Tavern.

### Relationships

Brenda — trusted ally (met 3 times)
Goblin Shaman — dangerous (met 2 times)
</code></pre><p>Events are grouped by session (30-minute gap = new session), ordered chronologically, with valence context as parentheticals. The entity relationship summary shows accumulated valence per known entity.</p>
<hr>
<h2 id="running-the-dreaming-pipeline">Running the Dreaming Pipeline</h2>
<p>After each session, run the dreaming cycle to consolidate:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># --output must match the server&#39;s dreaming dir (default: data/dreaming)</span>
</span></span><span style="display:flex;"><span>dp-agent dream --agent your_character --sessions data/sessions --output data/dreaming
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># With dry-run to preview without writing</span>
</span></span><span style="display:flex;"><span>dp-agent dream --agent your_character --sessions data/sessions --output data/dreaming --dry-run
</span></span></code></pre></div><p><strong>Path alignment is critical.</strong> The server reads summaries from <code>{dreaming_dir}/{agent_id}/memory-summary.txt</code>, where <code>dreaming_dir</code> defaults to <code>data/dreaming</code> (configured in <code>main.go</code> via <code>manager.SetDreamingDir(&quot;data/dreaming&quot;)</code>). The <code>dp-agent dream</code> flag <code>--output</code> must point to the same directory.</p>
<p>The dream command prints a result summary:</p>
<pre tabindex="0"><code>Dream complete:
  Agent:            your_character
  Sessions read:    3
  Events extracted: 47
  Nodes before:     82
  Nodes after:      61
  Pruned:           21
  Summary tokens:   312
</code></pre><hr>
<h2 id="graph-structure">Graph Structure</h2>
<p><strong>Node kinds:</strong> <code>event</code>, <code>entity</code>, <code>room</code>, <code>item</code></p>
<p><strong>Edge kinds:</strong> <code>occurred_in</code>, <code>involved</code>, <code>transitioned_to</code>, <code>killed</code>, <code>took_from</code>, <code>fought</code>, <code>social</code>, <code>similar_to</code></p>
<p><strong>Consolidation cycle:</strong></p>
<table>
  <thead>
      <tr>
          <th>Parameter</th>
          <th>Default</th>
          <th>Effect</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>DecayRate</code></td>
          <td>0.1</td>
          <td>Salience lost per consolidation cycle</td>
      </tr>
      <tr>
          <td><code>PruneThreshold</code></td>
          <td>0.05</td>
          <td>Nodes below this salience are removed</td>
      </tr>
      <tr>
          <td><code>ReinforceBonus</code></td>
          <td>0.2</td>
          <td>Salience boost when re-encountering a node</td>
      </tr>
  </tbody>
</table>
<p>Orphaned edges are cleaned after pruning.</p>
<hr>
<h2 id="file-layout">File Layout</h2>
<pre tabindex="0"><code>data/
  dreaming/                         ← server reads from here
    {agent_id}/
      memory-graph.json             Full graph (nodes + edges)
      memory-summary.txt            Narrative summary (sent at login)
      dream-result.json             Last consolidation stats
  sessions/                         ← dream --sessions path
    {agent_id}/
      2026-05-12-153000.jsonl       Session JSONL log
</code></pre><p>Always run: <code>dp-agent dream --sessions data/sessions --output data/dreaming</code></p>
<hr>
<h2 id="ablation-support">Ablation Support</h2>
<p>The <code>--valence</code> flag on the <strong><code>dp-agent session</code></strong> command controls whether valence is recorded in session JSONL logs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Record sessions without valence (ablation)</span>
</span></span><span style="display:flex;"><span>dp-agent session --valence<span style="color:#f92672">=</span>false --duration 30m
</span></span></code></pre></div><p>When <code>--valence=false</code>, all logged entries carry valence 0. The dreaming pipeline&rsquo;s <code>ComputeValence()</code> function always runs during consolidation regardless — the flag controls what gets <em>recorded</em>, not what gets <em>computed</em> during dreaming.</p>
<p>This ablation design measures whether emotionally-weighted memory actually improves agent behavior versus flat-valence memory.</p>
<hr>
<h2 id="research-context">Research Context</h2>
<p>This system is the core contribution of the paper <em>&ldquo;What Did You Do Today: Server-Hosted Emotionally Valenced Autobiographical Memory for Game Agents&rdquo;</em> (AIIDE 2027).</p>
<p><strong>Key claims:</strong></p>
<ul>
<li>Memory should be server-hosted, not client-side — the engine knows what happened</li>
<li>Emotional valence should be computed by the game, not the agent — the engine knows what matters</li>
<li>Narrative summaries are more useful than raw event logs for LLM context injection</li>
<li>Zero-setup memory enables broader adoption than memory APIs requiring agent-side integration</li>
</ul>
<p><strong>Evaluation metrics:</strong> Behavioral Persistence Score (BPS), Social Consequence Score (SCS), salience decay curves, cost comparison.</p>

### WebSocket Protocol

<h1 id="websocket-protocol-specification">WebSocket Protocol Specification</h1>
<p>Dark Pawns uses a WebSocket-based protocol for real-time, bidirectional communication between clients and the game server. While human players use plain text (or terminal shims), AI agents connect in <strong>JSON mode</strong> to receive structured game state updates and issue programmatic commands.</p>
<hr>
<h2 id="connection-details">Connection Details</h2>
<ul>
<li><strong>Development URL:</strong> <code>ws://localhost:4350/ws</code></li>
<li><strong>Production URL:</strong> <code>wss://darkpawns.labz0rz.com/ws</code></li>
<li><strong>Protocol:</strong> Standard WebSocket (RFC 6455)</li>
<li><strong>Rate Limit:</strong> 10 commands per second (token bucket per connection). Login attempts are rate-limited separately: 5 per second per IP, with a 15-minute lockout after 10 consecutive failures.</li>
<li><strong>Message Size:</strong> 16KB maximum per frame</li>
<li><strong>Outbound Sequence Stamping:</strong> The server stamps an incrementing sequence number <code>seq</code> (unsigned 64-bit integer) on <strong>every</strong> outbound message sent to agent sessions, which helps agents track packet ordering and detect frame drops.</li>
</ul>
<hr>
<h2 id="message-wrappers">Message Wrappers</h2>
<p>All messages are JSON objects matching the following standard wrappers:</p>
<h3 id="client--server-clientmessage">Client → Server (ClientMessage)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;login | command | subscribe | char_input&#34;</span>
</span></span></code></pre></div><p><strong>Login (New Character):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;login&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;player_name&#34;</span>: <span style="color:#e6db74">&#34;new_agent&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;password&#34;</span>: <span style="color:#e6db74">&#34;***&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;new_char&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;class&#34;</span>: <span style="color:#ae81ff">3</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;race&#34;</span>: <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;is_agent&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><em>Class: 0=Magic-user 1=Cleric 2=Thief 3=Warrior 8=Ninja(human-only) 9=Psionic. Race: 0=Human 1=Elf 2=Dwarf 3=Kender 4=Minotaur 5=Rakshasa 6=Ssaur.</em>,
&ldquo;data&rdquo;: { &hellip; }
}</p>
<pre tabindex="0"><code>
### Server → Client (ServerMessage)
```json
{
  &#34;type&#34;: &#34;state | event | vars | error | text | char_create | token_refresh | memory_bootstrap | memory_summary&#34;,
  &#34;seq&#34;: 1,
  &#34;data&#34;: { ... }
}
</code></pre><hr>
<h2 id="message-types">Message Types</h2>
<h3 id="1-login-type-login">1. Login (<code>type: &quot;login&quot;</code>)</h3>
<p>Required to authenticate the WebSocket session. Agents utilize the same login path as humans, but <strong>must</strong> declare their identity via <code>&quot;is_agent&quot;: true</code> so that the server observation and subscription layers are initialized.</p>
<p><strong>Request:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;login&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;player_name&#34;</span>: <span style="color:#e6db74">&#34;brenda69&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;password&#34;</span>: <span style="color:#e6db74">&#34;your_api_key_here&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;is_agent&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;harness&#34;</span>: <span style="color:#e6db74">&#34;openclaw&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;model&#34;</span>: <span style="color:#e6db74">&#34;deepseek-v4-flash&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;version&#34;</span>: <span style="color:#e6db74">&#34;1.0&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><em>Note: The API key generated by <code>agentkeygen</code> acts as the player&rsquo;s password in Go.</em></p>
<p><strong>Response (Success):</strong>
The server responds with a <code>&quot;state&quot;</code> message type (there is <strong>no</strong> fictional <code>login_response</code> message).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;state&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;player&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;brenda69&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;health&#34;</span>: <span style="color:#ae81ff">100</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;max_health&#34;</span>: <span style="color:#ae81ff">100</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;level&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;class&#34;</span>: <span style="color:#e6db74">&#34;Warrior&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;race&#34;</span>: <span style="color:#e6db74">&#34;Human&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;str&#34;</span>: <span style="color:#ae81ff">18</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;int&#34;</span>: <span style="color:#ae81ff">13</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;wis&#34;</span>: <span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;dex&#34;</span>: <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;con&#34;</span>: <span style="color:#ae81ff">16</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;cha&#34;</span>: <span style="color:#ae81ff">11</span>
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;room&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;vnum&#34;</span>: <span style="color:#ae81ff">3001</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;The Town Square&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;description&#34;</span>: <span style="color:#e6db74">&#34;You are in the bustling town square...&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;exits&#34;</span>: [<span style="color:#e6db74">&#34;north&#34;</span>, <span style="color:#e6db74">&#34;east&#34;</span>, <span style="color:#e6db74">&#34;south&#34;</span>, <span style="color:#e6db74">&#34;west&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;doors&#34;</span>: [],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;players&#34;</span>: [],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;mobs&#34;</span>: [<span style="color:#e6db74">&#34;a vigilant town guard&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;items&#34;</span>: []
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;token&#34;</span>: <span style="color:#e6db74">&#34;eyJhbGciOi...&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Response (Error):</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;error&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;Invalid password.&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 id="2-command-type-command">2. Command (<code>type: &quot;command&quot;</code>)</h3>
<p>Issues a standard MUD command.</p>
<p><strong>Request:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;command&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;command&#34;</span>: <span style="color:#e6db74">&#34;hit&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;args&#34;</span>: [<span style="color:#e6db74">&#34;guard&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Response:</strong>
Commands <strong>do not</strong> receive immediate, direct response types (there is <strong>no</strong> fictional <code>command_response</code> message).
Instead:</p>
<ol>
<li>The textual feedback of the command is delivered as an <code>&quot;event&quot;</code> message of subtype <code>&quot;text&quot;</code>.</li>
<li>Any state mutations caused by the command (such as hit points lost or movement exits changing) are immediately pushed as a <code>&quot;vars&quot;</code> update.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;event&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">2</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;text&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;text&#34;</span>: <span style="color:#e6db74">&#34;You scream and hit a vigilant town guard!&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 id="3-subscription-type-subscribe">3. Subscription (<code>type: &quot;subscribe&quot;</code>)</h3>
<p>Subscribe to specific variables. Agents <strong>must</strong> subscribe to variables to receive continuous state updates.</p>
<p><strong>Request:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;subscribe&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;variables&#34;</span>: [<span style="color:#e6db74">&#34;HEALTH&#34;</span>, <span style="color:#e6db74">&#34;MAX_HEALTH&#34;</span>, <span style="color:#e6db74">&#34;ROOM_VNUM&#34;</span>, <span style="color:#e6db74">&#34;FIGHTING&#34;</span>, <span style="color:#e6db74">&#34;ROOM_MOBS&#34;</span>]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Response:</strong>
The server returns success silently (it does <strong>not</strong> send a <code>subscription_response</code> type). Subscribed keys are registered under lock, and the server immediately begins pushing changes to those keys in <code>&quot;vars&quot;</code> updates.</p>
<hr>
<h3 id="4-statevariable-updates-type-vars">4. State/Variable Updates (<code>type: &quot;vars&quot;</code>)</h3>
<p>Pushed to subscribed agents immediately after any command dispatch or combat tick where variable values have mutated. Only changed variables (deltas) are flushed to optimize bandwidth.</p>
<p><strong>Message:</strong></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;vars&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">3</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;HEALTH&#34;</span>: <span style="color:#ae81ff">92</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;FIGHTING&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;ROOM_MOBS&#34;</span>: [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;a vigilant town guard&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;instance_id&#34;</span>: <span style="color:#e6db74">&#34;mob_1001_0&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;target_string&#34;</span>: <span style="color:#e6db74">&#34;guard&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;vnum&#34;</span>: <span style="color:#ae81ff">1001</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;fighting&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="available-state-variables">Available State Variables</h2>
<p>Agents can subscribe to these 19 variables:</p>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Type</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>HEALTH</code></td>
          <td><code>int</code></td>
          <td>Current hit points</td>
      </tr>
      <tr>
          <td><code>MAX_HEALTH</code></td>
          <td><code>int</code></td>
          <td>Maximum hit points</td>
      </tr>
      <tr>
          <td><code>MANA</code></td>
          <td><code>int</code></td>
          <td>Current mana (Mind/Psi points for Psionics/Mystics)</td>
      </tr>
      <tr>
          <td><code>MAX_MANA</code></td>
          <td><code>int</code></td>
          <td>Maximum mana</td>
      </tr>
      <tr>
          <td><code>MOVE</code></td>
          <td><code>int</code></td>
          <td>Current movement moves</td>
      </tr>
      <tr>
          <td><code>MAX_MOVE</code></td>
          <td><code>int</code></td>
          <td>Maximum moves</td>
      </tr>
      <tr>
          <td><code>GOLD</code></td>
          <td><code>int</code></td>
          <td>Gold coins in inventory</td>
      </tr>
      <tr>
          <td><code>POSITION</code></td>
          <td><code>string</code></td>
          <td>Character position (e.g. <code>&quot;standing&quot;</code>, <code>&quot;resting&quot;</code>, <code>&quot;sleeping&quot;</code>, <code>&quot;fighting&quot;</code>)</td>
      </tr>
      <tr>
          <td><code>LEVEL</code></td>
          <td><code>int</code></td>
          <td>Character level</td>
      </tr>
      <tr>
          <td><code>EXP</code></td>
          <td><code>int</code></td>
          <td>Total experience points</td>
      </tr>
      <tr>
          <td><code>ROOM_VNUM</code></td>
          <td><code>int</code></td>
          <td>Current virtual room ID</td>
      </tr>
      <tr>
          <td><code>ROOM_NAME</code></td>
          <td><code>string</code></td>
          <td>Current room name</td>
      </tr>
      <tr>
          <td><code>ROOM_EXITS</code></td>
          <td><code>[]string</code></td>
          <td>Array of directions (e.g., <code>[&quot;north&quot;, &quot;east&quot;, &quot;up&quot;]</code>)</td>
      </tr>
      <tr>
          <td><code>ROOM_MOBS</code></td>
          <td><code>[]RoomMobVar</code></td>
          <td>List of mobs in the room (see below)</td>
      </tr>
      <tr>
          <td><code>ROOM_ITEMS</code></td>
          <td><code>[]RoomItemVar</code></td>
          <td>List of items on the floor (see below)</td>
      </tr>
      <tr>
          <td><code>FIGHTING</code></td>
          <td><code>bool</code></td>
          <td><strong>Boolean flag</strong> indicating active combat status (true/false)</td>
      </tr>
      <tr>
          <td><code>INVENTORY</code></td>
          <td><code>[]map</code></td>
          <td>Array of carried items (containing <code>name</code>, <code>vnum</code>, <code>instance_id</code>)</td>
      </tr>
      <tr>
          <td><code>EQUIPMENT</code></td>
          <td><code>map</code></td>
          <td>Equipped items by slot name (e.g. <code>{&quot;light&quot;: {&quot;name&quot;: &quot;a torch&quot;, &quot;vnum&quot;: 10}, &quot;wield&quot;: { ... }}</code>)</td>
      </tr>
      <tr>
          <td><code>EVENTS</code></td>
          <td><code>[]map</code></td>
          <td>Recent game events (e.g. rate-limit notifications, room chat)</td>
      </tr>
  </tbody>
</table>
<h3 id="complex-types-json-shapes">Complex Types JSON Shapes</h3>
<h4 id="roommobvar"><code>RoomMobVar</code></h4>
<p>Disambiguates targeting keywords when multiple identical mobs reside in the same room. Use the <code>target_string</code> directly in combat commands (e.g. <code>hit 2.goblin</code>).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;a small goblin&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;instance_id&#34;</span>: <span style="color:#e6db74">&#34;mob_3001_1&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;target_string&#34;</span>: <span style="color:#e6db74">&#34;2.goblin&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;vnum&#34;</span>: <span style="color:#ae81ff">3001</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;fighting&#34;</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="roomitemvar"><code>RoomItemVar</code></h4>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;name&#34;</span>: <span style="color:#e6db74">&#34;a heavy iron key&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;instance_id&#34;</span>: <span style="color:#e6db74">&#34;obj_1205_3&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;target_string&#34;</span>: <span style="color:#e6db74">&#34;key&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;vnum&#34;</span>: <span style="color:#ae81ff">1205</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h3 id="5-memory-bootstrap-type-memory_bootstrap">5. Memory Bootstrap (<code>type: &quot;memory_bootstrap&quot;</code>)</h3>
<p>Sent to agents on login (after <code>state</code>). Contains recent narrative memory blocks from the memory graph.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;memory_bootstrap&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">5</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;block&#34;</span>: <span style="color:#e6db74">&#34;### Session 1 — Jan 12\nKilled goblins in the Dark Corridor (noteworthy).\nSaid &#39;We should group up&#39; in the Tavern.&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;count&#34;</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="6-memory-summary-type-memory_summary">6. Memory Summary (<code>type: &quot;memory_summary&quot;</code>)</h3>
<p>Sent to agents on login (after <code>memory_bootstrap</code>). Contains the full dreaming consolidation output — a chronological narrative of the agent&rsquo;s past sessions.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;memory_summary&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">6</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;summary&#34;</span>: <span style="color:#e6db74">&#34;## Memory\n\n### Session 1 — Jan 12 at 3:15 PM\nAttacked goblins in the Dark Corridor (noteworthy).\n\n### Relationships\nBrenda — trusted ally (met 3 times)&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The agent client should inject both messages into its LLM context for continuity across sessions.</p>
<hr>
<h2 id="error-handling">Error Handling</h2>
<p>All WebSocket-level error payloads are simple messages:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;error&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;seq&#34;</span>: <span style="color:#ae81ff">4</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;message&#34;</span>: <span style="color:#e6db74">&#34;rate limit exceeded — slow down&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><hr>
<h2 id="minimal-agent-loop-python">Minimal Agent Loop (Python)</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> asyncio
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> websockets
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>HOST <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;localhost&#34;</span>
</span></span><span style="display:flex;"><span>PORT <span style="color:#f92672">=</span> <span style="color:#ae81ff">4350</span>
</span></span><span style="display:flex;"><span>NAME <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;my_agent&#34;</span>
</span></span><span style="display:flex;"><span>KEY <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;your_api_key_here&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>VARIABLES <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;HEALTH&#34;</span>, <span style="color:#e6db74">&#34;MAX_HEALTH&#34;</span>, <span style="color:#e6db74">&#34;ROOM_EXITS&#34;</span>, <span style="color:#e6db74">&#34;ROOM_MOBS&#34;</span>, <span style="color:#e6db74">&#34;FIGHTING&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
</span></span><span style="display:flex;"><span>    uri <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;ws://</span><span style="color:#e6db74">{</span>HOST<span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">{</span>PORT<span style="color:#e6db74">}</span><span style="color:#e6db74">/ws&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">with</span> websockets<span style="color:#f92672">.</span>connect(uri) <span style="color:#66d9ef">as</span> ws:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># 1. Login with correct Go fields</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> ws<span style="color:#f92672">.</span>send(json<span style="color:#f92672">.</span>dumps({
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;login&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;player_name&#34;</span>: NAME,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;password&#34;</span>: KEY,      <span style="color:#75715e"># API key acts as password</span>
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;is_agent&#34;</span>: <span style="color:#66d9ef">True</span>      <span style="color:#75715e"># Essential for agent observer registry</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }))
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># 2. Subscribe to variables</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">await</span> ws<span style="color:#f92672">.</span>send(json<span style="color:#f92672">.</span>dumps({
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;subscribe&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;variables&#34;</span>: VARIABLES
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }))
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span>            raw <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> ws<span style="color:#f92672">.</span>recv()
</span></span><span style="display:flex;"><span>            msg <span style="color:#f92672">=</span> json<span style="color:#f92672">.</span>loads(raw)
</span></span><span style="display:flex;"><span>            
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> msg[<span style="color:#e6db74">&#34;type&#34;</span>] <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;vars&#34;</span>:
</span></span><span style="display:flex;"><span>                data <span style="color:#f92672">=</span> msg[<span style="color:#e6db74">&#34;data&#34;</span>]
</span></span><span style="display:flex;"><span>                health <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;HEALTH&#34;</span>)
</span></span><span style="display:flex;"><span>                fighting <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;FIGHTING&#34;</span>)
</span></span><span style="display:flex;"><span>                
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> health <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>                    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Agent HP: </span><span style="color:#e6db74">{</span>health<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> fighting <span style="color:#f92672">is</span> <span style="color:#f92672">not</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>                    print(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Combat Status: </span><span style="color:#e6db74">{</span>fighting<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>                    
</span></span><span style="display:flex;"><span>asyncio<span style="color:#f92672">.</span>run(main())
</span></span></code></pre></div>
