Compare commits

..

1 Commits

Author SHA1 Message Date
f7843ae87f feat: GENOME.md for evennia-local-world (#677)
Some checks failed
Smoke Test / smoke (pull_request) Failing after 30s
Full codebase analysis:
- Architecture diagram (Mermaid)
- Entry points and data flow
- Key abstractions (6 typeclasses, commands, scripts)
- API surface
- Test coverage gaps (8 critical paths identified)
- Security considerations (6 risks)
- Integration points (Timmy AI, Hermes, Federation)

Closes #677
2026-04-15 03:18:39 +00:00
5 changed files with 242 additions and 353 deletions

View File

@@ -1,61 +0,0 @@
# [PHASE-1] Survival - Keep the Lights On
Phase 1 is the manual-clicker stage of the fleet. The machines exist. The services exist. The human is still the automation loop.
## Phase Definition
- Current state: fleet exists, agents run, everything important still depends on human vigilance.
- Resources tracked here: Capacity, Uptime.
- Next phase: [PHASE-2] Automation - Self-Healing Infrastructure
## Current Buildings
- VPS hosts: Ezra, Allegro, Bezalel
- Agents: Timmy harness, Code Claw heartbeat, Gemini AI Studio worker
- Gitea forge
- Evennia worlds
## Current Resource Snapshot
- Fleet operational: yes
- Uptime baseline: 0.0%
- Days at or above 95% uptime: 0
- Capacity utilization: 0.0%
## Next Phase Trigger
To unlock [PHASE-2] Automation - Self-Healing Infrastructure, the fleet must hold both of these conditions at once:
- Uptime >= 95% for 30 consecutive days
- Capacity utilization > 60%
- Current trigger state: NOT READY
## Missing Requirements
- Uptime 0.0% / 95.0%
- Days at or above 95% uptime: 0/30
- Capacity utilization 0.0% / >60.0%
## Manual Clicker Interpretation
Paperclips analogy: Phase 1 = Manual clicker. You ARE the automation.
Every restart, every SSH, every check is a manual click.
## Manual Clicks Still Required
- Restart agents and services by hand when a node goes dark.
- SSH into machines to verify health, disk, and memory.
- Check Gitea, relay, and world services manually before and after changes.
- Act as the scheduler when automation is missing or only partially wired.
## Repo Signals Already Present
- `scripts/fleet_health_probe.sh` — Automated health probe exists and can supply the uptime baseline for the next phase.
- `scripts/fleet_milestones.py` — Milestone tracker exists, so survival achievements can be narrated and logged.
- `scripts/auto_restart_agent.sh` — Auto-restart tooling already exists as phase-2 groundwork.
- `scripts/backup_pipeline.sh` — Backup pipeline scaffold exists for post-survival automation work.
- `infrastructure/timmy-bridge/reports/generate_report.py` — Bridge reporting exists and can summarize heartbeat-driven uptime.
## Notes
- The fleet is alive, but the human is still the control loop.
- Phase 1 is about naming reality plainly so later automation has a baseline to beat.

View File

@@ -12,7 +12,6 @@ Quick-reference index for common operational tasks across the Timmy Foundation i
| Check fleet health | fleet-ops | `python3 scripts/fleet_readiness.py` |
| Agent scorecard | fleet-ops | `python3 scripts/agent_scorecard.py` |
| View fleet manifest | fleet-ops | `cat manifest.yaml` |
| Render Phase-1 survival report | timmy-home | `python3 scripts/fleet_phase_status.py --output docs/FLEET_PHASE_1_SURVIVAL.md` |
## the-nexus (Frontend + Brain)

View File

@@ -0,0 +1,242 @@
# GENOME.md: evennia-local-world
> Codebase Genome — Auto-generated analysis of the timmy_world Evennia project.
## Project Overview
**Name:** timmy_world
**Framework:** Evennia 6.0 (MUD/MUSH engine)
**Purpose:** Tower MUD world with spatial memory. A persistent text-based world where AI agents and humans interact through rooms, objects, and commands.
**Language:** Python 3.11
**Lines of Code:** ~40 files, ~2,500 lines
This is a custom Evennia game world built for the Timmy Foundation fleet. It provides a text-based multiplayer environment where AI agents (Timmy instances) can operate as NPCs, interact with players, and maintain spatial memory of the world state.
## Architecture
```
timmy_world/
+-- server/
| +-- conf/
| +-- settings.py # Server configuration
| +-- at_initial_setup.py # First-run setup hook
| +-- at_server_startstop.py
| +-- inputfuncs.py # Client input handlers
| +-- lockfuncs.py # Permission lock functions
| +-- cmdparser.py # Command parsing overrides
| +-- connection_screens.py # Login/creation screens
| +-- serversession.py # Session management
| +-- web_plugins.py # Web client plugins
+-- typeclasses/
| +-- characters.py # Player/NPC characters
| +-- rooms.py # Room containers
| +-- objects.py # Items and world objects (218 lines, key module)
| +-- exits.py # Room connectors
| +-- accounts.py # Player accounts (149 lines)
| +-- channels.py # Communication channels
| +-- scripts.py # Persistent background scripts (104 lines)
+-- commands/
| +-- command.py # Base command class (188 lines)
| +-- default_cmdsets.py # Command set definitions
+-- world/
| +-- prototypes.py # Object spawn templates
| +-- help_entries.py # File-based help system
+-- web/
+-- urls.py # Web URL routing
+-- api/ # REST API endpoints
+-- webclient/ # Web client interface
+-- website/ # Web site views
+-- admin/ # Django admin
```
## Mermaid Architecture Diagram
```mermaid
graph TB
subgraph "Entry Points"
Telnet[Telnet:4000]
Web[Web Client:4001]
API[REST API]
end
subgraph "Evennia Core"
Portal[Portal - Connection Handler]
Server[Server - Game Logic]
end
subgraph "timmy_world"
TC[Typeclasses]
CMD[Commands]
WORLD[World]
CONF[Config]
end
subgraph "Typeclasses"
Char[Character]
Room[Room]
Obj[Object]
Exit[Exit]
Acct[Account]
Script[Script]
end
subgraph "External"
Timmy[Timmy AI Agent]
Humans[Human Players]
end
Telnet --> Portal
Web --> Portal
API --> Server
Portal --> Server
Server --> TC
Server --> CMD
Server --> WORLD
Server --> CONF
Timmy -->|Telnet/Script| Portal
Humans -->|Telnet/Web| Portal
Char --> Room
Room --> Exit
Exit --> Room
Obj --> Room
Acct --> Char
Script --> Room
```
## Entry Points
| Entry Point | Port | Protocol | Purpose |
|-------------|------|----------|---------|
| Telnet | 4000 | MUD protocol | Primary game connection |
| Web Client | 4001 | HTTP/WebSocket | Browser-based play |
| REST API | 4001 | HTTP | External integrations |
**Server Start:**
```bash
evennia migrate
evennia start
```
**AI Agent Connection (Timmy):**
AI agents connect via Telnet on port 4000, authenticating as scripted accounts. The `Script` typeclass handles persistent NPC behavior.
## Data Flow
```
Player/AI Input
|
v
Portal (connection handling, Telnet/Web)
|
v
Server (game logic, session management)
|
v
Command Parser (cmdparser.py)
|
v
Command Execution (commands/command.py)
|
v
Typeclass Methods (characters.py, objects.py, etc.)
|
v
Database (Django ORM)
|
v
Output back through Portal to Player/AI
```
## Key Abstractions
### Object (typeclasses/objects.py) — 218 lines
The core world entity. Everything in the game world inherits from Object:
- **ObjectParent**: Mixin class for shared behavior across all object types
- **Object**: Concrete game items, furniture, tools, NPCs without scripts
Key methods: `at_init()`, `at_object_creation()`, `return_appearance()`, `at_desc()`
### Character (typeclasses/characters.py)
Puppetable entities. What players and AI agents control.
- Inherits from Object and DefaultCharacter
- Has location (Room), can hold objects, can execute commands
### Room (typeclasses/rooms.py)
Spatial containers. No location of their own.
- Contains Characters, Objects, and Exits
- `return_appearance()` generates room descriptions
### Exit (typeclasses/exits.py)
Connectors between Rooms. Always has a `destination` property.
- Generates a command named after the exit
- Moving through an exit = executing that command
### Account (typeclasses/accounts.py) — 149 lines
The persistent player identity. Survives across sessions.
- Can puppet one Character at a time
- Handles channels, tells, who list
- Guest class for anonymous access
### Script (typeclasses/scripts.py) — 104 lines
Persistent background processes. No in-game existence.
- Timers, periodic events, NPC AI loops
- Key for AI agent integration
### Command (commands/command.py) — 188 lines
User input handlers. MUX-style command parsing.
- `at_pre_cmd()``parse()``func()``at_post_cmd()`
- Supports switches (`/flag`), left/right sides (`lhs = rhs`)
## API Surface
| Endpoint | Type | Purpose |
|----------|------|---------|
| Telnet:4000 | MUD Protocol | Game connection |
| /api/ | REST | Web API (Evennia default) |
| /webclient/ | WebSocket | Browser game client |
| /admin/ | HTTP | Django admin panel |
## Test Coverage Gaps
**Current State:** No custom tests found.
**Missing Tests:**
1. **Object lifecycle**: `at_object_creation`, `at_init`, `delete`
2. **Room navigation**: Exit creation, movement between rooms
3. **Command parsing**: Switch handling, lhs/rhs splitting
4. **Account authentication**: Login flow, guest creation
5. **Script persistence**: Start, stop, timer accuracy
6. **Lock function evaluation**: Permission checks
7. **AI agent integration**: Telnet connection, command execution as NPC
8. **Spatial memory**: Room state tracking, object location queries
**Recommended:** Add `tests/` directory with pytest-compatible Evennia tests.
## Security Considerations
1. **Telnet is unencrypted** — All MUD traffic is plaintext. Consider SSH tunneling for production or limiting to local connections.
2. **Lock functions** — Custom lockfuncs.py defines permission checks. Review for bypass vulnerabilities.
3. **Web API** — Ensure Django admin is restricted to trusted IPs.
4. **Guest accounts** — Guest class exists. Limit permissions to prevent abuse.
5. **Script execution** — Scripts run server-side Python. Arbitrary script creation is a security risk if not locked down.
6. **AI agent access** — Timmy connects as a regular account. Ensure agent accounts have appropriate permission limits.
## Dependencies
- **Evennia 6.0** — MUD/MUSH framework (Django + Twisted)
- **Python 3.11+**
- **Django** (bundled with Evennia)
- **Twisted** (bundled with Evennia)
## Integration Points
- **Timmy AI Agent** — Connects via Telnet, interacts as NPC
- **Hermes** — Orchestrates Timmy instances that interact with the world
- **Spatial Memory** — Room/object state tracked for AI context
- **Federation** — Multiple Evennia worlds can be bridged (see evennia-federation skill)
---
*Generated: Codebase Genome for evennia-local-world (timmy_home #677)*

View File

@@ -1,224 +0,0 @@
#!/usr/bin/env python3
"""Render the current fleet survival phase as a durable report."""
from __future__ import annotations
import argparse
import json
from copy import deepcopy
from pathlib import Path
from typing import Any
PHASE_NAME = "[PHASE-1] Survival - Keep the Lights On"
NEXT_PHASE_NAME = "[PHASE-2] Automation - Self-Healing Infrastructure"
TARGET_UPTIME_PERCENT = 95.0
TARGET_UPTIME_DAYS = 30
TARGET_CAPACITY_PERCENT = 60.0
DEFAULT_BUILDINGS = [
"VPS hosts: Ezra, Allegro, Bezalel",
"Agents: Timmy harness, Code Claw heartbeat, Gemini AI Studio worker",
"Gitea forge",
"Evennia worlds",
]
DEFAULT_MANUAL_CLICKS = [
"Restart agents and services by hand when a node goes dark.",
"SSH into machines to verify health, disk, and memory.",
"Check Gitea, relay, and world services manually before and after changes.",
"Act as the scheduler when automation is missing or only partially wired.",
]
REPO_SIGNAL_FILES = {
"scripts/fleet_health_probe.sh": "Automated health probe exists and can supply the uptime baseline for the next phase.",
"scripts/fleet_milestones.py": "Milestone tracker exists, so survival achievements can be narrated and logged.",
"scripts/auto_restart_agent.sh": "Auto-restart tooling already exists as phase-2 groundwork.",
"scripts/backup_pipeline.sh": "Backup pipeline scaffold exists for post-survival automation work.",
"infrastructure/timmy-bridge/reports/generate_report.py": "Bridge reporting exists and can summarize heartbeat-driven uptime.",
}
DEFAULT_SNAPSHOT = {
"fleet_operational": True,
"resources": {
"uptime_percent": 0.0,
"days_at_or_above_95_percent": 0,
"capacity_utilization_percent": 0.0,
},
"current_buildings": DEFAULT_BUILDINGS,
"manual_clicks": DEFAULT_MANUAL_CLICKS,
"notes": [
"The fleet is alive, but the human is still the control loop.",
"Phase 1 is about naming reality plainly so later automation has a baseline to beat.",
],
}
def default_snapshot() -> dict[str, Any]:
return deepcopy(DEFAULT_SNAPSHOT)
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
result = deepcopy(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(result.get(key), dict):
result[key] = _deep_merge(result[key], value)
else:
result[key] = value
return result
def load_snapshot(snapshot_path: Path | None = None) -> dict[str, Any]:
snapshot = default_snapshot()
if snapshot_path is None:
return snapshot
override = json.loads(snapshot_path.read_text(encoding="utf-8"))
return _deep_merge(snapshot, override)
def collect_repo_signals(repo_root: Path) -> list[str]:
signals: list[str] = []
for rel_path, description in REPO_SIGNAL_FILES.items():
if (repo_root / rel_path).exists():
signals.append(f"`{rel_path}` — {description}")
return signals
def compute_phase_status(snapshot: dict[str, Any], repo_root: Path | None = None) -> dict[str, Any]:
repo_root = repo_root or Path(__file__).resolve().parents[1]
resources = snapshot.get("resources", {})
uptime_percent = float(resources.get("uptime_percent", 0.0))
uptime_days = int(resources.get("days_at_or_above_95_percent", 0))
capacity_percent = float(resources.get("capacity_utilization_percent", 0.0))
fleet_operational = bool(snapshot.get("fleet_operational", False))
missing: list[str] = []
if not fleet_operational:
missing.append("Fleet operational flag is false.")
if uptime_percent < TARGET_UPTIME_PERCENT:
missing.append(f"Uptime {uptime_percent:.1f}% / {TARGET_UPTIME_PERCENT:.1f}%")
if uptime_days < TARGET_UPTIME_DAYS:
missing.append(f"Days at or above 95% uptime: {uptime_days}/{TARGET_UPTIME_DAYS}")
if capacity_percent <= TARGET_CAPACITY_PERCENT:
missing.append(f"Capacity utilization {capacity_percent:.1f}% / >{TARGET_CAPACITY_PERCENT:.1f}%")
return {
"title": PHASE_NAME,
"current_phase": "PHASE-1 Survival",
"fleet_operational": fleet_operational,
"resources": {
"uptime_percent": uptime_percent,
"days_at_or_above_95_percent": uptime_days,
"capacity_utilization_percent": capacity_percent,
},
"current_buildings": list(snapshot.get("current_buildings", DEFAULT_BUILDINGS)),
"manual_clicks": list(snapshot.get("manual_clicks", DEFAULT_MANUAL_CLICKS)),
"notes": list(snapshot.get("notes", [])),
"repo_signals": collect_repo_signals(repo_root),
"next_phase": NEXT_PHASE_NAME,
"next_phase_ready": fleet_operational and not missing,
"missing_requirements": missing,
}
def render_markdown(status: dict[str, Any]) -> str:
resources = status["resources"]
missing = status["missing_requirements"]
ready_line = "READY" if status["next_phase_ready"] else "NOT READY"
lines = [
f"# {status['title']}",
"",
"Phase 1 is the manual-clicker stage of the fleet. The machines exist. The services exist. The human is still the automation loop.",
"",
"## Phase Definition",
"",
"- Current state: fleet exists, agents run, everything important still depends on human vigilance.",
"- Resources tracked here: Capacity, Uptime.",
f"- Next phase: {status['next_phase']}",
"",
"## Current Buildings",
"",
]
lines.extend(f"- {item}" for item in status["current_buildings"])
lines.extend([
"",
"## Current Resource Snapshot",
"",
f"- Fleet operational: {'yes' if status['fleet_operational'] else 'no'}",
f"- Uptime baseline: {resources['uptime_percent']:.1f}%",
f"- Days at or above 95% uptime: {resources['days_at_or_above_95_percent']}",
f"- Capacity utilization: {resources['capacity_utilization_percent']:.1f}%",
"",
"## Next Phase Trigger",
"",
f"To unlock {status['next_phase']}, the fleet must hold both of these conditions at once:",
f"- Uptime >= {TARGET_UPTIME_PERCENT:.0f}% for {TARGET_UPTIME_DAYS} consecutive days",
f"- Capacity utilization > {TARGET_CAPACITY_PERCENT:.0f}%",
f"- Current trigger state: {ready_line}",
"",
"## Missing Requirements",
"",
])
if missing:
lines.extend(f"- {item}" for item in missing)
else:
lines.append("- None. Phase 2 can unlock now.")
lines.extend([
"",
"## Manual Clicker Interpretation",
"",
"Paperclips analogy: Phase 1 = Manual clicker. You ARE the automation.",
"Every restart, every SSH, every check is a manual click.",
"",
"## Manual Clicks Still Required",
"",
])
lines.extend(f"- {item}" for item in status["manual_clicks"])
lines.extend([
"",
"## Repo Signals Already Present",
"",
])
if status["repo_signals"]:
lines.extend(f"- {item}" for item in status["repo_signals"])
else:
lines.append("- No survival-adjacent repo signals detected.")
if status["notes"]:
lines.extend(["", "## Notes", ""])
lines.extend(f"- {item}" for item in status["notes"])
return "\n".join(lines).rstrip() + "\n"
def main() -> None:
parser = argparse.ArgumentParser(description="Render the fleet phase-1 survival report")
parser.add_argument("--snapshot", help="Optional JSON snapshot overriding the default phase-1 baseline")
parser.add_argument("--output", help="Write markdown report to this path")
parser.add_argument("--json", action="store_true", help="Print computed status as JSON instead of markdown")
args = parser.parse_args()
snapshot = load_snapshot(Path(args.snapshot).expanduser() if args.snapshot else None)
repo_root = Path(__file__).resolve().parents[1]
status = compute_phase_status(snapshot, repo_root=repo_root)
if args.json:
rendered = json.dumps(status, indent=2)
else:
rendered = render_markdown(status)
if args.output:
output_path = Path(args.output).expanduser()
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(rendered, encoding="utf-8")
print(f"Phase status written to {output_path}")
else:
print(rendered)
if __name__ == "__main__":
main()

View File

@@ -1,67 +0,0 @@
from __future__ import annotations
import importlib.util
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT_PATH = ROOT / "scripts" / "fleet_phase_status.py"
DOC_PATH = ROOT / "docs" / "FLEET_PHASE_1_SURVIVAL.md"
def _load_module(path: Path, name: str):
assert path.exists(), f"missing {path.relative_to(ROOT)}"
spec = importlib.util.spec_from_file_location(name, path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def test_compute_phase_status_tracks_survival_gate_requirements() -> None:
mod = _load_module(SCRIPT_PATH, "fleet_phase_status")
status = mod.compute_phase_status(
{
"fleet_operational": True,
"resources": {
"uptime_percent": 94.5,
"days_at_or_above_95_percent": 12,
"capacity_utilization_percent": 45.0,
},
}
)
assert status["current_phase"] == "PHASE-1 Survival"
assert status["next_phase_ready"] is False
assert any("94.5% / 95.0%" in item for item in status["missing_requirements"])
assert any("12/30" in item for item in status["missing_requirements"])
assert any("45.0% / >60.0%" in item for item in status["missing_requirements"])
def test_render_markdown_preserves_phase_buildings_and_manual_clicker_language() -> None:
mod = _load_module(SCRIPT_PATH, "fleet_phase_status")
status = mod.compute_phase_status(mod.default_snapshot())
report = mod.render_markdown(status)
for snippet in (
"# [PHASE-1] Survival - Keep the Lights On",
"VPS hosts: Ezra, Allegro, Bezalel",
"Timmy harness",
"Gitea forge",
"Evennia worlds",
"Every restart, every SSH, every check is a manual click.",
):
assert snippet in report
def test_repo_contains_generated_phase_1_doc() -> None:
assert DOC_PATH.exists(), "missing committed phase-1 survival doc"
text = DOC_PATH.read_text(encoding="utf-8")
for snippet in (
"# [PHASE-1] Survival - Keep the Lights On",
"## Current Buildings",
"## Next Phase Trigger",
"## Manual Clicker Interpretation",
):
assert snippet in text