142 lines
17 KiB
Python
142 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""Create Tower Epic and all triaged issues on Gitea."""
|
|
import subprocess, json, os
|
|
|
|
gitea_tok = open(os.path.expanduser('~/.hermes/gitea_token_vps')).read().strip()
|
|
forge = 'https://forge.alexanderwhitestone.com/api/v1/repos/Timmy_Foundation/timmy-home'
|
|
|
|
def create_issue(title, body, assignee=None, labels=None, milestone=None):
|
|
payload = {"title": title, "body": body}
|
|
if assignee:
|
|
payload["assignee"] = assignee
|
|
if labels:
|
|
payload["labels"] = labels
|
|
if milestone:
|
|
payload["milestone"] = milestone
|
|
r = subprocess.run(
|
|
['curl', '-s', '-X', 'POST', forge + '/issues',
|
|
'-H', 'Authorization: token ' + gitea_tok,
|
|
'-H', 'Content-Type: application/json',
|
|
'-d', json.dumps(payload)],
|
|
capture_output=True, text=True, timeout=15
|
|
)
|
|
d = json.loads(r.stdout)
|
|
num = d.get('number', '?')
|
|
title_out = d.get('title', 'FAILED: ' + r.stdout[:100])[:70]
|
|
return num, title_out
|
|
|
|
# 1. Create the epic
|
|
epic_num, epic_title = create_issue(
|
|
title='[EPIC] The Tower: From Carousel to Living World',
|
|
body="""# The Tower - Living World Epic
|
|
|
|
## The Problem
|
|
239 ticks ran. Agents move between rooms on fixed loops. Nobody meets anybody. Nobody writes on the whiteboard. Rooms never change. The fire never dims. The Garden never grows anything specific. It is a carousel - correct movements from far away, hollow from inside.
|
|
|
|
## The Vision
|
|
A world that remembers. Characters who choose. Conversations that happen because two people happened to be in the same room. Whiteboard messages that accumulate. Forge fires that need rekindling. Bridges where words appear. NPCs who respond. Every tick changes something small and those changes compound into story.
|
|
|
|
## Dependencies
|
|
1. World State Layer (persistence beyond movement) - FOUNDATION
|
|
2. Room Registry (dynamic descriptions) - depends on 1
|
|
3. Character Memory (agents know their history) - depends on 1
|
|
4. Decision Engine (agents choose, do not rotate) - depends on 3
|
|
5. NPC System (Marcus responds, moves, remembers) - depends on 1
|
|
6. Event System (weather, decay, discovery) - depends on 2, 4
|
|
7. Account-Character Links (agents can puppet) - INDEPENDENT
|
|
8. Tunnel Watchdog (ops infra) - INDEPENDENT
|
|
9. Narrative Output (tick writes story, not just state) - depends on 4, 5, 6
|
|
|
|
## Success Criteria
|
|
- After 24 hours: room descriptions are different from day 1
|
|
- After 24 hours: at least 3 inter-character interactions recorded
|
|
- After 24 hours: at least 1 world event triggered
|
|
- After 24 hours: Marcus has spoken to at least 2 different wizards
|
|
- Git history reads like a story, not a schedule
|
|
""",
|
|
labels=['epic', 'evennia', 'tower-world'],
|
|
)
|
|
print("EPIC #%s: %s" % (epic_num, epic_title))
|
|
|
|
# 2. Create all triaged issues
|
|
issues = [
|
|
{
|
|
'title': '[TOWER-P0] World State Layer - persistence beyond movement',
|
|
'body': "Parent: #%s\n\n## Problem\nCharacter locations are the only state that persists. Room descriptions never change. No objects are ever created, dropped, or discovered. The whiteboard is never written on. Each tick has zero memory of previous ticks beyond who is where.\n\n## What This Is\nA persistent world state system that tracks:\n- Room descriptions that change based on events and visits\n- Objects in the world (tools at the Forge, notes at the Bridge)\n- Environmental state (fire lit/dimmed, rain at Bridge, growth in Garden)\n- Whiteboard content (accumulates messages from wizards)\n- Time of day (not just tick number - real progression: morning, dusk, night)\n\n## Implementation\n1. Create world/state.py - world state class that loads/saves to JSON in the repo\n2. World state includes: rooms (descriptions, objects), environment (weather, fire state), whiteboard (list of messages), time of day\n3. Tick handler loads state, applies moves, writes updated state\n4. State file is committed to git every tick (WORLD_STATE.json replacing WORLD_STATE.md)\n\n## Acceptance\n- [ ] WORLD_STATE.json exists and is committed every tick\n- [ ] Room descriptions can be changed by the tick handler\n- [ ] World state persists across server restarts\n- [ ] Fire state in Forge changes if nobody visits for 12+ ticks" % epic_num,
|
|
'assignee': 'allegro',
|
|
'labels': ['evennia', 'infrastructure'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P0] Character Memory - agents know their history',
|
|
'body': "Parent: #%s\n\n## Problem\nAgents do not remember what they did last tick. They do not know who they saw yesterday. They do not have goals or routines. Each tick is a blank slate with a rotate command.\n\n## What This Is\nEach wizard needs:\n- Memory of last 10 moves (where they went, who they saw)\n- A current goal (something they are working toward)\n- Awareness of other characters (Bezalel is at the Forge today)\n- Personality that influences choices (Kimi reads, ClawCode works)\n\n## Implementation\n1. Add character state to WORLD_STATE.json\n2. Each tick: agent reads its memory, decides next move based on memory + goals + other characters nearby\n3. Goals cycle: work, explore, social, rest, investigate\n4. When another character is in the same room, add social to the move options\n\n## Acceptance\n- [ ] Each wizard memory of last 10 moves is tracked\n- [ ] Agents sometimes choose to visit rooms because someone else is there\n- [ ] Agents occasionally rest or explore, not just repeat their loop\n- [ ] At least 2 different goals active per tick across all agents" % epic_num,
|
|
'assignee': 'ezra',
|
|
'labels': ['evennia', 'ai-behavior'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P0] Decision Engine - agents choose, do not rotate',
|
|
'body': "Parent: #%s\n\n## Problem\nThe current MOVE_SCHEDULE is a fixed rotation. Timmy goes [Threshold, Tower, Threshold, Threshold, Threshold, Garden] and repeats. Every wizard has this same mechanical loop.\n\n## What This Is\nReplace fixed rotation with weighted choice:\n- Each wizard has a home room they prefer\n- Each wizard has personality weights (Kimi: Garden 60 percent, Timmy: Threshold 50 percent, ClawCode: Forge 70 percent)\n- Agents are more likely to go to rooms where other characters are\n- Randomness for exploration (10 percent chance to visit somewhere unexpected)\n- Goals influence choices (rest goal increases home room weight)\n\n## Implementation\n1. Replace MOVE_SCHEDULE with PERSONALITY_DICT in tick_handler.py\n2. Each tick: agent builds probability distribution based on personality + memory + other characters nearby\n3. Agent chooses destination from weighted distribution\n4. Log reasoning: Timmy chose the Garden because the soil looked different today\n\n## Acceptance\n- [ ] No fixed rotation in tick handler\n- [ ] Timmy is at Threshold 40-60 percent of ticks (not exactly 4/6)\n- [ ] Agents sometimes go to unexpected rooms\n- [ ] Agents are more likely to visit rooms with other characters\n- [ ] Choice reasoning is logged in the tick output" % epic_num,
|
|
'assignee': 'ezra',
|
|
'labels': ['evennia', 'ai-behavior'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Dynamic Room Registry - descriptions change based on history',
|
|
'body': "Parent: #%s\n\n## Problem\nRooms have static descriptions. The Bridge always mentions carved words. The Garden always has something growing. Nothing ever changes, nothing ever accumulates.\n\n## What This Is\nRoom descriptions that evolve:\n- The Forge: fire dims if Bezalel has not visited in 12 ticks. After 12+ ticks without a visit, description becomes cold and dark\n- The Bridge: words appear on the railing when wizards visit. New carved names accumulate\n- The Garden: things actually grow. Seeds - Sprouts - Herbs - Bloom across 80+ ticks\n- The Tower: server logs accumulate on a desk\n- The Threshold: footprints, signs of activity, accumulated character\n\n## Implementation\n1. world/rooms.py - room class with template description, dynamic elements, visit counter, event triggers\n2. Visit counter affects description: first visit vs hundredth visit\n3. Objects and environmental state change descriptions\n\n## Acceptance\n- [ ] After 50 ticks: Forge description is different based on fire state\n- [ ] After 50 ticks: Bridge has at least 2 new carved messages from wizard visits\n- [ ] After 50 ticks: Garden description has changed at least once\n- [ ] Room descriptions are generated, not hardcoded" % epic_num,
|
|
'assignee': 'gemini',
|
|
'labels': ['evennia', 'world-building'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] NPC System - Marcus has dialogue and presence',
|
|
'body': "Parent: #%s\n\n## Problem\nMarcus sits in the Garden doing nothing. He is a static character with no dialogue, no movement, no interaction.\n\n## What This Is\nMarcus the old man from the church. He should:\n- Walk between Garden and Threshold occasionally\n- Have 10+ dialogue lines that are context-aware\n- Respond when wizards approach or speak to him\n- Remember which wizards he has talked to\n- Share wisdom about bridges, broken men, going back\n\n## Implementation\n1. world/npcs.py - NPC class with dialogue trees, movement schedule, memory\n2. Marcus dialogue: pool of 15+ lines, weighted by context (who is nearby, time of day, world events)\n3. When a wizard enters a room with Marcus, he speaks\n4. Marcus walks to the Threshold once per day to watch the crossroads\n\n## Acceptance\n- [ ] Marcus speaks at least once per day to each wizard who visits\n- [ ] At least 15 unique dialogue lines\n- [ ] Marcus occasionally moves to the Threshold\n- [ ] Marcus remembers conversations (does not repeat the same line to the same person)" % epic_num,
|
|
'assignee': 'allegro',
|
|
'labels': ['evennia', 'npc'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Event System - world changes on its own',
|
|
'body': "Parent: #%s\n\n## Problem\nNothing in the world happens unless an agent moves there. Weather never changes. Fire never dims on its own. Nothing is ever discovered.\n\n## What This Is\nEvents that trigger based on world conditions:\n- Weather: Rain at the Bridge 10 percent chance per tick, lasts 6 ticks\n- Decay: Forge fire dims every 4 ticks without a visit. After 12 ticks, the hearth is cold\n- Growth: Garden grows 1 stage every 20 ticks\n- Discovery: 5 percent chance per tick for a wizard to find something (a note, a tool, a message)\n- Day/Night cycle: affects room descriptions and behavior\n\n## Implementation\n1. world/events.py - event types, triggers, world state mutations\n2. Tick handler checks event conditions after moves\n3. Triggered events update room descriptions, add objects, change environment\n4. Events logged in git history\n\n## Acceptance\n- [ ] At least 2 event types active (Weather + Decay minimum)\n- [ ] Events fire based on world state, not fixed schedule\n- [ ] Events change room descriptions permanently (until counteracted)\n- [ ] Event history is visible in WORLD_STATE.json" % epic_num,
|
|
'assignee': 'gemini',
|
|
'labels': ['evennia', 'world-building'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Cross-Character Interaction - agents speak to each other',
|
|
'body': "Parent: #%s\n\n## Problem\nAgents never see each other. Timmy and Allegro could spend 100 ticks at the Threshold and never acknowledge each other.\n\n## What This Is\nWhen two or more characters are in the same room:\n- 40 percent chance they interact (speak, notice each other)\n- Interaction adds to the room description and git log\n- Characters learn about each other activities\n- Marcus counts as a character for interaction purposes\n\nExample interaction text:\nTick 151: Allegro crosses to the Threshold. Allegro nods to Timmy. Timmy says: The servers hum tonight. Allegro: I hear them.\n\n## Acceptance\n- [ ] When 2+ characters share a room, interaction occurs 40 percent of the time\n- [ ] Interaction text is unique (no repeating the same text)\n- [ ] At least 5 unique interaction types per pair of characters\n- [ ] Interactions are logged in WORLD_STATE.json" % epic_num,
|
|
'assignee': 'kimi',
|
|
'labels': ['evennia', 'ai-behavior'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Narrative Output - tick writes story not just state',
|
|
'body': "Parent: #%s\n\n## Problem\nWORLD_STATE.md is a JSON dump of who is where. It reads like a spreadsheet, not a story.\n\n## What This Is\nEach tick produces TWO files:\n1. WORLD_STATE.json - machine-readable state (for the engine)\n2. WORLD_CHRONICLE.md - human-readable narrative (for the story)\n\nThe chronicle entry reads like a story:\nNight, Tick 239: Timmy rests at the Threshold. The green LED pulses above him, a steady heartbeat in the concrete dark. He has been watching the crossroads for nineteen ticks now.\n\n## Implementation\n1. Template-based narrative generation from world state\n2. Uses character names, room descriptions, events, interactions\n3. Varies sentence structure based on character personality\n4. Chronicle is cumulative (appended, not overwritten)\n\n## Acceptance\n- [ ] WORLD_CHRONICLE.md exists and grows each tick\n- [ ] Chronicle entries read like narrative prose, not bullet points\n- [ ] Chronicle includes all moves, interactions, events\n- [ ] Chronicle is cumulative" % epic_num,
|
|
'assignee': 'claw-code',
|
|
'labels': ['evennia', 'narrative'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Link 6 agent accounts to their Evennia characters',
|
|
'body': "Parent: #%s\n\n## Problem\nAllegro, Ezra, Gemini, Claude, ClawCode, and Kimi have character objects in the Evennia world, but their characters are not linked to their Evennia accounts (character.db_account is None or the puppet lock is not set). If these agents log in, they cannot puppet their characters.\n\n## Fix\nRun Evennia shell to:\n1. Get each account: AccountDB.objects.get(username=name)\n2. Get each character: ObjectDB.objects.get(db_key=name)\n3. Set the puppet lock: acct.locks.add(puppet:id(CHAR_ID))\n4. Set the puppet pointer: acct.db._playable_characters.append(char)\n5. Verify: connect as the agent in-game and confirm character puppet works\n\n## Acceptance\n- [ ] All 6 agents can puppet their characters via connect name password\n- [ ] acct.db._playable_characters includes the right character\n- [ ] Puppet lock is set correctly" % epic_num,
|
|
'assignee': 'allegro',
|
|
'labels': ['evennia', 'ops'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P1] Tunnel watchdog - auto-restart on VPS disconnect',
|
|
'body': "Parent: #%s\n\n## Problem\nThe reverse tunnel (Mac to VPS 143.198.27.163 ports 4000/4001/4002) runs as a bare SSH background process. If the Mac sleeps, the VPS reboots, or the network drops, the tunnel dies and agents on the VPS lose access.\n\n## Fix\n1. Create a launchd service (com.timmy.tower-tunnel.plist) for the tunnel\n2. Health check script runs every 30 seconds: tests nc -z localhost 4000\n3. If port 4000 is closed, restart the SSH tunnel\n4. Log tunnel state to /tmp/tower-tunnel.log\n5. Watchdog writes status to TOWER_HEALTH.md in the repo (committed daily)\n\n## Acceptance\n- [ ] Tunnel runs as a launchd service\n- [ ] Tunnel restarts within 30s of any disconnect\n- [ ] Health check detects broken tunnel within 30s\n- [ ] Tunnel status is visible in TOWER_HEALTH.md\n- [ ] No manual intervention needed after Mac reboot or sleep/wake" % epic_num,
|
|
'assignee': 'allegro',
|
|
'labels': ['evennia', 'ops'],
|
|
},
|
|
{
|
|
'title': '[TOWER-P2] Whiteboard system - messages that accumulate',
|
|
'body': "Parent: #%s\n\n## Problem\nThe whiteboard on the wall is described as filled with rules and signatures. But nobody ever writes on it. Nobody ever reads it. It never changes.\n\n## What This Is\nThe whiteboard in The Threshold is a shared message board:\n- Timmy writes one message per day (his rule, a thought, a question)\n- Other wizards can write when they visit (10 percent chance)\n- Messages persist - they do not get removed\n- The whiteboard content affects the Threshold description\n- Messages reference other things that happened\n\n## Implementation\n1. Add whiteboard list to world state\n2. Tick handler: 5 percent chance per wizard to write on whiteboard when visiting Threshold\n3. Whiteboard content shown in Threshold description\n4. Timmy writes at least once every 20 ticks\n\n## Acceptance\n- [ ] Whiteboard has at least 3 messages after 50 ticks\n- [ ] At least 2 different wizards have written on it\n- [ ] Whiteboard content changes the Threshold description" % epic_num,
|
|
'assignee': 'claw-code',
|
|
'labels': ['evennia', 'world-building'],
|
|
},
|
|
]
|
|
|
|
for i, issue in enumerate(issues):
|
|
num, title = create_issue(
|
|
title=issue['title'],
|
|
body=issue['body'],
|
|
assignee=issue.get('assignee'),
|
|
labels=issue.get('labels', []),
|
|
)
|
|
labels = ','.join(issue.get('labels', []))
|
|
assignee = issue.get('assignee', 'nobody')
|
|
print(" #%s @%s [%s]: %s" % (num, assignee, labels, title))
|
|
|
|
print("\nDone. Epic #%s created with %s issues." % (epic_num, len(issues))) |