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:
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user