Skip to content

NPC Memory & Relationships — Implementation Plan

Design Document: docs/designs/v3.1/03-npc-memory-relationships.md Status: Draft Target: v3.1

Summary

This plan implements the NPC Memory & Relationships system described in the design document. The system consists of three subsystems:

  1. Memory System — Episodic memory for NPCs, extracted from dialogue via LLM (semantic/procedural deferred to Phase 2+)
  2. Relationship System — Relationship tracking across three dimensions (trust, respect, friendliness)
  3. Knowledge Graph — NPC-to-NPC gossip propagation and knowledge sharing (Phase 2)

The implementation is split across three packages following existing layering rules:

  • maid-engine: Protocols, infrastructure (ContextProvider, TokenBudgetManager, MemoryCache, PIIRedactor)
  • maid-stdlib: Game-domain implementations (memory/, relationships/, knowledge/ bounded contexts)
  • maid-classic-rpg: Integration into NPCDialogueSystem via EnrichedPromptBuilder

Hard Dependency: Doc 01 (Durable Persistence) must land first — all persistence uses DocumentStore.

Feature Flag: MAID_MEMORY__ENABLED (default false) controls whether the memory system is active. When disabled, EnrichedPromptBuilder passes through to PromptBuilder directly with {reputation} set to "neutral".

Migration Note: Existing conversations will have no memories. The system gracefully handles NPCs with zero memory state — no backfill is planned for v3.1.


Phase 1: Core Memory & Relationships (6 weeks)

1.1 Context Provider Protocol & Infrastructure (maid-engine)

These are the foundational abstractions that all context providers implement. They live in maid-engine because they are game-content-agnostic.

File: packages/maid-engine/src/maid_engine/ai/context_providers.py

  • [ ] Define ContextSection dataclass (content: str, token_count: int, priority: float, source: str, metadata: dict)
  • [ ] Define ContextProvider Protocol with async get_context(npc_id, player_id, token_budget) -> ContextSection | None
  • [ ] Define ContextOrchestrator class:
  • [ ] register_provider(name, provider, priority, default_budget_pct) method
  • [ ] async gather_context(npc_id, player_id, total_budget) method using asyncio.gather() with 40ms per-provider timeout
  • [ ] Sort returned sections by priority, trim to total token budget
  • [ ] Log warnings for providers that timeout or error (do not fail the whole gather)

File: packages/maid-engine/src/maid_engine/ai/token_budget.py

  • [ ] Define TokenBudgetManager class:
  • [ ] Constructor accepts total budget (int) and provider allocations (dict[str, float] as percentages)
  • [ ] allocate(provider_name) -> int returns token count for a provider
  • [ ] reallocate(used: dict[str, int]) redistributes unused budget from providers that used less
  • [ ] Uses estimate_tokens() from tokens.py for counting

File: packages/maid-engine/src/maid_engine/ai/cache.py

  • [ ] Define MemoryCache class:
  • [ ] LRU cache keyed by (npc_id, player_id) tuple
  • [ ] Configurable max entries (default 1000) and TTL (default 300s)
  • [ ] async get(npc_id, player_id) -> CachedContext | None
  • [ ] async set(npc_id, player_id, sections: list[ContextSection])
  • [ ] invalidate(npc_id, player_id) and invalidate_npc(npc_id) methods
  • [ ] Thread-safe via asyncio.Lock

File: packages/maid-engine/src/maid_engine/ai/pii.py

  • [ ] Define PIIRedactor class:
  • [ ] Regex-based detection for emails, IPs, phone numbers, URLs with auth tokens
  • [ ] redact(text: str) -> tuple[str, list[PIIMatch]] returns cleaned text + matches for audit
  • [ ] Configurable patterns via constructor
  • [ ] Applied before memory storage, not on prompt output

Dependencies: None (pure infrastructure).

Tests:

File: packages/maid-engine/tests/ai/test_context_providers.py

  • [ ] Test ContextOrchestrator gathers from multiple providers in parallel
  • [ ] Test ContextOrchestrator respects per-provider timeout (40ms)
  • [ ] Test ContextOrchestrator handles provider errors gracefully (returns other results)
  • [ ] Test ContextOrchestrator sorts by priority and trims to budget
  • [ ] Test TokenBudgetManager allocates correct percentages
  • [ ] Test TokenBudgetManager reallocates unused budget

File: packages/maid-engine/tests/ai/test_cache.py

  • [ ] Test MemoryCache get/set round-trip
  • [ ] Test MemoryCache TTL expiry
  • [ ] Test MemoryCache LRU eviction at max entries
  • [ ] Test MemoryCache invalidate by (npc_id, player_id) and by npc_id

File: packages/maid-engine/tests/ai/test_pii.py

  • [ ] Test PIIRedactor strips emails, IPs, phone numbers
  • [ ] Test PIIRedactor returns PIIMatch list for audit logging
  • [ ] Test PIIRedactor leaves clean text unchanged

1.2 Memory Data Models (maid-stdlib)

Define the Pydantic models for episodic memory. Semantic and procedural memory types are deferred to Phase 2+ (no system produces or consumes them in Phase 1).

File: packages/maid-stdlib/src/maid_stdlib/memory/__init__.py

  • [ ] Create package with public exports

File: packages/maid-stdlib/src/maid_stdlib/memory/models.py

  • [ ] Define MemoryType enum: EPISODIC, SEMANTIC, PROCEDURAL (enum defined for forward compatibility; only EPISODIC used in Phase 1)
  • [ ] Define MemoryImportance enum: TRIVIAL, LOW, MEDIUM, HIGH, CRITICAL
  • [ ] Define EpisodicMemory (MAIDBaseModel):
  • [ ] Fields: memory_id (UUID), npc_id (str), player_id (str), memory_type (MemoryType, default EPISODIC), content (str), importance (MemoryImportance), tags (list[str]), created_at (datetime), last_accessed (datetime), access_count (int), decay_factor (float, default 1.0), source_conversation_id (str | None), version (int, default 1), emotional_valence (float, -1.0 to 1.0), location (str | None), participants (list[str])
  • [ ] Define Memory as type alias for EpisodicMemory (will become union type when semantic/procedural added)
  • [ ] Define MemoryQueryResult dataclass: memory (Memory), relevance_score (float)

Dependencies: maid_stdlib.models.base.MAIDBaseModel

Tests:

File: packages/maid-stdlib/tests/memory/test_models.py

  • [ ] Test EpisodicMemory constructs with valid defaults
  • [ ] Test memory_type defaults to EPISODIC
  • [ ] Test importance enum ordering
  • [ ] Test serialization round-trip (model_dump / model_validate)
  • [ ] Test decay_factor clamping (0.0–1.0)

1.3 Memory Storage & Service (maid-stdlib)

The MemoryService manages CRUD operations and growth bounds for memories, backed by DocumentStore.

Prerequisite: The current Conversation class has no conversation_id field. Add id: UUID = field(default_factory=uuid4) to Conversation in maid-engine for extraction idempotency.

File: packages/maid-stdlib/src/maid_stdlib/memory/service.py

  • [ ] Define MemoryService class:
  • [ ] Constructor takes DocumentStore, collection name "npc_memories"
  • [ ] async store(memory: Memory) -> str — stores with PIIRedactor pass, returns memory_id
  • [ ] async get(memory_id: str) -> Memory | None — retrieves and updates last_accessed/access_count
  • [ ] async query(npc_id: str, player_id: str, memory_type: MemoryType | None, tags: list[str] | None, limit: int = 50) -> list[Memory] — filtered retrieval
  • [ ] async delete(memory_id: str) -> bool
  • [ ] async count(npc_id: str, player_id: str, memory_type: MemoryType | None) -> int
  • [ ] async enforce_bounds(npc_id: str, player_id: str) — enforces growth limit (200 episodic memories per NPC-player pair); evicts lowest importance + highest decay memories first
  • [ ] Register npc_memories collection schema in StdlibContentPack.register_document_schemas()

Note: DocumentStore has no batch_update() or batch_delete(). All operations use individual create()/update()/delete() calls. With a cap of 200 memories per pair, individual operations are fast enough.

Dependencies: 1.1 (PIIRedactor), 1.2 (Memory models), Doc 01 (DocumentStore)

Tests:

File: packages/maid-stdlib/tests/memory/test_service.py

  • [ ] Test store and retrieve round-trip
  • [ ] Test query filters by npc_id, player_id, memory_type, tags
  • [ ] Test enforce_bounds evicts lowest priority memories when over limit
  • [ ] Test enforce_bounds respects per-type limit (200 episodic)
  • [ ] Test delete removes memory
  • [ ] Test get updates last_accessed and access_count
  • [ ] Test store applies PIIRedactor to content

1.4 Memory Extraction Pipeline (maid-stdlib)

Async extraction pipeline that processes completed conversations to produce memories. This is the write path — it must never block dialogue.

File: packages/maid-stdlib/src/maid_stdlib/memory/extraction.py

  • [ ] Define ExtractionJob dataclass: conversation_id (str), npc_id (str), player_id (str), messages (list[ConversationMessage]), npc_context (dict), created_at (datetime)
  • [ ] Define ExtractionResult dataclass: memories (list[Memory]), relationship_updates (list[RelationshipDelta]), extraction_time_ms (float), tokens_used (int)
  • [ ] Define ExtractionJobQueue class:
  • [ ] Bounded asyncio.Queue (maxsize=100, configurable)
  • [ ] async submit(job: ExtractionJob) -> bool — returns False if queue full (log + drop)
  • [ ] async process_loop() — background worker consuming jobs, one at a time
  • [ ] shutdown() — drain queue, stop worker
  • [ ] Startup/shutdown hooks integrated with ECS lifecycle (register in System.startup()/System.shutdown())
  • [ ] Data loss note: In-memory queue; pending jobs are lost on crash. Acceptable for v3.1 — document this limitation.

Extraction triggers: - Conversation end (end_conversation()) - Working buffer reaches capacity (e.g., 20 exchanges) - Conversation stale-cleaned by ConversationManager timeout - Server shutdown (drain queue before stopping)

  • [ ] Define MemoryExtractor class:
  • [ ] Constructor takes LLMProvider, MemoryService, CircuitBreakerRegistry
  • [ ] async extract(job: ExtractionJob) -> ExtractionResult — sends conversation to LLM with extraction prompt, parses structured response into Memory objects
  • [ ] Uses circuit breaker named "memory_extractor" to guard LLM calls
  • [ ] Logs extraction metrics (time, token count, memory count)

File: packages/maid-stdlib/src/maid_stdlib/memory/prompts.py

  • [ ] Define extraction prompt template (system prompt instructing LLM to extract episodic memories from conversation)
  • [ ] Prompt output format: JSON array of memory objects with type, content, importance, tags, emotional_valence
  • [ ] Include few-shot examples in prompt for reliable structured output
  • [ ] Optionally extract KnowledgeEntry candidates for world-relevant facts (e.g., "player said the eastern road is blocked"); these are stored separately in Phase 2 but the extraction format should accommodate them now

Dependencies: 1.2 (Memory models), 1.3 (MemoryService), existing LLMProvider, CircuitBreakerRegistry

Tests:

File: packages/maid-stdlib/tests/memory/test_extraction.py

  • [ ] Test ExtractionJobQueue submit and process round-trip
  • [ ] Test ExtractionJobQueue drops jobs when full (returns False)
  • [ ] Test MemoryExtractor parses valid LLM response into Memory objects
  • [ ] Test MemoryExtractor handles malformed LLM response gracefully (empty result, not crash)
  • [ ] Test MemoryExtractor uses circuit breaker (trips after failures)
  • [ ] Test ExtractionJobQueue shutdown drains pending jobs

File: packages/maid-stdlib/tests/memory/test_prompts.py

  • [ ] Test extraction prompt includes conversation messages
  • [ ] Test extraction prompt output parses as valid JSON schema

File: packages/maid-stdlib/tests/fixtures/memory_extraction/

  • [ ] Create 10–15 representative conversation fixtures (shopkeeper trade, quest dialogue, combat encounter, small talk, etc.) with expected extraction outputs
  • [ ] Test extraction prompt parsing against fixtures with mocked LLM responses
  • [ ] Assert extracted memories match expected tags, importance ranges, and content patterns

1.5 Memory Working Buffer (maid-stdlib)

Lightweight in-memory-only buffer for tracking exchanges in the current conversation. This is a session buffer — it does NOT persist to DocumentStore. ConversationManager already provides conversation persistence and stale cleanup; this buffer serves only to track exchange count for triggering mid-conversation extraction.

File: packages/maid-stdlib/src/maid_stdlib/memory/working_buffer.py

  • [ ] Define WorkingBuffer class:
  • [ ] Stores in-progress conversation context as lightweight BufferEntry objects
  • [ ] add_exchange(player_msg: str, npc_response: str, timestamp: datetime) — appends exchange
  • [ ] get_recent(n: int) -> list[BufferEntry] — returns last N exchanges
  • [ ] get_summary() -> str — returns concatenated summary for extraction prompt
  • [ ] clear() — resets buffer
  • [ ] Max entries configurable (default 20), triggers extraction submission when full
  • [ ] Define WorkingBufferManager class:
  • [ ] Manages per-conversation WorkingBuffer instances keyed by (npc_id, player_id)
  • [ ] get_or_create(npc_id, player_id) -> WorkingBuffer
  • [ ] remove(npc_id, player_id) — cleanup on conversation end

Dependencies: None

Tests:

File: packages/maid-stdlib/tests/memory/test_working_buffer.py

  • [ ] Test add_exchange and get_recent
  • [ ] Test max entries triggers extraction callback
  • [ ] Test get_summary concatenation
  • [ ] Test WorkingBufferManager get_or_create returns same buffer for same key
  • [ ] Test WorkingBufferManager remove cleans up

1.6 Memory Context Provider (maid-stdlib)

Implements the ContextProvider protocol to inject relevant memories into NPC prompts.

File: packages/maid-stdlib/src/maid_stdlib/memory/context_provider.py

  • [ ] Define MemoryContextProvider implementing ContextProvider:
  • [ ] Constructor takes MemoryService, MemoryCache
  • [ ] async get_context(npc_id, player_id, token_budget) -> ContextSection | None:
    • [ ] Check cache first; on miss, query MemoryService
    • [ ] Score memories by relevance: importance * decay_factor * recency_weight
    • [ ] Select top-N memories that fit within token_budget
    • [ ] Format as natural-language summary (not raw JSON)
    • [ ] Cache result, return ContextSection with source="memory"

Dependencies: 1.1 (ContextProvider protocol, MemoryCache), 1.3 (MemoryService)

Tests:

File: packages/maid-stdlib/tests/memory/test_context_provider.py

  • [ ] Test returns None when no memories exist
  • [ ] Test returns formatted context section with memories
  • [ ] Test respects token budget (truncates to fit)
  • [ ] Test uses cache on second call
  • [ ] Test relevance scoring ranks high-importance recent memories first

1.7 Relationship Data Models & Manager (maid-stdlib)

Relationship tracking between NPCs and players across three core dimensions.

File: packages/maid-stdlib/src/maid_stdlib/relationships/__init__.py

  • [ ] Create package with public exports

File: packages/maid-stdlib/src/maid_stdlib/relationships/models.py

  • [ ] Define RelationshipDimension enum: TRUST, RESPECT, FRIENDLINESS (covers "will they help me", "do they defer to me", "do they like me" — additional dimensions like FEAR, LOYALTY can be added in Phase 2)
  • [ ] Define RelationshipTier enum: HOSTILE, UNFRIENDLY, NEUTRAL, FRIENDLY, ALLIED, DEVOTED
  • [ ] Define RelationshipState (MAIDBaseModel):
  • [ ] Fields: npc_id (str), player_id (str), dimensions (dict[RelationshipDimension, float] — each -100.0 to 100.0), tier (RelationshipTier), interaction_count (int), last_interaction (datetime), version (int, default 1), history (list[RelationshipEvent])
  • [ ] Computed property overall_disposition -> float — weighted average of dimensions
  • [ ] Method get_tier() -> RelationshipTier — maps overall_disposition to tier thresholds
  • [ ] Define RelationshipDelta dataclass: dimension (RelationshipDimension), change (float), reason (str)
  • [ ] Define RelationshipEvent (MAIDBaseModel): timestamp (datetime), deltas (list[RelationshipDelta]), source (str — e.g. "dialogue", "quest", "gossip")
  • [ ] Define tier thresholds as constants: HOSTILE < -60, UNFRIENDLY < -20, NEUTRAL < 20, FRIENDLY < 50, ALLIED < 80, DEVOTED >= 80

File: packages/maid-stdlib/src/maid_stdlib/relationships/manager.py

  • [ ] Define RelationshipManager class:
  • [ ] Constructor takes DocumentStore, collection name "npc_relationships"
  • [ ] async get(npc_id, player_id) -> RelationshipState — returns existing or creates default (all dimensions 0.0, NEUTRAL)
  • [ ] async update(npc_id, player_id, deltas: list[RelationshipDelta]) -> RelationshipState:
    • [ ] Load current state, apply deltas (clamp to -100/+100), recalculate tier
    • [ ] Optimistic concurrency: check version, retry once on conflict
    • [ ] Append RelationshipEvent to history
    • [ ] Return updated state
  • [ ] async get_all_for_npc(npc_id) -> list[RelationshipState] — for gossip system
  • [ ] async get_all_for_player(player_id) -> list[RelationshipState] — for player profile
  • [ ] Detect tier transitions — return old_tier and new_tier when tier changes
  • [ ] compute_deltas_from_memories(memories: list[Memory]) -> list[RelationshipDelta] — tag-to-dimension mapping (replaces standalone RelationshipEvaluator):
    • [ ] Tag-to-dimension mapping (configurable dict, extensible by content packs via register_tag_mapping()):
    • [ ] "helpful" → TRUST +5, FRIENDLINESS +3
    • [ ] "hostile" → TRUST -8, FRIENDLINESS -5
    • [ ] "respectful" → RESPECT +4
    • [ ] "deceptive" → TRUST -10
    • [ ] "generous" → FRIENDLINESS +5, TRUST +2
    • [ ] Scale delta by memory importance (CRITICAL = 2x, HIGH = 1.5x, MEDIUM = 1x, LOW = 0.5x, TRIVIAL = 0.25x)
  • [ ] Register npc_relationships collection schema in StdlibContentPack.register_document_schemas()

Dependencies: 1.2 (models depend on MAIDBaseModel), Doc 01 (DocumentStore)

Tests:

File: packages/maid-stdlib/tests/relationships/test_models.py

  • [ ] Test RelationshipState dimension clamping (-100 to 100)
  • [ ] Test overall_disposition weighted calculation
  • [ ] Test get_tier maps to correct tier at each threshold boundary
  • [ ] Test RelationshipDelta applies correctly

File: packages/maid-stdlib/tests/relationships/test_manager.py

  • [ ] Test get creates default NEUTRAL state for new NPC-player pair
  • [ ] Test update applies deltas and persists
  • [ ] Test update clamps dimensions to -100/+100
  • [ ] Test update detects tier transition (returns old/new tier)
  • [ ] Test optimistic concurrency retry on version conflict
  • [ ] Test get_all_for_npc returns all player relationships
  • [ ] Test compute_deltas_from_memories maps tags to correct deltas
  • [ ] Test importance scaling (CRITICAL = 2x multiplier)
  • [ ] Test unknown tags produce no deltas
  • [ ] Test custom tag registration via register_tag_mapping()

1.8 Relationship Context Provider (maid-stdlib)

Implements ContextProvider to inject relationship state into NPC prompts.

File: packages/maid-stdlib/src/maid_stdlib/relationships/context_provider.py

  • [ ] Define RelationshipContextProvider implementing ContextProvider:
  • [ ] Constructor takes RelationshipManager
  • [ ] async get_context(npc_id, player_id, token_budget) -> ContextSection | None:
    • [ ] Load RelationshipState for this NPC-player pair
    • [ ] Format as natural language (e.g., "You trust this person moderately (42/100). You have high respect for them (78/100)...")
    • [ ] Include tier label and interaction count
    • [ ] Return ContextSection with source="relationship"
  • [ ] Relationship context is small (< 200 tokens typically), so token_budget rarely limits

Dependencies: 1.1 (ContextProvider protocol), 1.7 (RelationshipManager)

Tests:

File: packages/maid-stdlib/tests/relationships/test_context_provider.py

  • [ ] Test returns None for default NEUTRAL with zero interactions (optional — may still return context)
  • [ ] Test returns formatted context with dimension values and tier
  • [ ] Test context fits within token budget

1.9 Relationship Evaluator — Merged into Section 1.7

The tag-to-dimension mapping and delta computation are now methods on RelationshipManager (see Section 1.7 compute_deltas_from_memories()). This avoids a separate class, module, and test file for what is essentially a static dict lookup + accumulation loop.


1.10 Memory Event Bridge (maid-stdlib)

Translates relationship tier transitions and memory events into EventBus events for other systems to react to.

File: packages/maid-stdlib/src/maid_stdlib/memory_bridge.py

  • [ ] Define MemoryEventBridge class:
  • [ ] Constructor takes EventBus
  • [ ] emit_tier_change(npc_id, player_id, old_tier, new_tier) — fires RelationshipTierChangedEvent
  • [ ] emit_memory_stored(memory: Memory) — fires MemoryStoredEvent
  • [ ] emit_extraction_complete(npc_id, player_id, result: ExtractionResult) — fires ExtractionCompleteEvent

File: packages/maid-stdlib/src/maid_stdlib/relationships/events.py

  • [ ] Define RelationshipTierChangedEvent(Event): npc_id, player_id, old_tier, new_tier, dimension_values
  • [ ] Define MemoryStoredEvent(Event): memory_id, npc_id, player_id, memory_type, importance
  • [ ] Define ExtractionCompleteEvent(Event): npc_id, player_id, memory_count, relationship_deltas_count

Dependencies: 1.7 (RelationshipTier, models), existing EventBus, Event

Tests:

File: packages/maid-stdlib/tests/test_memory_bridge.py

  • [ ] Test emit_tier_change fires correct event with old/new tier
  • [ ] Test emit_memory_stored fires event with memory metadata
  • [ ] Test EventBus subscribers receive events

1.11 Memory Rate Limiting & Content Safety (maid-stdlib)

Rate limiting for memory extraction and content filtering. Extend the existing RateLimiter from maid-engine rather than creating a new implementation.

File: packages/maid-engine/src/maid_engine/ai/rate_limiter.py (MODIFY)

  • [ ] Generalize existing RateLimiter to accept a generic entity_id parameter (currently uses player_id)
  • [ ] Rename parameter from player_id to entity_id in check_and_reserve() and record_usage()
  • [ ] This enables NPC-scoped rate limiting with the same sliding-window + daily-counter logic

File: packages/maid-stdlib/src/maid_stdlib/memory/content_filter.py

  • [ ] Define MemoryContentFilter class:
  • [ ] Wraps existing ContentFilter from maid-engine
  • [ ] async filter_memory(memory: Memory) -> Memory | None:
    • [ ] Runs memory.content through ContentFilter.check_output()
    • [ ] Returns None if content fails safety check (memory is dropped)
    • [ ] Logs dropped memories for audit
  • [ ] async filter_extraction_prompt(prompt: str) -> str:
    • [ ] Runs extraction prompt through ContentFilter.check_input()

Dependencies: Existing RateLimiter and ContentFilter (maid-engine), 1.2 (Memory models)

Tests:

File: packages/maid-stdlib/tests/memory/test_content_filter.py

  • [ ] Test filter_memory passes clean content
  • [ ] Test filter_memory drops unsafe content (returns None)
  • [ ] Test filter_extraction_prompt sanitizes input

1.12 EnrichedPromptBuilder & NPCDialogueSystem Integration (maid-classic-rpg)

The integration point that wires everything together. EnrichedPromptBuilder wraps the existing PromptBuilder and injects context from providers.

Note on existing context modules: maid_stdlib.ai.context already provides WorldContext and EntityContext. EnrichedPromptBuilder wraps PromptBuilder (which already uses those). Memory/relationship context is injected in addition to existing context, not replacing it. Clarify this boundary during implementation.

Note on build_for_npc() vs build_system_prompt(): NPCDialogueSystem calls build_for_npc() on PromptBuilder, not build_system_prompt() directly. EnrichedPromptBuilder must override/wrap build_for_npc() to inject memory context at the correct abstraction level.

File: packages/maid-stdlib/src/maid_stdlib/memory/enriched_prompt_builder.py

  • [ ] Define EnrichedPromptBuilder class:
  • [ ] Constructor takes PromptBuilder (existing), ContextOrchestrator
  • [ ] async build_enriched_prompt(npc_id, player_id, npc_dialogue_component, **kwargs) -> str:
    • [ ] Call ContextOrchestrator.gather_context() to get all context sections
    • [ ] Call PromptBuilder.build_for_npc() with existing NPC fields
    • [ ] Inject context sections into the prompt (append after base prompt, before conversation)
    • [ ] Replace {reputation} placeholder with actual relationship tier
    • [ ] When MAID_MEMORY__ENABLED is false, pass through to PromptBuilder directly with {reputation} set to "neutral"
  • [ ] Does NOT modify PromptBuilder interface — wraps it

File: packages/maid-stdlib/src/maid_stdlib/memory/startup.py (see 1.13)

  • [ ] In StdlibContentPack.on_load(): instantiate MemoryService, RelationshipManager, MemoryCache, ContextOrchestrator, and attach to engine/world for access by downstream packs (e.g., engine.memory_service)

File: packages/maid-classic-rpg/src/maid_classic_rpg/systems/npc/dialogue.py (MODIFY)

  • [ ] Import EnrichedPromptBuilder, ExtractionJobQueue, WorkingBufferManager
  • [ ] In NPCDialogueSystem.startup():
  • [ ] Look up world.engine.memory_service (or similar) to construct EnrichedPromptBuilder
  • [ ] This fits the ECS pattern where systems are constructed with just world
  • [ ] In NPCDialogueSystem.process_dialogue():
  • [ ] If EnrichedPromptBuilder available, use it instead of raw PromptBuilder.build_for_npc()
  • [ ] After NPC response, add exchange to WorkingBuffer
  • [ ] In conversation end handling:
  • [ ] Submit ExtractionJob to ExtractionJobQueue with full conversation
  • [ ] Clean up WorkingBuffer for this conversation
  • [ ] Ensure backward compatibility: if no memory system available, behavior is unchanged

Dependencies: 1.1–1.11 (all core components), existing NPCDialogueSystem

Tests:

File: packages/maid-stdlib/tests/memory/test_enriched_prompt_builder.py

  • [ ] Test enriched prompt includes memory context section
  • [ ] Test enriched prompt includes relationship context section
  • [ ] Test enriched prompt falls back to base prompt when no context available
  • [ ] Test reputation placeholder is replaced with actual tier
  • [ ] Test pass-through when MAID_MEMORY__ENABLED is false

File: packages/maid-classic-rpg/tests/systems/npc/test_dialogue_memory_integration.py

  • [ ] Test NPCDialogueSystem uses EnrichedPromptBuilder when available
  • [ ] Test NPCDialogueSystem falls back to PromptBuilder when memory system not configured
  • [ ] Test conversation end submits ExtractionJob
  • [ ] Test working buffer receives exchanges during dialogue

1.13 Extraction Pipeline Integration (maid-stdlib)

Wire the extraction pipeline end-to-end: ExtractionJobQueue worker → MemoryExtractor → RelationshipManager → MemoryService → EventBridge. The ExtractionJobQueue._worker() method serves as the pipeline orchestrator — no separate MemoryPipeline class needed.

File: packages/maid-stdlib/src/maid_stdlib/memory/pipeline.py

  • [ ] Extend ExtractionJobQueue._worker() to perform the full pipeline:
  • Check rate limiter (existing RateLimiter with NPC entity_id) — skip if over budget
  • Call MemoryExtractor.extract(job) to get raw memories + relationship deltas
  • Filter each memory through MemoryContentFilter
  • Store passing memories via MemoryService.store()
  • Compute relationship deltas via RelationshipManager.compute_deltas_from_memories()
  • Apply all deltas via RelationshipManager.update()
  • Enforce memory bounds via MemoryService.enforce_bounds()
  • Emit events via MemoryEventBridge
  • Invalidate MemoryCache for this NPC-player pair
  • [ ] Wrap entire process in try/except — log errors, never crash

File: packages/maid-stdlib/src/maid_stdlib/memory/startup.py

  • [ ] Define setup_memory_system(engine, document_store, llm_provider, event_bus, ...) -> ExtractionJobQueue:
  • [ ] Factory function that wires all components together
  • [ ] Creates MemoryService, RelationshipManager, MemoryExtractor, ExtractionJobQueue, etc.
  • [ ] Registers context providers with ContextOrchestrator
  • [ ] Starts ExtractionJobQueue background worker
  • [ ] Attaches services to engine for downstream access
  • [ ] Returns configured ExtractionJobQueue for integration
  • [ ] Called from StdlibContentPack.on_load() — this is where all memory services are instantiated and made globally accessible
  • [ ] Register all collection schemas (npc_memories, npc_relationships) in StdlibContentPack.register_document_schemas()

Dependencies: 1.1–1.12 (all components)

Tests:

File: packages/maid-stdlib/tests/integration/memory/test_pipeline.py

  • [ ] Test full pipeline: job → extraction → storage → relationship update → events
  • [ ] Test pipeline skips extraction when rate limited
  • [ ] Test pipeline drops memories that fail content filter
  • [ ] Test pipeline enforces bounds after storing
  • [ ] Test pipeline invalidates cache after processing
  • [ ] Test pipeline handles extractor failure gracefully

Phase 2: Knowledge Graph & Gossip (4 weeks)

2.1 Knowledge Graph Models (maid-stdlib)

File: packages/maid-stdlib/src/maid_stdlib/knowledge/__init__.py

  • [ ] Create package with public exports

File: packages/maid-stdlib/src/maid_stdlib/knowledge/models.py

  • [ ] Define KnowledgeEntry (MAIDBaseModel):
  • [ ] Fields: entry_id (UUID), subject (str — entity/concept this is about), predicate (str — relationship type, e.g. "is_enemy_of", "lives_in"), object (str — target entity/concept), confidence (float, 0.0–1.0), source_npc_id (str — who originally knew this), source_type (str — "observed", "told", "gossip"), created_at (datetime), expires_at (datetime | None), propagation_count (int, default 0)
  • [ ] Define GossipPacket dataclass:
  • [ ] Fields: knowledge (KnowledgeEntry), sender_npc_id (str), receiver_npc_id (str), distortion_factor (float — how much the info degraded), timestamp (datetime)
  • [ ] Define KnowledgeFilter for querying: subject, predicate, min_confidence, source_type

Dependencies: MAIDBaseModel

Tests:

File: packages/maid-stdlib/tests/knowledge/test_models.py

  • [ ] Test KnowledgeEntry construction and serialization
  • [ ] Test GossipPacket construction
  • [ ] Test confidence clamping (0.0–1.0)

2.2 Knowledge Manager (maid-stdlib)

File: packages/maid-stdlib/src/maid_stdlib/knowledge/manager.py

  • [ ] Define KnowledgeManager class:
  • [ ] Constructor takes DocumentStore, collection name "npc_knowledge"
  • [ ] async add(entry: KnowledgeEntry) -> str — stores knowledge, deduplicates by (subject, predicate, object, source_npc_id)
  • [ ] async query(npc_id: str, filters: KnowledgeFilter | None, limit: int = 50) -> list[KnowledgeEntry]
  • [ ] async get_about(subject: str) -> list[KnowledgeEntry] — all knowledge about a subject across NPCs
  • [ ] async propagate(packet: GossipPacket) -> KnowledgeEntry | None:
    • [ ] Apply distortion: reduce confidence by distortion_factor
    • [ ] If confidence drops below threshold (0.1), discard
    • [ ] Increment propagation_count
    • [ ] Store as new entry for receiver NPC with source_type="gossip"
  • [ ] async expire_stale() — remove entries past expires_at

Dependencies: 2.1 (models), Doc 01 (DocumentStore)

Tests:

File: packages/maid-stdlib/tests/knowledge/test_manager.py

  • [ ] Test add and query round-trip
  • [ ] Test deduplication by subject/predicate/object/source
  • [ ] Test propagate reduces confidence and increments propagation_count
  • [ ] Test propagate discards below confidence threshold
  • [ ] Test expire_stale removes expired entries
  • [ ] Test get_about returns cross-NPC knowledge

2.3 Gossip System (maid-stdlib)

ECS System that drives NPC-to-NPC knowledge sharing each tick.

File: packages/maid-stdlib/src/maid_stdlib/knowledge/gossip.py

  • [ ] Define GossipSystem(System):
  • [ ] priority ClassVar set to low priority (runs after core systems)
  • [ ] Constructor takes World, KnowledgeManager, RelationshipManager
  • [ ] Pre-computed gossip-ready queue: NPCs that share a room and have relationship tier >= FRIENDLY
  • [ ] update(delta: float):
    • [ ] Budget: max 3 gossip exchanges per tick
    • [ ] For each eligible NPC pair:
    • [ ] Select random knowledge entry from sender that receiver doesn't have
    • [ ] Calculate distortion based on sender's friendliness dimension with receiver
    • [ ] Create GossipPacket and call KnowledgeManager.propagate()
    • [ ] Rebuild gossip-ready queue periodically (every 30 ticks, not every tick)
  • [ ] Track cooldowns: same NPC pair can only gossip once per 10 ticks

Dependencies: 2.2 (KnowledgeManager), 1.7 (RelationshipManager), ECS System base

Tests:

File: packages/maid-stdlib/tests/knowledge/test_gossip.py

  • [ ] Test gossip occurs between NPCs in same room with FRIENDLY+ relationship
  • [ ] Test gossip respects per-tick budget (max 3)
  • [ ] Test gossip respects cooldown (10 ticks between same pair)
  • [ ] Test distortion increases with lower friendliness
  • [ ] Test gossip-ready queue rebuilds periodically
  • [ ] Test no gossip between NPCs with < FRIENDLY relationship

2.4 Knowledge Context Provider (maid-stdlib)

Injects NPC's knowledge into prompt context.

File: packages/maid-stdlib/src/maid_stdlib/knowledge/context_provider.py

  • [ ] Define KnowledgeContextProvider implementing ContextProvider:
  • [ ] Constructor takes KnowledgeManager
  • [ ] async get_context(npc_id, player_id, token_budget) -> ContextSection | None:
    • [ ] Query NPC's knowledge about the player and about topics relevant to current context
    • [ ] Format as natural language ("You've heard that...", "You know that...")
    • [ ] Return ContextSection with source="knowledge"

Dependencies: 1.1 (ContextProvider), 2.2 (KnowledgeManager)

Tests:

File: packages/maid-stdlib/tests/knowledge/test_context_provider.py

  • [ ] Test returns knowledge formatted as natural language
  • [ ] Test respects token budget
  • [ ] Test returns None when NPC has no knowledge

2.5 Memory Consolidator (maid-stdlib)

Periodically consolidates episodic memories into semantic memories using LLM. This is where SemanticMemory is introduced.

File: packages/maid-stdlib/src/maid_stdlib/memory/models.py (MODIFY)

  • [ ] Add SemanticMemory(MAIDBaseModel):
  • [ ] Additional fields: confidence (float, 0.0 to 1.0), source_memories (list[str] — IDs of episodic memories it was derived from), contradicts (list[str] — IDs of memories this supersedes)
  • [ ] memory_type defaults to SEMANTIC
  • [ ] Update Memory type alias to EpisodicMemory | SemanticMemory
  • [ ] Update MemoryService.enforce_bounds() to handle semantic limit (50 per NPC-player pair)

File: packages/maid-stdlib/src/maid_stdlib/memory/prompts.py (MODIFY)

  • [ ] Add consolidation prompt template (system prompt for consolidating episodic → semantic)

File: packages/maid-stdlib/src/maid_stdlib/memory/consolidation.py

  • [ ] Define MemoryConsolidator class:
  • [ ] Constructor takes MemoryService, LLMProvider, CircuitBreakerRegistry
  • [ ] async consolidate(npc_id: str, player_id: str) -> list[SemanticMemory]:
    • [ ] Query episodic memories with access_count >= 3 and similar tags
    • [ ] Group by tags/themes
    • [ ] For each group with 3+ memories, send to LLM with consolidation prompt
    • [ ] Parse response into SemanticMemory objects linking to source episodic memories
    • [ ] Store semantic memories, mark source episodics as consolidated (update tags)
  • [ ] Uses circuit breaker named "memory_consolidator"
  • [ ] Rate limited: max 5 consolidations per NPC per hour

File: packages/maid-stdlib/src/maid_stdlib/memory/consolidation_system.py

  • [ ] Define MemoryConsolidationSystem(System):
  • [ ] Runs on low-frequency schedule (every 100 ticks)
  • [ ] Selects NPCs with >= 10 unconsolidated episodic memories
  • [ ] Processes max 1 NPC per tick (to avoid LLM burst)
  • [ ] Calls MemoryConsolidator.consolidate() for selected NPC-player pairs

Dependencies: 1.3 (MemoryService), 1.4 (extraction prompts), existing LLMProvider, ECS System

Tests:

File: packages/maid-stdlib/tests/memory/test_consolidation.py

  • [ ] Test consolidation groups episodic memories by tags
  • [ ] Test consolidation produces semantic memories linking to source episodics
  • [ ] Test consolidation respects rate limit (5 per NPC per hour)
  • [ ] Test consolidation system selects NPCs with enough memories
  • [ ] Test consolidation handles LLM failure gracefully via circuit breaker

2.6 Emotional State (maid-stdlib)

Phase 2: Add NPC emotional state that influences memory formation and retrieval. Emotions decay toward neutral over time and are derived from recent memory valence + relationship tier. Design the detailed data model when this phase is implemented — do not over-specify now.


Phase 3: Advanced Features (5 weeks)

3.1 Memory Decay System (maid-stdlib)

ECS System that runs periodic memory decay, reducing decay_factor over time for unaccessed memories.

File: packages/maid-stdlib/src/maid_stdlib/memory/decay_system.py

  • [ ] Define MemoryDecaySystem(System):
  • [ ] Runs every 50 ticks
  • [ ] Queries memories with last_accessed older than threshold (configurable, default 24h game-time)
  • [ ] Reduces decay_factor by configurable rate (default 0.01 per cycle)
  • [ ] Memories with decay_factor < 0.1 are candidates for deletion (but CRITICAL importance memories never decay below 0.5)
  • [ ] Batch-updates via MemoryService (individual update() calls — no batch API available)
  • [ ] Budget: process max 100 memories per tick cycle

Dependencies: 1.3 (MemoryService), ECS System

Tests:

File: packages/maid-stdlib/tests/memory/test_decay_system.py

  • [ ] Test decay reduces decay_factor for old unaccessed memories
  • [ ] Test CRITICAL memories floor at 0.5 decay_factor
  • [ ] Test recently accessed memories are not decayed
  • [ ] Test batch processing respects budget (max 100)

3.2 Memory Query API (maid-stdlib)

Structured facade for Doc 07/08 integration — provides clean API for quest systems and other content to query NPC memories.

File: packages/maid-stdlib/src/maid_stdlib/memory/query_api.py

  • [ ] Define MemoryQueryAPI class:
  • [ ] Constructor takes MemoryService, RelationshipManager, KnowledgeManager
  • [ ] async has_memory_of(npc_id, player_id, topic: str) -> bool — checks if NPC has memories tagged with topic
  • [ ] async get_relationship_tier(npc_id, player_id) -> RelationshipTier — convenience wrapper
  • [ ] async get_memories_about(npc_id, topic: str) -> list[Memory] — cross-player memories about a topic
  • [ ] async knows_about(npc_id, subject: str) -> bool — checks knowledge graph
  • [ ] async get_disposition(npc_id, player_id) -> float — returns overall_disposition
  • [ ] async get_npc_knowledge(npc_id, subject: str) -> list[KnowledgeEntry] — knowledge graph query
  • [ ] All methods are read-only, safe for use in command/quest conditions

Dependencies: 1.3 (MemoryService), 1.7 (RelationshipManager), 2.2 (KnowledgeManager)

Tests:

File: packages/maid-stdlib/tests/memory/test_query_api.py

  • [ ] Test has_memory_of returns True when matching memories exist
  • [ ] Test has_memory_of returns False when no matching memories
  • [ ] Test get_relationship_tier returns correct tier
  • [ ] Test knows_about queries knowledge graph
  • [ ] Test all methods are safe to call with nonexistent NPC/player pairs

3.3 Observability & Metrics (maid-engine + maid-stdlib)

Add structured logging and metrics for memory system operations.

File: packages/maid-engine/src/maid_engine/ai/metrics.py

  • [ ] Define MemoryMetrics class:
  • [ ] Counter: extractions_total (success/failure/skipped)
  • [ ] Counter: memories_stored_total (by type)
  • [ ] Histogram: extraction_duration_ms
  • [ ] Gauge: extraction_queue_depth
  • [ ] Gauge: cache_hit_rate
  • [ ] Counter: gossip_exchanges_total
  • [ ] Counter: consolidations_total
  • [ ] Counter: memories_decayed_total
  • [ ] All metrics exposed via existing profiling infrastructure

File: packages/maid-stdlib/src/maid_stdlib/memory/logging.py

  • [ ] Define structured log formatters for memory operations
  • [ ] Log extraction jobs: start, complete, fail, skip (rate limited)
  • [ ] Log relationship tier transitions
  • [ ] Log gossip exchanges
  • [ ] Log consolidation events
  • [ ] All log entries include npc_id, player_id, operation type

Dependencies: Existing profiling infrastructure, existing AuditLogger

Tests:

File: packages/maid-engine/tests/ai/test_metrics.py

  • [ ] Test metrics increment correctly
  • [ ] Test metric labels are populated

3.4 Admin API Extensions (maid-stdlib)

REST API endpoints for admin inspection and management of NPC memories and relationships. Routes are registered via StdlibContentPack.register_api_routes() (pack-level registration), not directly in maid-engine's admin router.

File: packages/maid-stdlib/src/maid_stdlib/api/admin/memory.py

  • [ ] GET /admin/memory/{npc_id} — list all memories for an NPC
  • [ ] GET /admin/memory/{npc_id}/{player_id} — list memories for NPC-player pair
  • [ ] DELETE /admin/memory/{memory_id} — delete specific memory
  • [ ] GET /admin/relationships/{npc_id} — list all relationships for an NPC
  • [ ] GET /admin/relationships/{npc_id}/{player_id} — get specific relationship
  • [ ] PUT /admin/relationships/{npc_id}/{player_id} — manually set relationship dimensions
  • [ ] GET /admin/knowledge/{npc_id} — list NPC's knowledge graph entries
  • [ ] GET /admin/memory/stats — system-wide memory statistics (counts by type, cache hit rate, queue depth)

Dependencies: 1.3 (MemoryService), 1.7 (RelationshipManager), 2.2 (KnowledgeManager), existing admin API router

Tests:

File: packages/maid-engine/tests/api/admin/test_memory_api.py

  • [ ] Test GET memories returns correct data
  • [ ] Test DELETE memory removes it
  • [ ] Test GET relationship returns dimensions and tier
  • [ ] Test PUT relationship updates dimensions
  • [ ] Test stats endpoint returns aggregate counts

3.5 Builder Commands (maid-stdlib)

In-game commands for builders/admins to inspect and manage NPC memory and relationships. Note: @memory is already registered for profiling; these commands use @npcmemory to avoid conflict.

File: packages/maid-stdlib/src/maid_stdlib/commands/building/memory_commands.py

  • [ ] @npcmemory <npc> [player] — show NPC's memories (optionally filtered by player)
  • [ ] @npcmemory clear <npc> [player] — clear NPC's memories
  • [ ] @npcmemory add <npc> <player> <type> <content> — manually add a memory
  • [ ] @relationship <npc> <player> — show relationship state
  • [ ] @relationship set <npc> <player> <dimension> <value> — manually set dimension
  • [ ] @knowledge <npc> — show NPC's knowledge graph
  • [ ] @knowledge add <npc> <subject> <predicate> <object> — manually add knowledge
  • [ ] @gossip status — show gossip system status (queue depth, recent exchanges)
  • [ ] All commands require BUILDER access level

Dependencies: 3.2 (MemoryQueryAPI), existing command registration pattern

Tests:

File: packages/maid-stdlib/tests/commands/building/test_memory_commands.py

  • [ ] Test @npcmemory shows memories for NPC
  • [ ] Test @npcmemory clear removes memories
  • [ ] Test @relationship shows dimension values and tier
  • [ ] Test @relationship set updates dimension
  • [ ] Test commands require BUILDER access level

3.6 Performance Testing & Benchmarks

File: packages/maid-stdlib/tests/performance/test_memory_performance.py

  • [ ] Benchmark: ContextOrchestrator.gather_context latency with 3 providers (target: < 50ms p95)
  • [ ] Benchmark: MemoryService.query with 200 memories per NPC-player pair (target: < 10ms)
  • [ ] Benchmark: ExtractionJobQueue throughput (target: > 10 jobs/sec)
  • [ ] Benchmark: GossipSystem.update with 50 NPCs in same room (target: < 5ms per tick)
  • [ ] Benchmark: MemoryCache hit rate under load (target: > 80% after warmup)
  • [ ] Load test: 100 concurrent conversations with memory extraction enabled

File: packages/maid-stdlib/tests/performance/test_relationship_performance.py

  • [ ] Benchmark: RelationshipManager.update latency (target: < 5ms)
  • [ ] Benchmark: RelationshipManager.get_all_for_npc with 100 relationships (target: < 20ms)

Dependencies: All previous phases


Integration Checkpoints

After Phase 1 Completion

  • [ ] End-to-end test: Player has conversation with NPC → conversation ends → memories extracted → relationship updated → next conversation includes memory context
  • [ ] Verify backward compatibility: NPCDialogueSystem works without memory system configured
  • [ ] Verify memory growth bounds are enforced (200 episodic limit)
  • [ ] Verify cache invalidation after extraction
  • [ ] Verify PIIRedactor strips sensitive data before storage

After Phase 2 Completion

  • [ ] End-to-end test: NPC A learns fact → NPC A gossips to NPC B → NPC B references fact in dialogue
  • [ ] Verify gossip distortion reduces confidence
  • [ ] Verify consolidation merges episodic into semantic memories
  • [ ] Verify knowledge context appears in prompts

After Phase 3 Completion

  • [ ] End-to-end test: Memory decay reduces old unaccessed memories
  • [ ] Verify admin API endpoints return correct data
  • [ ] Verify builder commands work for memory/relationship inspection
  • [ ] Performance benchmarks pass target thresholds
  • [ ] Verify MemoryQueryAPI provides clean interface for Doc 07/08

Cross-Cutting Concerns

Package Exports

Each new package must export its public API from __init__.py:

  • [ ] maid_stdlib.memory exports: MemoryService, MemoryContextProvider, MemoryQueryAPI, Memory, EpisodicMemory, MemoryType, MemoryImportance, EnrichedPromptBuilder
  • [ ] maid_stdlib.relationships exports: RelationshipManager, RelationshipContextProvider, RelationshipState, RelationshipDimension, RelationshipTier, RelationshipDelta
  • [ ] maid_stdlib.knowledge exports: KnowledgeManager, KnowledgeContextProvider, GossipSystem, KnowledgeEntry, GossipPacket
  • [ ] maid_engine.ai updates: add ContextProvider, ContextOrchestrator, TokenBudgetManager, MemoryCache, PIIRedactor to existing exports

Configuration

File: packages/maid-engine/src/maid_engine/config/settings.py (MODIFY)

  • [ ] Add MemorySettings section:
  • [ ] enabled: bool = False — master feature flag (MAID_MEMORY__ENABLED)
  • [ ] extraction_queue_size: int = 100
  • [ ] cache_max_entries: int = 1000
  • [ ] cache_ttl_seconds: int = 300
  • [ ] context_provider_timeout_ms: int = 40
  • [ ] total_context_budget_tokens: int = 2000
  • [ ] memory_bounds_episodic: int = 200
  • [ ] decay_cycle_ticks: int = 50
  • [ ] decay_rate: float = 0.01
  • [ ] consolidation_cycle_ticks: int = 100
  • [ ] gossip_budget_per_tick: int = 3
  • [ ] gossip_cooldown_ticks: int = 10
  • [ ] extraction_rpm_per_npc: int = 10
  • [ ] extraction_daily_tokens_per_npc: int = 50000
  • [ ] Environment variables with MAID_MEMORY__ prefix

Documentation

  • [ ] Update docs/designs/v3.1/03-npc-memory-relationships.md with implementation notes (any deviations)
  • [ ] Add docs/guides/npc-memory.md — user-facing guide for content pack authors
  • [ ] Add docs/guides/npc-relationships.md — relationship system usage guide
  • [ ] Update CHANGELOG.md with v3.1 memory/relationship entries

Dependency Graph

Phase 1:
  1.1 Context Provider Protocol ─────────────────────┐
  1.2 Memory Models ─────────────────────────────────┤
  1.3 Memory Service ──────── (depends: 1.1, 1.2) ──┤
  1.4 Extraction Pipeline ─── (depends: 1.2, 1.3) ──┤
  1.5 Working Buffer ────────────────────────────────┤
  1.6 Memory Context Provider (depends: 1.1, 1.3) ──┤
  1.7 Relationship Models+Mgr ───────────────────────┤
  1.8 Relationship Context ── (depends: 1.1, 1.7) ──┤
  1.9 (merged into 1.7) ────────────────────────────┤
  1.10 Event Bridge ───────── (depends: 1.7) ────────┤
  1.11 Rate Limiting/Safety ─ (depends: 1.2) ────────┤
  1.12 Prompt Integration ─── (depends: 1.1–1.11) ──┤
  1.13 Pipeline Integration ─ (depends: 1.1–1.12) ──┘

Phase 2: (depends: Phase 1)
  2.1 Knowledge Models ──────────────────────────────┐
  2.2 Knowledge Manager ──── (depends: 2.1) ─────────┤
  2.3 Gossip System ──────── (depends: 2.2, 1.7) ───┤
  2.4 Knowledge Context ──── (depends: 1.1, 2.2) ───┤
  2.5 Consolidator ────────── (depends: 1.3, 1.4) ──┤
  2.6 Emotional State ─────── (depends: 1.2, 1.7) ──┘

Phase 3: (depends: Phase 2)
  3.1 Decay System ────────── (depends: 1.3) ────────┐
  3.2 Query API ──────────── (depends: 1.3, 1.7, 2.2)┤
  3.3 Observability ──────────────────────────────────┤
  3.4 Admin API ──────────── (depends: 3.2) ──────────┤
  3.5 Builder Commands ───── (depends: 3.2) ──────────┤
  3.6 Performance Testing ── (depends: all) ──────────┘

Risk Notes

  1. DocumentStore QueryOptions — current implementation only supports exact-match filters, no range queries. Memory queries by recency or decay_factor ranges will need in-process filtering after retrieval. No batch_update() or batch_delete() — all operations are individual calls.
  2. LLM extraction reliability — structured output parsing from LLM is inherently fragile. Extraction prompts need extensive few-shot examples and robust JSON parsing with fallback. Use test fixtures with representative conversations (see 1.4) to validate extraction quality.
  3. Gossip system scaling — with many NPCs in one room, gossip-ready queue computation could be expensive. The periodic rebuild (every 30 ticks) mitigates this, but rooms with > 50 NPCs need testing.
  4. Token budget tuning — the 2000-token default context budget and per-provider allocations will need empirical tuning based on LLM model context windows and response quality.
  5. Optimistic concurrency — relationship updates with a single retry may be insufficient under high contention. Monitor conflict rate and consider exponential backoff if needed.
  6. Extraction queue data loss — the in-memory asyncio.Queue loses pending jobs on crash. This is acceptable for v3.1 but should be documented. Recovery can be addressed in a future version.
  7. Layering caveat — maid-engine's AI module already imports some maid-stdlib types. Strict layering purity may require refactoring these existing imports; scope this before assuming clean boundaries.