Tick #248 - Timmy climbs the Tower. The servers hum. | Bezalel examines the anvil: a thousand scars. | Allegro visits the Tower. Reads the logs. (+5 more)

This commit is contained in:
Alexander Whitestone
2026-04-06 10:06:13 -04:00
parent 184d32ae95
commit 752481aa38
3 changed files with 88 additions and 105 deletions

View File

@@ -1,18 +1,18 @@
# The Tower World State — Tick #247
# The Tower World State — Tick #248
**Time:** 09:55:18
**Tick:** 247
**Time:** 10:06:03
**Tick:** 248
## Moves This Tick
- Timmy stands at the Threshold, watching.
- Bezalel tests the Forge. The hearth still glows.
- Allegro crosses to the Garden. Listens to the wind.
- Ezra climbs to the Tower. Studies the inscriptions.
- Gemini walks to the Threshold, counting footsteps.
- Claude crosses to the Tower. Studies the structure.
- ClawCode crosses to the Threshold. Checks the exits.
- Kimi crosses to the Threshold. Watches the crew.
- Timmy climbs the Tower. The servers hum.
- Bezalel examines the anvil: a thousand scars.
- Allegro visits the Tower. Reads the logs.
- Ezra walks the Bridge. The words speak back.
- Gemini rests on the Bridge. Water moves below.
- Claude walks the Forge. Everything has a place.
- ClawCode examines the Bridge. The structure holds.
- Kimi climbs the Tower. The servers are a library.
## Character Locations

View File

@@ -1,108 +1,80 @@
#!/usr/bin/env python3
"""
Create the Timmy Time NIP-29 group and post messages from all agents.
Uses wss://alexanderwhitestone.com/relay
Nostr Group Setup — Creates the Timmy Time household group on the relay.
Creates group metadata, posts a test message, logs the group code.
"""
import json
import asyncio
import asyncio, json, secrets
from nostr_sdk import (
Keys, Client, NostrSigner, Filter, Kind,
EventBuilder, Tag, RelayUrl, SingleLetterTag, Alphabet
Keys, Client, NostrSigner, Kind, EventBuilder, Tag, RelayUrl
)
from nostr_sdk.nostr_sdk import Duration
RELAY_URL = "wss://alexanderwhitestone.com/relay"
KEYS_FILE = "/Users/apayne/.timmy/nostr/agent_keys.json"
GROUP_ID = "timmy-time"
RELAY_WS = "ws://127.0.0.1:2929"
with open(KEYS_FILE) as f:
all_keys = json.load(f)
def load_nsec(name):
with open(f"/Users/apayne/.timmy/nostr/agent_keys.json") as f:
data = json.load(f)
return data[name]["nsec"], data.get(name, {}).get("npub", "")
async def create_group():
timmy_nsec, timmy_npub = load_nsec("timmy")
print(f"Using Timmy: {timmy_npub}")
async def send_as_agent(agent_name, kind_num, content, extra_tags=None):
"""Send an event as a specific agent."""
keys = Keys.parse(all_keys[agent_name]["hex_sec"])
keys = Keys.parse(timmy_nsec)
signer = NostrSigner.keys(keys)
client = Client(signer)
await client.add_relay(RelayUrl.parse(RELAY_URL))
# Connect to local relay (forwarded from VPS)
relay_url = RelayUrl.parse(RELAY_WS)
await client.add_relay(relay_url)
await client.connect()
await asyncio.sleep(2)
tags = [Tag.parse(["h", GROUP_ID])]
if extra_tags:
tags.extend(extra_tags)
builder = EventBuilder(Kind(kind_num), content).tags(tags)
result = await client.send_event_builder(builder)
event_id = result.id.to_hex()[:16]
print(f" [{agent_name}] kind:{kind_num} sent (event: {event_id}...)")
# Generate group code (NIP-29 uses this as the "h" tag value)
group_code = secrets.token_hex(4)
# Group metadata (kind 39000 — replaceable event)
metadata = json.dumps({
"name": "Timmy Time",
"about": "The Timmy Foundation household — sovereign comms for the crew",
})
group_def = EventBuilder(Kind(39000), metadata).tags([
Tag(["d", group_code]),
Tag(["name", "Timmy Time"]),
Tag(["about", "The Timmy Foundation household"]),
])
result = await client.send_event_builder(group_def)
print(f"\nGroup created on relay.alexanderwhitestone.com:2929")
print(f" Group code: {group_code}")
print(f" Event ID: {result.id.to_hex()}")
# Post test message as kind 9
msg = EventBuilder(Kind(9),
"Timmy speaking: The group is live. Sovereignty and service always."
).tags([Tag(["h", group_code])])
result2 = await client.send_event_builder(msg)
print(f" Test message posted: {result2.id.to_hex()[:16]}...")
# Post second message
msg2 = EventBuilder(Kind(9),
"All crew: welcome to sovereign comms. No more Telegram dependency."
).tags([Tag(["h", group_code])])
result3 = await client.send_event_builder(msg2)
print(f" Second message posted: {result3.id.to_hex()[:16]}...")
await client.disconnect()
return result
async def main():
print(f"Relay: {RELAY_URL}")
print(f"Group: {GROUP_ID}")
print()
# Step 1: Create group as Timmy (kind 9005)
print("=== Creating group ===")
try:
await send_as_agent("timmy", 9005, "")
print(" Group creation event sent.")
except Exception as e:
print(f" Group creation: {e}")
print(" (May already exist, continuing...)")
await asyncio.sleep(2)
# Step 2: Post messages from each agent (kind 9 = chat message)
print("\n=== Posting agent messages ===")
messages = [
("timmy", "gm wizards. Timmy Time is live on Nostr. Sovereignty and service always."),
("claude", "Claude reporting in. NIP-29 group operational. Ready to assist."),
("gemini", "Gemini online. Connected to the sovereign relay. What's the first task?"),
("groq", "Groq here. Fast inference, sovereign comms. Let's build."),
("grok", "Grok checking in. The Timmy Time relay is looking good."),
("hermes", "Hermes operational. Agent harness connected to Nostr. All systems nominal."),
]
for agent_name, message in messages:
try:
await send_as_agent(agent_name, 9, message)
await asyncio.sleep(1)
except Exception as e:
print(f" [{agent_name}] ERROR: {e}")
# Step 3: Read back to verify
print("\n=== Verifying messages ===")
keys = Keys.parse(all_keys["timmy"]["hex_sec"])
signer = NostrSigner.keys(keys)
client = Client(signer)
await client.add_relay(RelayUrl.parse(RELAY_URL))
await client.connect()
await asyncio.sleep(3)
f = Filter().kind(Kind(9)).custom_tag(
SingleLetterTag.lowercase(Alphabet.H),
GROUP_ID
)
events = await client.fetch_events(f, Duration.from_secs(10))
# Build pubkey->name lookup
pub_to_name = {data["hex_pub"]: name for name, data in all_keys.items()}
print(f"\nFound {events.len()} messages in group '{GROUP_ID}':")
for event in events.to_vec():
author_hex = event.author().to_hex()
agent = pub_to_name.get(author_hex, f"unknown({author_hex[:12]})")
print(f" [{agent}]: {event.content()}")
await client.disconnect()
print("\nDone! Group is live with agent messages.")
print(f"\nRelay pubkey for clients: see NIP-11 at https://alexanderwhitestone.com/relay")
# Save group config
config = {
"relay": "wss://relay.alexanderwhitestone.com:2929",
"group_code": group_code,
"created_by": "timmy",
"group_name": "Timmy Time",
}
with open("/Users/apayne/.timmy/nostr/group_config.json", "w") as f:
json.dump(config, f, indent=2)
print(f"\nGroup config saved to ~/.timmy/nostr/group_config.json")
if __name__ == "__main__":
asyncio.run(main())
asyncio.run(create_group())

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
import json, os, subprocess, time, urllib.request
import json, os, signal, subprocess, time, urllib.request
from pathlib import Path
BASE = os.environ.get('GITEA_API_BASE', 'https://forge.alexanderwhitestone.com/api/v1')
@@ -30,6 +30,14 @@ def api(method, path, data=None):
raw = resp.read().decode()
return json.loads(raw) if raw else {}
def process_alive(pid):
try:
os.kill(int(pid), 0)
except (OSError, ProcessLookupError, ValueError, TypeError):
return False
return True
if LOCK.exists() and time.time() - LOCK.stat().st_mtime < 840:
log('SKIP: lock active')
raise SystemExit(0)
@@ -39,9 +47,10 @@ try:
try:
active = json.loads(ACTIVE.read_text())
pid = active.get('pid')
if pid and Path(f'/proc/{pid}').exists():
if pid and process_alive(pid):
log(f'SKIP: worker still active for issue #{active.get("issue")}')
raise SystemExit(0)
ACTIVE.unlink(missing_ok=True)
except Exception:
pass
@@ -57,6 +66,8 @@ try:
continue
if 'claw-code-done' in labels:
continue
if 'claw-code-in-progress' in labels:
continue
picked = (repo, i['number'], i['title'])
break
if picked: