Files
timmy-home/evennia_tools/mind_palace.py
Alexander Whitestone ef5e0ec439
Some checks failed
Smoke Test / smoke (pull_request) Failing after 11s
feat: add evennia mind palace architecture (refs #567)
2026-04-15 00:20:31 -04:00

271 lines
9.6 KiB
Python

from __future__ import annotations
from dataclasses import asdict, dataclass
HALL_OF_KNOWLEDGE = "Hall of Knowledge"
LEDGER_OBJECT = "The Ledger"
@dataclass(frozen=True)
class MindPalaceIssue:
issue_number: int
state: str
title: str
layer: str
spatial_role: str
rationale: str
def summary_line(self) -> str:
return f"#{self.issue_number} {self.title} [{self.state} · {self.layer} · {self.spatial_role}]"
@dataclass(frozen=True)
class MutableFact:
key: str
value: str
source: str
def to_dict(self) -> dict[str, str]:
return asdict(self)
@dataclass(frozen=True)
class BurnCycleSnapshot:
repo: str
branch: str
active_issue: int
focus: str
active_operator: str
blockers: tuple[str, ...] = ()
def to_dict(self) -> dict[str, object]:
return {
"repo": self.repo,
"branch": self.branch,
"active_issue": self.active_issue,
"focus": self.focus,
"active_operator": self.active_operator,
"blockers": list(self.blockers),
}
EVENNIA_MIND_PALACE_ISSUES = (
MindPalaceIssue(
508,
"closed",
"[P0] Tower Game — contextual dialogue (NPCs recycle 15 lines forever)",
"L4",
"Dialogue tutor NPCs",
"Contextual dialogue belongs in procedural behavior surfaces so the right NPC can teach or respond based on current room state.",
),
MindPalaceIssue(
509,
"closed",
"[P0] Tower Game — trust must decrease, conflict must exist",
"L2",
"Mutable relationship state",
"Trust, resentment, and alliance changes are world facts that should live on objects and characters, not in flat prompt text.",
),
MindPalaceIssue(
510,
"closed",
"[P0] Tower Game — narrative arc (tick 200 = tick 20)",
"L3",
"Archive chronicle",
"A spatial memory needs a chronicle room where prior events can be replayed and searched so the world can develop an actual arc.",
),
MindPalaceIssue(
511,
"open",
"[P0] Tower Game — energy must meaningfully constrain",
"L2",
"Mutable world meter",
"Energy is a changing state variable that should be visible in-room and affect what actions remain possible.",
),
MindPalaceIssue(
512,
"open",
"[P1] Sonnet workforce — full end-to-end smoke test",
"L3",
"Proof shelf",
"End-to-end smoke traces belong in the archive so world behavior can be proven, revisited, and compared over time.",
),
MindPalaceIssue(
513,
"open",
"[P1] Tower Game — world events must affect gameplay",
"L2",
"Event-reactive room state",
"If storms, fire, or decay do not alter the room state, the world is decorative instead of mnemonic.",
),
MindPalaceIssue(
514,
"open",
"[P1] Tower Game — items that change the world",
"L2",
"Interactive objects",
"World-changing items are exactly the kind of mutable facts and affordances that a spatial memory substrate should expose.",
),
MindPalaceIssue(
515,
"open",
"[P1] Tower Game — NPC-NPC relationships",
"L2",
"Social graph in-world",
"Relationships should persist on characters and become inspectable through spatial proximity rather than hidden transcript-only state.",
),
MindPalaceIssue(
516,
"closed",
"[P1] Tower Game — Timmy richer dialogue + internal monologue",
"L4",
"Inner-room teaching patterns",
"Internal monologue and richer dialogue are procedural behaviors that can be attached to rooms, NPCs, and character routines.",
),
MindPalaceIssue(
517,
"open",
"[P1] Tower Game — NPCs move between rooms with purpose",
"L5",
"Movement-driven retrieval",
"Purposeful movement is retrieval logic made spatial: who enters which room determines what knowledge is loaded and acted on.",
),
MindPalaceIssue(
534,
"open",
"[BEZ-P0] Fix Evennia settings on 104.131.15.18 — remove bad port tuples, DB is ready",
"L1",
"Runtime threshold",
"Before the mind palace can be inhabited, the base Evennia runtime topology has to load cleanly at the threshold.",
),
MindPalaceIssue(
535,
"open",
"[BEZ-P0] Install Tailscale on Bezalel VPS (104.131.15.18) for internal networking",
"L1",
"Network threshold",
"Network identity and reachability are static environment facts that determine which rooms and worlds are even reachable.",
),
MindPalaceIssue(
536,
"open",
"[BEZ-P1] Create Bezalel Evennia world with themed rooms and characters",
"L1",
"First room graph",
"Themed rooms and characters are the static world scaffold that lets memory become place instead of prose.",
),
MindPalaceIssue(
537,
"closed",
"[BRIDGE-P1] Deploy Evennia bridge API on all worlds — sync presence and events",
"L5",
"Cross-world routing",
"Bridge APIs turn movement across worlds into retrieval across houses instead of forcing one global prompt blob.",
),
MindPalaceIssue(
538,
"closed",
"[ALLEGRO-P1] Fix SSH access from Mac to Allegro VPS (167.99.126.228)",
"L1",
"Operator ingress",
"Operator access is part of the static world boundary: if the house cannot be reached, its memory cannot be visited.",
),
MindPalaceIssue(
539,
"closed",
"[ARCH-P2] Implement Evennia hub-and-spoke federation architecture",
"L5",
"Federated retrieval map",
"Federation turns room-to-room travel into selective retrieval across sovereign worlds instead of a single central cache.",
),
)
OPEN_EVENNIA_MIND_PALACE_ISSUES = tuple(issue for issue in EVENNIA_MIND_PALACE_ISSUES if issue.state == "open")
def build_hall_of_knowledge_entry(
active_issues: tuple[MindPalaceIssue, ...] | list[MindPalaceIssue],
ledger_fact: MutableFact,
burn_cycle: BurnCycleSnapshot,
) -> dict[str, object]:
issue_lines = [issue.summary_line() for issue in active_issues]
blocker_lines = list(burn_cycle.blockers) or ["No blockers recorded."]
return {
"room": {
"key": HALL_OF_KNOWLEDGE,
"purpose": "Load live issue topology, current burn-cycle focus, and the minimum durable facts Timmy needs before acting.",
},
"object": {
"key": LEDGER_OBJECT,
"purpose": "Expose one mutable fact from Timmy's durable memory so the room proves stateful recall instead of static documentation.",
"fact": ledger_fact.to_dict(),
},
"ambient_context": [
f"Room entry into {HALL_OF_KNOWLEDGE} preloads active Gitea issue topology for {burn_cycle.repo}.",
*issue_lines,
f"Ledger fact {ledger_fact.key}: {ledger_fact.value}",
f"Timmy burn cycle focus: issue #{burn_cycle.active_issue} on {burn_cycle.branch}{burn_cycle.focus}",
f"Operator lane: {burn_cycle.active_operator}",
],
"burn_cycle": burn_cycle.to_dict(),
"commands": {
"/who lives here": "; ".join(issue_lines) or "No issues loaded.",
"/status forge": f"{burn_cycle.repo} @ {burn_cycle.branch} (issue #{burn_cycle.active_issue})",
"/what is broken": "; ".join(blocker_lines),
},
}
def render_room_entry_proof(
active_issues: tuple[MindPalaceIssue, ...] | list[MindPalaceIssue],
ledger_fact: MutableFact,
burn_cycle: BurnCycleSnapshot,
) -> str:
entry = build_hall_of_knowledge_entry(active_issues, ledger_fact, burn_cycle)
lines = [
f"ENTER {entry['room']['key']}",
f"Purpose: {entry['room']['purpose']}",
"Ambient context:",
]
lines.extend(f"- {line}" for line in entry["ambient_context"])
lines.extend(
[
f"Object: {entry['object']['key']}",
f"- {entry['object']['fact']['key']}: {entry['object']['fact']['value']}",
f"- source: {entry['object']['fact']['source']}",
"Timmy burn cycle:",
f"- repo: {burn_cycle.repo}",
f"- branch: {burn_cycle.branch}",
f"- active issue: #{burn_cycle.active_issue}",
f"- focus: {burn_cycle.focus}",
f"- operator: {burn_cycle.active_operator}",
"Command surfaces:",
f"- /who lives here -> {entry['commands']['/who lives here']}",
f"- /status forge -> {entry['commands']['/status forge']}",
f"- /what is broken -> {entry['commands']['/what is broken']}",
]
)
return "\n".join(lines)
def demo_room_entry_proof() -> str:
return render_room_entry_proof(
active_issues=OPEN_EVENNIA_MIND_PALACE_ISSUES[:3],
ledger_fact=MutableFact(
key="canonical-evennia-body",
value="timmy_world on localhost:4001 remains the canonical local body while room entry preloads live issue topology.",
source="reports/production/2026-03-28-evennia-world-proof.md",
),
burn_cycle=BurnCycleSnapshot(
repo="Timmy_Foundation/timmy-home",
branch="fix/567",
active_issue=567,
focus="Evennia as Agent Mind Palace — Spatial Memory Architecture",
active_operator="BURN-7-1",
blockers=("Comment on issue #567 with room-entry proof after PR creation",),
),
)