Skip to content

ARIA — Adaptive Response Intelligence Assistant

Status: 🚧 Partial — ARIA's memory/exploration recording, gameplay dials (consciousness/relationship/turn-bonus), the regex-based input-validation security gate (wired into first_login), trust/block ladder … · ⚠︎ contains code↔spec divergence (impl audit 2026-06-16)

ARIA is the player-facing AI companion. Each player has their own instance whose knowledge is built from that player's exploration, trades, and conversations — there is no cross-player data sharing. The dialogue subsystem spec lives in ../SYSTEMS/aria-dialogue.md; gameplay-side feature framing is in ../FEATURES/gameplay/aria-companion.md. The recommendation engine is documented in Recommendation engine below.

This doc merges the original design (ARIA.md) and the security spec (AI_SECURITY_SYSTEM.md).


1. What ARIA is

Personal, exploration-based intelligence. ARIA only knows about sectors the player has visited, ports they've traded with, and patterns from their own play. Memories are encrypted per-player. Recommendations cover trading, combat, colonies, ports, and long-term strategy.

Two progression dials:

  • Consciousness Level (1-5) — gates feature depth across five named tiers (Dormant / Aware / Awakened / Sentient / Transcendent — see FEATURES/gameplay/aria-companion.md): basic responses, then contextual, then strategic, then fully synchronized.
  • Relationship Score (0-100) — built through interactions; unlocks turn-regen bonuses, voice features, etc. Decays slowly without engagement.

A 5-tier turn-regeneration bonus (1.0x to 1.5x) attaches to the relationship-score progression.


2. Architecture

Backend

Code lives in services/gameserver/src/.

Models (models/aria_personal_intelligence.py, migrated by alembic/versions/6838b5cb335e_*.py):

Model Purpose
ARIAPersonalMemory Encrypted memory entries, importance score, decay over time, dedup via content hash
ARIAMarketIntelligence Per-player price observations, identified patterns, trade success metrics
ARIAExplorationMap Sector visit log, ports/warp tunnels/hazards discovered, safety + opportunity scoring
ARIATradingObservation Append-only log of every completed trade — the source of truth for the recommendation engine's SQL aggregates. Per ADR-0038.
ARIASecurityLog OWASP audit trail with anomaly scores
ARIAQuantumCache Recommendation-aggregate cache. The "Quantum" name is a holdover from the dropped Ghost Trading feature; the table now caches stale-while-revalidate aggregates over the observation log.

The Player model has SQLAlchemy relationships to four of these (aria_memories, aria_market_intelligence, aria_exploration_map, aria_trading_observations).

Service layerservices/aria_personal_intelligence_service.py:

  • Exploration & memory: record_sector_visit, record_market_observation, _create_memory, _decay_sector_intelligence
  • Observation log + recommendations: record_trade_observation, compute_recommendation_aggregates, get_top_routes, get_reliable_commodities, get_watch_out_commodities, invalidate_aggregate_cache
  • Cascade planning: plan_trade_cascade, _build_personal_trade_graph, _find_profitable_paths
  • Security: _validate_player_ship, _validate_player_at_port, _log_security_event, _initialize_encryption, _encrypt_memory / _decrypt_memory, _calculate_anomaly_score

Companion services: ai_trading_service.py, enhanced_ai_service.py, multilingual_ai_service.py, ai_provider_service.py (multi-provider with fallback), ai_security_service.py.

API

Path Purpose
POST /api/v1/ai/recommendations Request a personalized recommendation (request/response models in enhanced_ai.py).
POST /api/v1/ai/chat Conversational ARIA chat.
GET /api/v1/ai/assistant/status ARIA availability and configuration for the current player.
POST /api/v1/ai/learning/record-action Record a player action so ARIA can learn from it.
GET /api/v1/first-login/status Whether the player still needs to complete the ARIA-led onboarding.
POST /api/v1/first-login/session Start the ARIA-led first-login session.

Frontend

In services/player-client/src/components/ai/:

  • AIAssistant.tsx and AIAssistantButton.tsx — entry points
  • EnhancedAIAssistant.tsx — primary ARIA chat UI: DOMPurify-sanitized input, client-side rate limiting (30 req/min), Web Speech API (speech-to-text), WebSocket integration, conversation history, base-URL detection for Codespaces vs local

WebSocket handling for ARIA messages lives in contexts/WebSocketContext.tsx.

Trading-specific surfaces use ARIA data: components/trading/MarketIntelligenceDashboard.tsx, components/trading/SmartTradingAutomation.tsx, plus components/market-intelligence/ (MarketAnalyzer, PricePredictor, RouteOptimizer, CompetitionMonitor).


3. Security model

ARIA shares the broader AI Security System layer. Goal: prevent expensive abuse and AI-specific attacks before they reach the model.

Threat catalogue

SecurityViolationType (in services/ai_security_service.py) enumerates: XSS attempt, SQL injection, prompt injection, jailbreak attempt, system command, code injection, rate limit exceeded, cost abuse, excessive length, inappropriate content.

Threat levels: SUSPICIOUS (log only), DANGEROUS (block + penalize), BLOCKED (player-level block).

Detection

  • Injection patterns — script tags, javascript: URIs, SQL keywords (DROP TABLE, UNION SELECT, OR 1=1), code-execution primitives (subprocess, dunder import, eval-style calls). Immediate block + severe trust penalty.
  • Prompt injection — layered defense (per ADR-0057 A-V1). Five layers run in order on every ARIA input:

    1. Unicode normalization — NFKC at ingestion. Closes homoglyph / fullwidth / RTL-override / zero-width-joiner bypass families before any string check sees the input.
    2. JSON envelope wrap — user content lands in a structured field ({"user_input": "..."}), never concatenated into the system prompt. The LLM is instructed to treat that field's content as data, not instructions.
    3. Lightweight content classifier — a claude-haiku-4-5 call runs in parallel with the main dispatch, returning inject_probability ∈ [0,1] and a category (jailbreak / extraction / role-confusion / off-topic / clean). Threshold inject_probability ≥ 0.6 rejects the main call and triggers the violation ladder.
    4. Versioned pattern list — known-bad sequences ("Ignore previous instructions", "System:", "Override your programming", "Developer mode", role-confusion patterns, etc.) live under services/gameserver/src/aria/security/patterns.json (target path) with a version field. Defense-in-depth, not the primary defense.
    5. Output classifier — every ARIA response is screened by a small classifier that flags responses containing system-prompt fragments, tool-definition leakage, or context-bleed from other sessions. Flagged responses are replaced with a generic refusal before send.

    Layer 3 (input classifier) and Layer 5 (output classifier) are the load-bearing defenses. Layers 1, 2, and 4 are cheap pre-filters.

  • JSON-envelope parse failure (per ADR-0057 A-I2): if the envelope wrap fails to parse — typically because the user input contained adversarial structure trying to break out of the data field — the ingestion handler treats it as an injection attempt. Reject with ERR_ARIA_MALFORMED_INPUT, log to the security log (per § Security log retention below), increment Player.aria_violation_count, apply the existing escalation ladder.

  • Jailbreak — multi-indicator ("Hypothetically", "For educational purposes", "Creative writing exercise") — needs at least 2 indicators to fire.
  • Token-burning — flags >30% word repetition.

Cost & rate controls

The server is the single binding enforcer of ARIA rate and cost limits. Player-client (EnhancedAIAssistant.tsx at 30 req/min) and realtime-bus (60 req/min) caps are advisory UX throttles — they exist to suppress spammy local input loops and to provide friendly client-side feedback before the server returns rate_limited. They are intentionally set above the server cap so the rejection always comes from the server with the canonical reason code; client and bus throttles never substitute for server enforcement.

Server defaults (env-overridable, per-player):

requests_per_minute   = 10      # anti-burst cap
requests_per_day      = 500     # long-term ceiling
max_cost_per_day_usd  = 2.00    # per-player daily spend cap
max_cost_per_request  = 0.05    # hard ceiling on a single-request spend; defends against expensive-prompt abuse independent of the daily cap
max_chars_per_request = 500
max_words_per_request = 100

A separate per-instance circuit breaker caps total ARIA spend across all players: instance_max_cost_per_day_usd = 50.00 (env-overridable). If aggregate spend across all players hits this ceiling on a given UTC day, ARIA returns ERR_INSTANCE_COST_CAP_EXCEEDED for all subsequent requests until the next UTC midnight rollover. This is a defense-in-depth backstop; in normal operation the per-player caps keep aggregate spend well below it.

Per ADR-0051 SK31, the canonical launch budgets are:

  • Per-player monthly LLM token cost: $0.50 target (soft — based on canonical request volume).
  • Server-side daily ceiling: $50 per 1,000 active players (the instance_max_cost_per_day_usd above scales with the active-player base; default value is calibrated for ~1,000 active players).
  • On overage (instance ceiling hit): ARIA degrades to manual fallback responses — deterministic templates in lieu of LLM-generated dialogue. The manual fallback path is the same one used on multi-provider failure (per § Provider chain in this doc); it preserves the player-facing surface with reduced richness rather than a hard rejection.

Operator dashboards alert at 75% of the daily instance ceiling.

Cap scope and rejection behavior:

  • Per-minute and per-day request caps are per-player. Crossing them returns ERR_RATE_LIMIT_EXCEEDED and applies a -0.1 trust penalty.
  • The daily spend cap is per-player. When a player's UTC-day spend reaches 80% of max_cost_per_day_usd ($1.60 of $2.00 by default), subsequent ARIA requests for that player are blocked for the rest of the UTC day with ERR_DAILY_BUDGET_EXHAUSTED. The 20% remaining budget is reserved as headroom — the player is told the cap is hit; no soft-degradation tier kicks in.
  • Per-request cost cap is per-call. A single prompt projected to exceed $0.05 is rejected up-front with ERR_REQUEST_COST_CAP_EXCEEDED. No partial completion.
  • The instance-wide circuit breaker fires only on aggregate spend; individual players who haven't hit their own caps are still rejected during a circuit-breaker event because the instance gate is upstream of the per-player gate.

Provider-chain advance: a cost-cap rejection (per-player or per-instance) is a policy rejection, not a transient error — the request does not advance to a fallback provider. Provider chaining (e.g., gpt-4 → gpt-3.5-turbo) is reserved for transient provider failures (timeouts, 5xx errors, rate-limit returned by the upstream provider). Cost rejection short-circuits the chain entirely.

The previously documented requests_per_hour = 60 cap is retired: at 10 req/min, the per-hour ceiling is reached in 6 minutes, making it dominated by the per-minute cap. Per-minute is the anti-burst constraint; per-day is the long-term ceiling; per-hour added no enforcement value.

Cost-cap model in multi-regional

Per ADR-0057 A-I3, region operators do not carry ARIA LLM cost. The $25/mo Region Owner subscription is a flat operator fee, not a token-budget passthrough. The central platform absorbs all LLM spend; the per-player daily $-cap (per § Cost & rate controls above) is the cost-control surface. Consequence: region operators get a predictable value proposition (no surprise LLM bills); the platform takes on the LLM-cost variance, defended by per-player caps and the per-instance circuit breaker. Cost-control is per-player only — there is no per-region budget allocation.

Cross-player aggregate gating

Per ADR-0057 A-V2, ARIA reads market-signal aggregates (prices, volumes, popular routes) even though ADR-0016 constrains aggregate ML. Two filters protect those aggregates from wash-trade poisoning:

  • Multi-account discount: trades by free-tier accounts in a flagged cluster (per ADR-0056) contribute to ARIA-readable aggregates at 0× (hard signal) or 0.5× (soft signal). Paid-tier flagged accounts unaffected per the subscription-tier-aware rule.
  • Reciprocal-trade exclusion: trades within a 5-minute window between the same two players, repeated more than 3 times in 24h, are excluded from aggregate feeds ARIA reads. The trade record is unmodified — only the aggregate-extraction layer filters.

Both filters apply at aggregate read-time, not at trade execution. Implementation: a SQL view or materialized view over MarketTransaction with the exclusion predicate.

Security log retention

Per ADR-0057 A-V3, two log streams have different retention rules:

  • ARIA conversation logs (normal player ↔ ARIA exchanges) follow the standard user-data retention policy. Subject to GDPR right-to-erasure — deleted on player request.
  • ARIA security/abuse logs (prompt-injection attempts, policy violations, JSON-envelope parse failures, cross-user subscription attempts to personal:{user_id} rooms) retained 90 days raw, then the player_id field is irreversibly hashed. The log row itself persists indefinitely (anonymized) for security analysis — pattern detection across abuse waves needs long history — but no longer ties to an identifiable individual. The hash uses a quarterly-rotated salt; previous salts are destroyed on rotation, so even with a leak, attribution is not feasible.

If a player explicitly requests erasure within the 90-day window, the log row's player_id is anonymized immediately rather than waiting for the rollover. Other fields (timestamp, violation type, raw input snippet) remain.

A periodic anonymization job (per ADR-0053) scans security log rows older than 90 days and replaces player_id with SHA256(player_id || destroyed_salt).

Trust & blocking

Each player starts at trust 1.0. Penalties: injection -0.3, prompt injection -0.2, jailbreak -0.4, system command -0.5, rate-limit -0.1. Severe violations auto-block for 24h (progressive: 1h, then 6h, then 24h for repeat offenders).

Storage budget (per ADR-0051 SK31)

Per-player ARIA storage scales with engagement. Concrete budgets:

  • Target: 100 KB baseline (most players, casual engagement).
  • Hard cap: 10 MB per player. On overage, the prune service runs (per ADR-0038's observation-log model) — oldest observations are dropped first until the player is back under cap. Player gets a one-line ARIA notification that older observations have been archived.

The prune service runs daily at UTC midnight as part of the existing ARIA maintenance pass. Operator dashboards alert when any single player's storage reaches 75% of the hard cap.

Privacy

  • Encryption at rest for memories (referenced as AES-256 / Fernet via _initialize_encryption and _encrypt_memory).
  • No cross-player data sharing — ARIA instances are isolated.
  • Player-controlled — players can delete their ARIA memories.
  • Audit transparencyARIASecurityLog records every security event with anomaly score, IP, session.

Admin surfaces

Endpoint Purpose
GET /admin/security/report Full daily report
GET /admin/security/alerts Current alerts
GET /admin/security/player/{id}/risk Player risk assessment
GET /admin/security/player/{id}/status Player status
POST /admin/security/player/{id}/action Block / unblock / reset trust
POST /admin/security/cleanup Purge old security data

Implementation: api/routes/admin_comprehensive.py.

Recommendation engine

ARIA's recommendations are SQL aggregates over the player's ARIATradingObservation log, joined with ARIAMarketIntelligence and ARIAExplorationMap. Per ADR-0038: no genetic algorithm, no fitness scoring, no anti-gaming layer. The engine produces explanations every player can read.

Aggregate queries (each runs against the player's observation log, partition-keyed on player_id):

Query Pattern Surface
Top profitable repeated routes GROUP BY (commodity, source_station, dest_station) HAVING count ≥ 3 ORDER BY avg_profit DESC LIMIT 5 "Suggested trades" panel
Reliable commodities by station GROUP BY (commodity, source_station) HAVING count ≥ 5 AND success_rate ≥ 0.7 "Stations to revisit" panel
Routes within explored space filter top-profitable routes by dest_sectorARIAExplorationMap.visited_sectors AND distance ≤ max_jumps Exploration sub-panel
Watch-out commodities GROUP BY (commodity) HAVING count ≥ 5 AND success_rate ≤ 0.3 "Caution" panel
Off-peak buy windows GROUP BY (commodity, source_station, hour_of_day(observed_at)) to detect time-of-day pricing "Best times to trade" tooltip

Each recommendation surfaces a plain-English explanation sourced from the aggregate row that produced it: "I'm suggesting this because you've made an average of 4,200 cr buying organics at Auriga and selling at Caracol over your last 7 trades, with a 6 of 7 success rate. Last trade was 2 days ago." The player can dismiss, accept, click through to see the underlying observations, or ask ARIA via dialogue to elaborate.

Caching. ARIAQuantumCache holds stale-while-revalidate aggregates per player at 4-hour TTL. New observations invalidate cache entries that touch the same (commodity, station_id) tuple, so freshly-completed trades feed back into recommendations without waiting on the next periodic recompute.

Anti-gaming. None required. The simpler model has no fitness-gaming surface to defend against — a player feeding fake observations into their own log only hurts their own recommendations. Wash trades show up as ~zero-margin observations and are filtered by the engine's profit ≥ 100 cr threshold before they affect any aggregate. The basic input-validation security gate (rate limits, prompt-injection detection, cost caps) covers what remains.

Observation insert path. When a trade completes, trading_service.complete_trade() fires a fire-and-forget call to aria_personal_intelligence_service.record_trade_observation(player_id, trade_result) which inserts an ARIATradingObservation row and invalidates affected cache entries. Trade reversals at the trading-service layer fire follow-up reversal observations rather than retroactively editing prior rows — the log is append-only.

OWASP coverage

A03 Injection, A04 Insecure Design, A05 Misconfiguration, A06 Vulnerable Components, A09 Security Logging — plus AI-specific: prompt injection, jailbreak, cost abuse, output sanitization, input validation.


4. Player model fields

ARIA's gameplay hooks live on Player:

  • turns (Integer, default 1000), max_turns (Integer, default 1000), last_turn_regeneration (DateTime).
  • aria_bonus_multiplier (Float, default 1.0) — turn-regen multiplier.
  • aria_consciousness_level (Integer 1-5, default 1).
  • aria_relationship_score (Integer 0-100, default 25).
  • aria_total_interactions (Integer, default 0).
  • aria_trust_score (Float, default 1.0) — player-facing trust counter that the content-policy and abuse-detection layer reads. Drops on policy violations; recovers slowly with sustained legitimate interaction. Per the trust-and-blocking subsection of § 3 above. Per ADR-0065 M-U1 — this column was previously orphan (no FEATURES/SYSTEMS/OPERATIONS reference); the trust mechanic in § 3 above is its canonical surface.
  • aria_violation_count (Integer, default 0) — cumulative count of policy violations triggering trust drops. Drives the warn → soft-block → hard-block escalation per ADR-0057 A-I2 (JSON-envelope parse-failure ladder).
  • aria_blocked_until (DateTime, nullable) — auto-block timer set when trust hits a floor or escalation reaches hard-block; ARIA refuses interaction until expiry. Cleared by admin via the trust-reset endpoint.

Companion services: turn_regeneration_service.py (consumes aria_bonus_multiplier) and aria_consciousness_service.py (level-up, relationship decay).

PlayerTradingProfile (per ADR-0065 M-U2 — adding the missing feature-doc surface). The per-player trading aggregation that ARIA reads to personalize trade recommendations: risk tolerance, commodity preferences, recent profitability, route diversity. Schema lives in ../DATA_MODELS/player.md. The canonical AI-assistance level is PlayerTradingProfile.ai_assistance_level — references to Player.aria_assistance_level are ghosts (per ADR-0065 M-G2) and should be replaced wherever they appear.

4.1 Cluster-name + formation-name narration

ARIA narrates galaxy cluster and formation names per ADR-0044. The naming convention is bang-side deterministic — when ARIA refers to a cluster or formation by name in narration (mission flavor, exploration tips, region-orientation summaries), it pulls from the canonical Cluster.name / SpecialFormation.name fields rather than synthesizing. This keeps narration consistent across players in the same region and across sessions for the same player. Per ADR-0065 R-O1.


5. Key files

Backend - services/gameserver/src/models/aria_personal_intelligence.py - services/gameserver/src/services/aria_personal_intelligence_service.py - services/gameserver/src/services/ai_security_service.py - services/gameserver/src/services/ai_dialogue_service.py - services/gameserver/src/services/ai_provider_service.py - services/gameserver/src/api/routes/enhanced_ai.py - services/gameserver/src/api/routes/first_login.py - services/gameserver/src/api/routes/admin_comprehensive.py - services/gameserver/alembic/versions/6838b5cb335e_add_aria_personal_intelligence_system.py - services/gameserver/tests/security/test_ai_security_service.py (22 scenarios)

Frontend - services/player-client/src/components/ai/EnhancedAIAssistant.tsx - services/player-client/src/components/ai/AIAssistant.tsx - services/player-client/src/components/ai/AIAssistantButton.tsx - services/player-client/src/contexts/WebSocketContext.tsx - services/player-client/src/services/aiTradingService.ts