#!/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)