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:
- Memory System — Episodic memory for NPCs, extracted from dialogue via LLM (semantic/procedural deferred to Phase 2+)
- Relationship System — Relationship tracking across three dimensions (trust, respect, friendliness)
- 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
ContextSectiondataclass (content: str, token_count: int, priority: float, source: str, metadata: dict) - [ ] Define
ContextProviderProtocol withasync get_context(npc_id, player_id, token_budget) -> ContextSection | None - [ ] Define
ContextOrchestratorclass: - [ ]
register_provider(name, provider, priority, default_budget_pct)method - [ ]
async gather_context(npc_id, player_id, total_budget)method usingasyncio.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
TokenBudgetManagerclass: - [ ] Constructor accepts total budget (int) and provider allocations (dict[str, float] as percentages)
- [ ]
allocate(provider_name) -> intreturns token count for a provider - [ ]
reallocate(used: dict[str, int])redistributes unused budget from providers that used less - [ ] Uses
estimate_tokens()fromtokens.pyfor counting
File: packages/maid-engine/src/maid_engine/ai/cache.py
- [ ] Define
MemoryCacheclass: - [ ] 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)andinvalidate_npc(npc_id)methods - [ ] Thread-safe via
asyncio.Lock
File: packages/maid-engine/src/maid_engine/ai/pii.py
- [ ] Define
PIIRedactorclass: - [ ] 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
MemoryTypeenum:EPISODIC,SEMANTIC,PROCEDURAL(enum defined for forward compatibility; only EPISODIC used in Phase 1) - [ ] Define
MemoryImportanceenum: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
Memoryas type alias forEpisodicMemory(will become union type when semantic/procedural added) - [ ] Define
MemoryQueryResultdataclass: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
MemoryServiceclass: - [ ] 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 updateslast_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_memoriescollection schema inStdlibContentPack.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
ExtractionJobdataclass:conversation_id(str),npc_id(str),player_id(str),messages(list[ConversationMessage]),npc_context(dict),created_at(datetime) - [ ] Define
ExtractionResultdataclass:memories(list[Memory]),relationship_updates(list[RelationshipDelta]),extraction_time_ms(float),tokens_used(int) - [ ] Define
ExtractionJobQueueclass: - [ ] 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
MemoryExtractorclass: - [ ] 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
KnowledgeEntrycandidates 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
WorkingBufferclass: - [ ] Stores in-progress conversation context as lightweight
BufferEntryobjects - [ ]
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
WorkingBufferManagerclass: - [ ] Manages per-conversation
WorkingBufferinstances 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
MemoryContextProviderimplementingContextProvider: - [ ] 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
RelationshipDimensionenum: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
RelationshipTierenum: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
RelationshipDeltadataclass: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
RelationshipManagerclass: - [ ] 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)
- [ ] Tag-to-dimension mapping (configurable dict, extensible by content packs via
- [ ] Register
npc_relationshipscollection schema inStdlibContentPack.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
RelationshipContextProviderimplementingContextProvider: - [ ] 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
MemoryEventBridgeclass: - [ ] Constructor takes
EventBus - [ ]
emit_tier_change(npc_id, player_id, old_tier, new_tier)— firesRelationshipTierChangedEvent - [ ]
emit_memory_stored(memory: Memory)— firesMemoryStoredEvent - [ ]
emit_extraction_complete(npc_id, player_id, result: ExtractionResult)— firesExtractionCompleteEvent
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
RateLimiterto accept a genericentity_idparameter (currently usesplayer_id) - [ ] Rename parameter from
player_idtoentity_idincheck_and_reserve()andrecord_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
MemoryContentFilterclass: - [ ] Wraps existing
ContentFilterfrom 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
EnrichedPromptBuilderclass: - [ ] 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__ENABLEDis false, pass through toPromptBuilderdirectly with{reputation}set to"neutral"
- [ ] Call
- [ ] Does NOT modify
PromptBuilderinterface — wraps it
File: packages/maid-stdlib/src/maid_stdlib/memory/startup.py (see 1.13)
- [ ] In
StdlibContentPack.on_load(): instantiateMemoryService,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 constructEnrichedPromptBuilder - [ ] This fits the ECS pattern where systems are constructed with just
world - [ ] In
NPCDialogueSystem.process_dialogue(): - [ ] If
EnrichedPromptBuilderavailable, use it instead of rawPromptBuilder.build_for_npc() - [ ] After NPC response, add exchange to
WorkingBuffer - [ ] In conversation end handling:
- [ ] Submit
ExtractionJobtoExtractionJobQueuewith full conversation - [ ] Clean up
WorkingBufferfor 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
RateLimiterwith 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
MemoryCachefor 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) inStdlibContentPack.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
GossipPacketdataclass: - [ ] Fields:
knowledge(KnowledgeEntry),sender_npc_id(str),receiver_npc_id(str),distortion_factor(float — how much the info degraded),timestamp(datetime) - [ ] Define
KnowledgeFilterfor 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
KnowledgeManagerclass: - [ ] 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"
- [ ] Apply distortion: reduce confidence by
- [ ]
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): - [ ]
priorityClassVar 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
KnowledgeContextProviderimplementingContextProvider: - [ ] 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_typedefaults toSEMANTIC - [ ] Update
Memorytype alias toEpisodicMemory | 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
MemoryConsolidatorclass: - [ ] 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_accessedolder than threshold (configurable, default 24h game-time) - [ ] Reduces
decay_factorby 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
MemoryQueryAPIclass: - [ ] 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
MemoryMetricsclass: - [ ] 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.memoryexports:MemoryService,MemoryContextProvider,MemoryQueryAPI,Memory,EpisodicMemory,MemoryType,MemoryImportance,EnrichedPromptBuilder - [ ]
maid_stdlib.relationshipsexports:RelationshipManager,RelationshipContextProvider,RelationshipState,RelationshipDimension,RelationshipTier,RelationshipDelta - [ ]
maid_stdlib.knowledgeexports:KnowledgeManager,KnowledgeContextProvider,GossipSystem,KnowledgeEntry,GossipPacket - [ ]
maid_engine.aiupdates: addContextProvider,ContextOrchestrator,TokenBudgetManager,MemoryCache,PIIRedactorto existing exports
Configuration¶
File: packages/maid-engine/src/maid_engine/config/settings.py (MODIFY)
- [ ] Add
MemorySettingssection: - [ ]
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.mdwith 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.mdwith 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¶
- 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()orbatch_delete()— all operations are individual calls. - 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.
- 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.
- 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.
- Optimistic concurrency — relationship updates with a single retry may be insufficient under high contention. Monitor conflict rate and consider exponential backoff if needed.
- 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.
- 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.