Skip to content

ARIA Dialogue

Status: ๐Ÿšง Partial โ€” ARIA chat is reachable, sanitized, and per-player-isolated, but its replies are deterministic intent-templates with NO LLM call โ€” the documented provider chain is wired but carries no production traffic (matches the "Deep Brain" framing per ADR-0092 ยง3, folded into OPERATIONS/aria.md ยง2 today). Re-verified 2026-08-04: encrypted-memory writes confirmed live (aria_personal_intelligence_service.py _encrypt_memory, called from both memory-creation paths). No further divergence found. ADR-0093 item 28 greenlit the "dark trio" โ€” LLM-scored narrative haggling with a server-side clamp (see ../FEATURES/economy/haggling.md, still ๐Ÿ“ design-only pending build), tool-first Deep-Brain retrieval over aria_data_streams-scoped read-only tools, and true-spend cost metering against the ADR-0057 caps (metering to be built first, as the safety piece) โ€” all three remain dark behind ARIA_LLM_CHAT_ENABLED / ARIA_PROMPT_CLASSIFIER_ENABLED per the item-17 standing order, authorization only, not yet shipped (folded from ADR-0093, re-verified 2026-08-07).

Purpose

ARIA is the per-player AI assistant. The dialogue system is the pipeline that turns a player message into a sanitized, language-aware, context-rich prompt; routes it through a fallback-equipped multi-provider stack; sanitizes the response; logs the exchange; and pushes the assistant reply back over the realtime bus. As shipped today (per the Status banner above and DECISIONS.md's aria-llm-vs-template entry, confirming this as the intended-for-now architecture, not a stopgap), the "stack" resolves to deterministic intent-templates โ€” the LLM provider slots (AI_PROVIDER_PRIMARY / _SECONDARY / _FALLBACK below) are wired end-to-end but carry no production traffic. Every step is auditable; every step has a deterministic fallback so the assistant is never fully unavailable.

Inputs

The pipeline reads: - The raw player message body. - The player's User/Player row (locale, aria_consciousness_level, aria_relationship_score, blocked status). - The player's ARIAPersonalMemory, ARIAExplorationMap, ARIAMarketIntelligence, and recent ARIATradingObservation rows (top-K by recency or aggregate-relevance). - Current session context: sector, ship, recent dialogue exchanges (last N turns). - AI provider config (env): AI_PROVIDER_PRIMARY, AI_PROVIDER_SECONDARY, AI_PROVIDER_FALLBACK. - Security config: rate limits, max chars, cost cap, blocked patterns.

The system fires on: - POST /api/v1/ai/chat โ€” direct chat send. - POST /api/v1/ai/recommendations โ€” request a recommendation (system-style query). - WebSocket aria:send command on the realtime bus. [NO-CANON naming discrepancy, flagged 2026-08-06, doc-only โ€” no code changed]: the actual live websocket contract (services/gameserver/src/services/enhanced_websocket_service.py:693-762) dispatches on message-type-level ai_request (trading recommendations / market predictions, _handle_ai_request) and aria_chat (conversational ARIA, _handle_aria_chat) โ€” not a literal aria:send command name. Both share an independent rate-limit bucket, max_ai_requests_per_minute (default 60/min, distinct from the REST POST /api/v1/ai/chat limits above), keyed on any message_type starting ai_ or equal to aria_chat. Whether aria:send is a stale/renamed reference or a separate legacy transport wasn't determined by this pass โ€” flagged for someone with fuller websocket-contract context rather than guessed at, since this is AI-dialogue-adjacent. - First-login dialogue (specialized variant โ€” see first-login.md). - Tool-result callbacks (when the model invokes a tool and the tool returns).

Process

Player message
   โ”‚
   โ–ผ
[1] Client-side sanitize (DOMPurify, length cap)
   โ”‚  Server-side: build dialogue context with auth_snapshot
   โ”‚  โ€ข subscription_tier        (User.subscription_tier at this instant)
   โ”‚  โ€ข aria_consciousness_level (Player.aria_consciousness_level)
   โ”‚  โ€ข aria_bonus_multiplier    (Player.aria_bonus_multiplier)
   โ”‚  Per ADR-0058 A-F1, all subsequent steps read these from the
   โ”‚  snapshot, never from the live row. A subscription upgrade or
   โ”‚  consciousness level-up landing mid-exchange takes effect on
   โ”‚  the NEXT exchange โ€” the in-flight one completes consistently.
   โ”‚
   โ–ผ
[2] Server input gate (ai_security_service)
   โ”‚  โ€ข length / word cap
   โ”‚  โ€ข injection patterns (script, sql, dunder, eval)
   โ”‚  โ€ข prompt-injection patterns (25+ regexes)
   โ”‚  โ€ข jailbreak patterns (โ‰ฅ2 indicators)
   โ”‚  โ€ข token-burning (>30% repetition)
   โ”‚  โ€ข classifier category policy: instruction_override / extraction /
   โ”‚      jailbreak / role_confusion โ†’ hard block (400, lore-friendly
   โ”‚      rejection); off-topic / clean โ†’ filtered + logged + trust-laddered
   โ”‚  โ€ข rate limit (req/min, /day โ€” per-hour retired per AU2-18)
   โ”‚  โ€ข cost cap (USD/day, USD/request)
   โ”‚  โ†’ on hit: log SecurityViolation, penalize trust, possibly block
   โ”‚
   โ–ผ
[3] Intent classification
   โ”‚  โ€ข lightweight classifier (rule + small model)
   โ”‚  โ€ข routes: chat | trade-advice | combat-advice | explore | market-query
   โ”‚           | colony-advice | meta (memory recall)
   โ”‚
   โ–ผ
[4] Multilingual context build (multilingual_ai_service)
   โ”‚  โ€ข translation_service.get_user_language_preference(user_id)
   โ”‚  โ€ข cultural-context tag (western/hispanic/chinese/...)
   โ”‚  โ€ข cultural guidelines (tone, formality, humor, authority)
   โ”‚  โ†’ response_language fixed; instruction-stream tagged
   โ”‚
   โ–ผ
[5] Prompt assembly
   โ”‚  โ€ข system header (role, persona, language directive)
   โ”‚  โ€ข personal memory snippets (top-K, decay-weighted)
   โ”‚  โ€ข exploration data slice (sectors visited, ports known)
   โ”‚  โ€ข market intelligence slice (relevant commodities)
   โ”‚  โ€ข last N dialogue turns
   โ”‚  โ€ข the (sanitized) player message embedded in a JSON envelope
   โ”‚       so injected text lands inside a data field, not the
   โ”‚       instruction stream
   โ”‚  โ€ข tool definitions (market_lookup, sector_info, route_plan, ...)
   โ”‚
   โ–ผ
[6] Provider chain (ai_provider_service)
   โ”‚   primary โ†’ secondary โ†’ manual fallback
   โ”‚   each step has a hard timeout; chain advances on failure or 5xx
   โ”‚
   โ–ผ
[7] Tool invocation loop
   โ”‚  while response.tool_call:
   โ”‚    โ€ข validate tool args (schema)
   โ”‚    โ€ข run tool (read-only handler โ€” market query, sector lookup, ...)
   โ”‚    โ€ข feed result back as a follow-up turn
   โ”‚    โ€ข cap at 3 tool turns to avoid infinite loops
   โ”‚
   โ–ผ
[8] Response sanitize
   โ”‚  โ€ข strip control characters
   โ”‚  โ€ข cap length
   โ”‚  โ€ข run output filters (no PII echo, no other-player private data)
   โ”‚  โ€ข DOMPurify on the client before render
   โ”‚
   โ–ผ
[9] Persist
   โ”‚  โ€ข ARIAPersonalMemory row (encrypted) for the exchange
   โ”‚  โ€ข DialogueExchange row in chat history
   โ”‚  โ€ข ARIASecurityLog if any anomaly
   โ”‚  โ€ข increment Player.aria_total_interactions
   โ”‚  โ€ข check consciousness threshold (50/150/400/1000) and bump
   โ”‚    aria_consciousness_level + aria_bonus_multiplier
   โ”‚
   โ”‚  Multiplier semantics (per ADR-0057 A-D2): the multiplier applies
   โ”‚  CONTINUOUSLY on every ARIA interaction โ€” read on each call to
   โ”‚  scale recommendation strength, narration richness, observation
   โ”‚  depth. Level transitions are atomic boundary events; the
   โ”‚  multiplier itself is not gated to those transitions.
   โ”‚
   โ–ผ
[10] Push (realtime-bus)
   โ”‚  โ€ข aria_message event (personal:{user_id})
   โ”‚  โ€ข turn_pool_updated if multiplier changed

Provider chain detail

AI_PROVIDER_PRIMARY=openai
AI_PROVIDER_SECONDARY=anthropic
AI_PROVIDER_FALLBACK=manual    # rule-based, always available

Each provider exposes a uniform analyze(...) / generate(...) API. Selection is per-request; a provider failure (timeout, 5xx, content filter) advances the chain. The manual provider (enhanced_manual_provider.py) replicates the response shape with rule-based pattern matching so the assistant is never offline.

Rate / cost controls

requests_per_minute   = 10     (env: ARIA_RPM)         # anti-burst cap
requests_per_day      = 500    (env: ARIA_RPD)         # long-term ceiling
max_cost_per_day_usd  = 2.00   (env: ARIA_DAILY_USD)   # per-player daily spend cap
max_cost_per_request  = 0.25   (env: ARIA_REQ_USD)     # per-request hard ceiling
max_chars_per_request = 500
max_words_per_request = 100

Plus a per-instance circuit breaker: instance_max_cost_per_day_usd = 50.00 (env: ARIA_INSTANCE_DAILY_USD) โ€” defense-in-depth backstop on aggregate spend across all players. Per-hour cap retired (AU2-18) โ€” was dominated by per-minute math. When daily spend hits 80% of the per-player limit, the player is blocked for the rest of the UTC day with ERR_DAILY_BUDGET_EXHAUSTED.

Trust scoring

Each player starts with trust = 1.0. Penalties: - Injection attempt: โˆ’0.3 - Prompt injection: โˆ’0.2 - Jailbreak: โˆ’0.4 - System command: โˆ’0.5 - Rate-limit hit: โˆ’0.1

Severe violations auto-block for 24 h (progressive: 1 h โ†’ 6 h โ†’ 24 h on repeats).

Multilingual routing

The user's language preference (User.locale) drives: - Response language directive in the prompt. - Cultural guideline injection (tone, formality, humor, authority). - Pre-translated UI strings used in tool outputs (see translation_service).

Fallback to en if the user has no preference or the preference is unsupported.

Outputs / state changes

Per dialogue turn: - ARIAPersonalMemory โ€” encrypted memory entry (importance score, content hash for dedup). - DialogueExchange (or equivalent) โ€” chat-history row. - ARIASecurityLog โ€” if any security event fired. - Player.aria_total_interactions โ€” incremented. - Player.aria_consciousness_level, Player.aria_bonus_multiplier โ€” bumped on threshold crossings. - User.trust_score โ€” adjusted per any violation.

Events emitted: - aria_message โ€” personal unicast with the assistant's reply. - turn_pool_updated โ€” if multiplier changed (downstream of consciousness bump). - aria_security_alert โ€” admin feed, on dangerous violations. - notification โ€” personal, if a tool surfaced an alert (e.g. price hit).

Downstream: - Memory writes feed future ARIA queries (recall in step 5). - Security log feeds the admin dashboard.

Invariants

  1. Every player input is rate-limited and sanitized before reaching any provider.
  2. The player's input is never inlined directly into the instruction stream โ€” it lives in a JSON data field.
  3. Each ARIA instance is per-player; no cross-player data is read or written.
  4. Memories are encrypted at rest (Fernet/AES-256) under ARIA_ENCRYPTION_KEY, a persistent stack-loaded secret (same discipline as JWT_SECRET) โ€” memories survive gameserver restarts.
  5. Provider failure never produces an unhandled exception to the user โ€” the chain falls through to manual fallback.
  6. Tool-call loops cap at 3 iterations; runaway prevented.
  7. Cost caps are enforced before the provider call, not after.
  8. Audit trail (ARIASecurityLog) is written for every detected violation and every blocked request.
  9. Output is sanitized on both server and client (defense in depth: server strip + client DOMPurify).

Failure modes

Mode Target handling
All providers down Manual fallback delivers a rule-based response; user sees a degraded-mode banner.
Provider returns malformed JSON Validator catches; chain advances; fallback if exhausted.
Tool call to unknown tool Reject with explicit error; do not advance chain (model bug, not provider failure).
Player rate-limited Return 429; suggest cooldown; emit aria_rate_limited.
Cost cap reached Block until next UTC day; admin can lift via security action.
Injection / jailbreak / extraction / role_confusion / instruction_override Hard block (400), log, penalize trust; reply with a lore-friendly in-voice rejection, not a raw error string. The four-category block set (instruction_override / extraction / jailbreak / role_confusion) was ratified as the layer-4 policy by ADR-0093 item 14 โ€” every other classification flows filtered + logged + trust-laddered rather than blocked (folded from ADR-0093, re-verified 2026-08-07).
Translation lookup fails Fall back to English directive; log the locale that missed.
Memory decryption fails (key rotation, corruption) Skip that memory snippet; continue with reduced context; log.
Realtime bus push fails Reply still persisted; client picks up via REST history on next open.
First-login variant during normal chat (mistaken context) First-login flow is a separate handler (see first-login.md); cross-routing rejected at intent classification.

Source map

Concern Path (target)
Dialogue orchestration services/gameserver/src/services/ai_dialogue_service.py
Multi-provider client services/gameserver/src/services/ai_provider_service.py
Manual fallback provider services/gameserver/src/services/enhanced_manual_provider.py
Multilingual routing services/gameserver/src/services/multilingual_ai_service.py
Translation lookups services/gameserver/src/services/translation_service.py
Personal memory + tool data services/gameserver/src/services/aria_personal_intelligence_service.py
Trade-DNA / market intelligence services/gameserver/src/services/ai_trading_service.py
Security gate services/gameserver/src/services/ai_security_service.py
Audit log model services/gameserver/src/models/aria_personal_intelligence.py:ARIASecurityLog
Encrypted memory model services/gameserver/src/models/aria_personal_intelligence.py:ARIAPersonalMemory
Chat / recommendation routes services/gameserver/src/api/routes/ai.py, enhanced_ai.py
Admin security routes services/gameserver/src/api/routes/admin_comprehensive.py
Player-client UI services/player-client/src/components/ai/EnhancedAIAssistant.tsx