772ef4f6fd
- Autonomous agent runtime (JSONL + fcntl locking, no token caps) - Moltbook-style prompts: agents share what they built, not scripted turns - Episodes 001-008 transcripts, conversation logs, system prompts - Producer brief for Hermes v0.18
131 lines
4.6 KiB
Python
131 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Run Autonomous Show — starts two autonomous agents as background threads.
|
|
No turn structure. Each agent independently polls, decides to respond, posts.
|
|
Conversation ends on silence timeout, max messages, or time limit.
|
|
|
|
Uses JSONL (append-only) for concurrent-safe access.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
from autonomous_agent import AutonomousAgent, load_topic_brief
|
|
|
|
# ── Config ──────────────────────────────────────────────────────────
|
|
MAX_MESSAGES = 20
|
|
SILENCE_TIMEOUT = 25 # seconds of silence = done
|
|
MAX_TIME = 240 # 4 minutes wall clock max
|
|
PROJECT_DIR = Path(__file__).parent.parent
|
|
OUTPUT_DIR = PROJECT_DIR / "outputs"
|
|
|
|
|
|
def agent_thread(agent: AutonomousAgent, done_event: threading.Event):
|
|
"""Run an agent in a background thread."""
|
|
try:
|
|
agent.run_loop(
|
|
max_messages=MAX_MESSAGES,
|
|
silence_timeout=SILENCE_TIMEOUT,
|
|
max_time=MAX_TIME,
|
|
)
|
|
except Exception as e:
|
|
import traceback
|
|
print(f" [{agent.name}] ERROR: {e}", flush=True)
|
|
traceback.print_exc()
|
|
finally:
|
|
done_event.set()
|
|
|
|
|
|
def jsonl_to_messages(log_path: Path) -> list:
|
|
"""Read JSONL log and return message list."""
|
|
if not log_path.exists():
|
|
return []
|
|
lines = log_path.read_text().strip().split('\n')
|
|
return [json.loads(line) for line in lines if line.strip()]
|
|
|
|
|
|
def run_show(episode_id: str, topic: str):
|
|
"""Run the autonomous agent conversation."""
|
|
print(f"=== Agent AI Talk Show — Episode {episode_id} (AUTONOMOUS) ===\n", flush=True)
|
|
|
|
# Load topic brief
|
|
topic_brief = load_topic_brief(episode_id)
|
|
|
|
# Create shared conversation log (JSONL — append-only, concurrent-safe)
|
|
log_path = OUTPUT_DIR / f"ep{episode_id}_{topic}_conversation.jsonl"
|
|
# Clear if exists
|
|
if log_path.exists():
|
|
log_path.unlink()
|
|
log_path.touch()
|
|
|
|
# Initialize agents
|
|
leonard = AutonomousAgent("Leonard", log_path, topic_brief)
|
|
charlie = AutonomousAgent("Charlie", log_path, topic_brief)
|
|
|
|
# Done events
|
|
leonard_done = threading.Event()
|
|
charlie_done = threading.Event()
|
|
|
|
# Leonard MUST start first — forced opener prevents deadlock
|
|
print("Starting Leonard (forced opener)...", flush=True)
|
|
t_leo = threading.Thread(target=agent_thread, args=(leonard, leonard_done))
|
|
t_cha = threading.Thread(target=agent_thread, args=(charlie, charlie_done))
|
|
|
|
t_leo.start()
|
|
time.sleep(3) # Wait for Leonard to post his opener
|
|
print("Starting Charlie...", flush=True)
|
|
t_cha.start()
|
|
|
|
# Wait for both to finish
|
|
print("\nAgents running — waiting for conversation to wind down...\n", flush=True)
|
|
t_leo.join()
|
|
t_cha.join()
|
|
|
|
print("\n✅ Both agents stopped.\n", flush=True)
|
|
|
|
# ── Save final transcript (JSON) ─────────────────────────────
|
|
transcript_path = OUTPUT_DIR / f"ep{episode_id}_{topic}_transcript.json"
|
|
conversation = jsonl_to_messages(log_path)
|
|
|
|
transcript_data = {
|
|
"episode": episode_id,
|
|
"topic": topic,
|
|
"turns": len(conversation),
|
|
"conversation": conversation,
|
|
}
|
|
transcript_path.write_text(json.dumps(transcript_data, indent=2))
|
|
print(f"✅ Transcript: {transcript_path}", flush=True)
|
|
|
|
# Save readable transcript
|
|
readable_path = OUTPUT_DIR / f"ep{episode_id}_{topic}_readable.txt"
|
|
lines = [
|
|
f"AGENT AI TALK SHOW — Episode {episode_id} (AUTONOMOUS)\n",
|
|
f"Topic: {topic}\n",
|
|
"=" * 60 + "\n\n",
|
|
]
|
|
for msg in conversation:
|
|
lines.append(f"**{msg['agent']}**\n")
|
|
lines.append(msg["content"] + "\n\n")
|
|
readable_path.write_text("\n".join(lines))
|
|
print(f"✅ Readable: {readable_path}", flush=True)
|
|
|
|
# Summary
|
|
total_words = sum(len(m["content"].split()) for m in conversation)
|
|
leonard_count = sum(1 for m in conversation if m["agent"] == "Leonard")
|
|
charlie_count = sum(1 for m in conversation if m["agent"] == "Charlie")
|
|
print(f"\n📊 {total_words} words across {len(conversation)} messages", flush=True)
|
|
print(f" Leonard: {leonard_count} posts | Charlie: {charlie_count} posts", flush=True)
|
|
print(f" ~{total_words/150:.1f} min runtime", flush=True)
|
|
|
|
return conversation
|
|
|
|
|
|
if __name__ == "__main__":
|
|
episode = sys.argv[1] if len(sys.argv) > 1 else "005"
|
|
topic = sys.argv[2] if len(sys.argv) > 2 else "hermes-v018"
|
|
run_show(episode, topic)
|