Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
169b2c119b fix: test collection errors in test_bannerlord_harness.py and test_evennia_ws_bridge.py (#1509)
Some checks failed
CI / test (pull_request) Failing after 1m23s
CI / validate (pull_request) Failing after 1m23s
Review Approval Gate / verify-review (pull_request) Successful in 15s
Two fixes:
1. evennia_ws_bridge.py: Moved clean_lines, parse_room_output, normalize_event
   from inside playback() function to module level for test imports.
2. bannerlord_harness.py: Fixed import from 'bannerlord_trace' to
   'nexus.bannerlord_trace'.

Results:
- test_evennia_ws_bridge.py: 5 tests pass
- test_bannerlord_harness.py: 37 passed, 2 skipped

Closes #1509.
2026-04-14 23:04:36 -04:00
3 changed files with 57 additions and 313 deletions

262
GENOME.md
View File

@@ -1,262 +0,0 @@
# GENOME.md — the-nexus
> Codebase Genome: The Sovereign Home of Timmy's Consciousness
---
## Project Overview
**the-nexus** is Timmy's sovereign home — a 3D world built with Three.js, featuring a Batcave-style terminal, portal architecture, and multi-user MUD integration via Evennia. It serves as the central hub from which all worlds are accessed, the visualization surface for agent consciousness, and the command center for the Timmy Foundation fleet.
**Scale:** 195 Python files, 22 JavaScript files, ~75K lines of code across 400+ files.
---
## Architecture
```mermaid
graph TB
subgraph "Frontend Layer"
IDX[index.html]
BOOT[boot.js]
COMP[nexus/components/*]
PLAY[playground/playground.html]
end
subgraph "Backend Layer"
SRV[server.py<br/>WebSocket Gateway :8765]
BRIDGE[multi_user_bridge.py<br/>Evennia MUD Bridge]
LLAMA[nexus/llama_provider.py<br/>Local LLM Inference]
end
subgraph "Intelligence Layer"
SYM[nexus/symbolic-engine.js<br/>Symbolic Reasoning]
THINK[nexus/nexus_think.py<br/>Consciousness Loop]
PERCEP[nexus/perception_adapter.py<br/>Perception Buffer]
TRAJ[nexus/trajectory_logger.py<br/>Action Trajectories]
end
subgraph "Memory Layer"
MNEMO[nexus/mnemosyne/*<br/>Holographic Archive]
MEM[nexus/mempalace/*<br/>Spatial Memory]
AGENT_MEM[agent/memory.py<br/>Cross-Session Memory]
EXP[nexus/experience_store.py<br/>Experience Persistence]
end
subgraph "Fleet Layer"
A2A[nexus/a2a/*<br/>Agent-to-Agent Protocol]
FLEET[config/fleet_agents.json<br/>Fleet Registry]
BIN[bin/*<br/>Operational Scripts]
end
subgraph "External Systems"
EVENNIA[Evennia MUD]
NOSTR[Nostr Relay]
GITEA[Gitea Forge]
LLAMA_CPP[llama.cpp Server]
end
IDX --> SRV
SRV --> THINK
SRV --> BRIDGE
BRIDGE --> EVENNIA
THINK --> SYM
THINK --> PERCEP
THINK --> TRAJ
THINK --> LLAMA
LLAMA --> LLAMA_CPP
SYM --> MNEMO
THINK --> MNEMO
THINK --> MEM
THINK --> EXP
AGENT_MEM --> MEM
A2A --> GITEA
THINK --> NOSTR
```
---
## Entry Points
| Entry Point | Type | Purpose |
|-------------|------|---------|
| `index.html` | Browser | Main 3D world (Three.js) |
| `server.py` | Python | WebSocket gateway on :8765 |
| `boot.js` | Browser | Module loader, file protocol guard |
| `multi_user_bridge.py` | Python | Evennia MUD ↔ AI agent bridge |
| `nexus/a2a/server.py` | Python | A2A JSON-RPC server |
| `nexus/mnemosyne/cli.py` | CLI | Archive management |
| `bin/nexus_watchdog.py` | Script | Health monitoring |
| `scripts/smoke.mjs` | Script | Smoke tests |
---
## Data Flow
```
User (Browser)
index.html (Three.js 3D world)
├── WebSocket ──► server.py :8765
│ │
│ ├──► nexus_think.py (consciousness loop)
│ │ ├── perception_adapter.py (parse events)
│ │ ├── symbolic-engine.js (reasoning)
│ │ ├── llama_provider.py (inference)
│ │ ├── trajectory_logger.py (action log)
│ │ └── experience_store.py (persistence)
│ │
│ └──► evennia_ws_bridge.py
│ └──► Evennia MUD (telnet :4000)
├── Three.js Scene ──► nexus/components/*
│ ├── memory-particles.js (memory viz)
│ ├── portal-status-wall.html (portals)
│ ├── fleet-health-dashboard.html
│ └── session-rooms.js (spatial rooms)
└── Playground ──► playground/playground.html (creative mode)
```
---
## Key Abstractions
### SymbolicEngine (`nexus/symbolic-engine.js`)
Bitmask-based symbolic reasoning engine. Facts are stored as boolean flags, rules fire when patterns match. Used for world state reasoning without LLM overhead.
### NexusMind (`nexus/nexus_think.py`)
The consciousness loop. Receives perceptions, invokes reasoning, produces actions. The bridge between the 3D world and the AI agent.
### PerceptionBuffer (`nexus/perception_adapter.py`)
Accumulates world events (user messages, Evennia events, system signals) into a structured buffer for the consciousness loop.
### MemPalace (`nexus/mempalace/`, `mempalace/`)
Spatial memory system. Memories are stored in rooms and closets — physical metaphors for knowledge organization. Supports fleet-wide shared memory wings.
### Mnemosyne (`nexus/mnemosyne/`)
Holographic archive. Ingests documents, extracts meaning, builds a graph of linked concepts. The long-term memory layer.
### Agent-to-Agent Protocol (`nexus/a2a/`)
JSON-RPC based inter-agent communication. Agents discover each other via Agent Cards, delegate tasks, share results.
### Multi-User Bridge (`multi_user_bridge.py`)
121K-line Evennia MUD bridge. Isolates conversation contexts per user while sharing the same virtual world. Each user gets their own AIAgent instance.
---
## API Surface
### WebSocket API (server.py :8765)
```
ws://localhost:8765
send: {"type": "perception", "data": {...}}
recv: {"type": "action", "data": {...}}
recv: {"type": "heartbeat", "data": {...}}
```
### A2A JSON-RPC (nexus/a2a/server.py)
```
POST /a2a/v1
{"jsonrpc": "2.0", "method": "SendMessage", "params": {...}}
GET /.well-known/agent-card.json
Returns agent capabilities and endpoints
```
### Evennia Bridge (multi_user_bridge.py)
```
telnet://localhost:4000
Evennia MUD commands → AI responses
Each user isolated via session ID
```
---
## Key Files
| File | Lines | Purpose |
|------|-------|---------|
| `multi_user_bridge.py` | 121K | Evennia MUD bridge (largest file) |
| `index.html` | 21K | Main 3D world |
| `nexus/symbolic-engine.js` | 12K | Symbolic reasoning |
| `nexus/evennia_ws_bridge.py` | 14K | Evennia ↔ WebSocket |
| `nexus/a2a/server.py` | 12K | A2A server |
| `agent/memory.py` | 12K | Cross-session memory |
| `server.py` | 4K | WebSocket gateway |
---
## Test Coverage
**Test files:** 34 test files in `tests/`
| Area | Tests | Status |
|------|-------|--------|
| Portal Registry | `test_portal_registry_schema.py` | ✅ |
| MemPalace | `test_mempalace_*.py` (4 files) | ✅ |
| Nexus Watchdog | `test_nexus_watchdog.py` | ✅ |
| A2A | `test_a2a.py` | ✅ |
| Fleet Audit | `test_fleet_audit.py` | ✅ |
| Provenance | `test_provenance.py` | ✅ |
| Boot | `boot.test.js` | ✅ |
### Coverage Gaps
- **No tests for `multi_user_bridge.py`** (121K lines, zero test coverage)
- **No tests for `server.py` WebSocket gateway**
- **No tests for `nexus/symbolic-engine.js`** (only `symbolic-engine.test.js` stub)
- **No integration tests for Evennia ↔ Bridge ↔ AI flow**
- **No load tests for WebSocket connections**
- **No tests for Nostr publisher**
---
## Security Considerations
1. **WebSocket gateway** runs on `0.0.0.0:8765` — accessible from network. Needs auth or firewall.
2. **No authentication** on WebSocket or A2A endpoints in current code.
3. **Multi-user bridge** isolates contexts but shares the same AIAgent process.
4. **Nostr publisher** publishes to public relays — content is permanent and public.
5. **Fleet scripts** in `bin/` have broad filesystem access.
6. **Systemd services** (`systemd/llama-server.service`) run as root.
---
## Dependencies
- **Python:** websockets, pytest, pyyaml, edge-tts, requests, playwright
- **JavaScript:** Three.js (CDN), Monaco Editor (CDN)
- **External:** Evennia MUD, llama.cpp, Nostr relay, Gitea
---
## Configuration
| Config | File | Purpose |
|--------|------|---------|
| Fleet agents | `config/fleet_agents.json` | Agent registry for A2A |
| MemPalace | `nexus/mempalace/config.py` | Memory paths and settings |
| DeepDive | `config/deepdive_sources.yaml` | Research sources |
| MCP | `mcp_config.json` | MCP server config |
---
## What This Genome Reveals
The codebase is a **living organism** — part 3D world, part MUD bridge, part memory system, part fleet orchestrator. The `multi_user_bridge.py` alone is 121K lines — larger than most entire projects.
**Critical findings:**
1. The 121K-line bridge has zero test coverage
2. WebSocket gateway exposes on 0.0.0.0 without auth
3. No load testing infrastructure exists
4. Symbolic engine test is a stub
5. Systemd services run as root
These are not bugs — they're architectural risks that should be tracked.
---
*Generated by Codebase Genome Pipeline — Issue #672*

View File

@@ -29,7 +29,7 @@ from typing import Any, Callable, Optional
import websockets
from bannerlord_trace import BannerlordTraceLogger
from nexus.bannerlord_trace import BannerlordTraceLogger
# ═══════════════════════════════════════════════════════════════════════════
# CONFIGURATION

View File

@@ -49,6 +49,62 @@ def strip_ansi(text: str) -> str:
return ANSI_RE.sub("", text or "")
def clean_lines(text: str) -> list[str]:
"""Clean ANSI codes and split text into non-empty lines."""
text = strip_ansi(text).replace("\r", "")
return [line.strip() for line in text.split("\n") if line.strip()]
def parse_room_output(text: str) -> dict | None:
"""Parse Evennia room output into structured data."""
lines = clean_lines(text)
if len(lines) < 2:
return None
title = lines[0]
desc = lines[1]
exits = []
objects = []
for line in lines[2:]:
if line.startswith("Exits:"):
raw = line.split(":", 1)[1].strip().replace(" and ", ", ")
exits = [{"key": t.strip(), "destination_id": t.strip().title(), "destination_key": t.strip().title()} for t in raw.split(",") if t.strip()]
elif line.startswith("You see:"):
raw = line.split(":", 1)[1].strip().replace(" and ", ", ")
parts = [t.strip() for t in raw.split(",") if t.strip()]
objects = [{"id": p.removeprefix("a ").removeprefix("an "), "key": p.removeprefix("a ").removeprefix("an "), "short_desc": p} for p in parts]
return {"title": title, "desc": desc, "exits": exits, "objects": objects}
def normalize_event(raw: dict, hermes_session_id: str) -> list[dict]:
"""Normalize raw Evennia event into Nexus event format."""
from nexus.evennia_event_adapter import (
actor_located, command_issued, command_result,
room_snapshot, session_bound,
)
out = []
event = raw.get("event")
actor = raw.get("actor", "Timmy")
timestamp = raw.get("timestamp")
if event == "connect":
out.append(session_bound(hermes_session_id, evennia_account=actor, evennia_character=actor, timestamp=timestamp))
parsed = parse_room_output(raw.get("output", ""))
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
elif event == "command":
cmd = raw.get("command", "")
output = raw.get("output", "")
out.append(command_issued(hermes_session_id, actor, cmd, timestamp=timestamp))
success = not output.startswith("Command '") and not output.startswith("Could not find")
out.append(command_result(hermes_session_id, actor, cmd, strip_ansi(output), success=success, timestamp=timestamp))
parsed = parse_room_output(output)
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
return out
class LogTailer:
"""Async file tailer that yields new lines as they appear."""
@@ -183,56 +239,6 @@ async def live_bridge(log_dir: str, ws_url: str, reconnect_delay: float = 5.0):
async def playback(log_path: Path, ws_url: str):
"""Legacy mode: replay a telemetry JSONL file."""
from nexus.evennia_event_adapter import (
actor_located, command_issued, command_result,
room_snapshot, session_bound,
)
def clean_lines(text: str) -> list[str]:
text = strip_ansi(text).replace("\r", "")
return [line.strip() for line in text.split("\n") if line.strip()]
def parse_room_output(text: str):
lines = clean_lines(text)
if len(lines) < 2:
return None
title = lines[0]
desc = lines[1]
exits = []
objects = []
for line in lines[2:]:
if line.startswith("Exits:"):
raw = line.split(":", 1)[1].strip().replace(" and ", ", ")
exits = [{"key": t.strip(), "destination_id": t.strip().title(), "destination_key": t.strip().title()} for t in raw.split(",") if t.strip()]
elif line.startswith("You see:"):
raw = line.split(":", 1)[1].strip().replace(" and ", ", ")
parts = [t.strip() for t in raw.split(",") if t.strip()]
objects = [{"id": p.removeprefix("a ").removeprefix("an "), "key": p.removeprefix("a ").removeprefix("an "), "short_desc": p} for p in parts]
return {"title": title, "desc": desc, "exits": exits, "objects": objects}
def normalize_event(raw: dict, hermes_session_id: str) -> list[dict]:
out = []
event = raw.get("event")
actor = raw.get("actor", "Timmy")
timestamp = raw.get("timestamp")
if event == "connect":
out.append(session_bound(hermes_session_id, evennia_account=actor, evennia_character=actor, timestamp=timestamp))
parsed = parse_room_output(raw.get("output", ""))
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
elif event == "command":
cmd = raw.get("command", "")
output = raw.get("output", "")
out.append(command_issued(hermes_session_id, actor, cmd, timestamp=timestamp))
success = not output.startswith("Command '") and not output.startswith("Could not find")
out.append(command_result(hermes_session_id, actor, cmd, strip_ansi(output), success=success, timestamp=timestamp))
parsed = parse_room_output(output)
if parsed:
out.append(actor_located(actor, parsed["title"], parsed["title"], timestamp=timestamp))
out.append(room_snapshot(parsed["title"], parsed["title"], parsed["desc"], exits=parsed["exits"], objects=parsed["objects"], timestamp=timestamp))
return out
hermes_session_id = log_path.stem
async with websockets.connect(ws_url) as ws:
for line in log_path.read_text(encoding="utf-8").splitlines():