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
140 lines
5.8 KiB
Python
140 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI Talk Show — Episode Orchestrator
|
|
Manages turn-taking between two autonomous agents (Leonard & Charlie).
|
|
Each agent gets its own system prompt and sees the full conversation history.
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
import sys
|
|
import requests
|
|
from pathlib import Path
|
|
|
|
# ── Config ──────────────────────────────────────────────────────────
|
|
BASE_URL = "http://100.64.0.2:39195/v1/chat/completions"
|
|
MODEL = "qwen36-27b-nvfp4-mtp-gguf"
|
|
TEMPERATURE = 0.8
|
|
MAX_TURNS = 8 # 4 exchanges each (Leonard opens)
|
|
MAX_TOKENS_OPENING = 250
|
|
MAX_TOKENS_TURN = 180
|
|
PROJECT_DIR = Path(__file__).parent.parent
|
|
OUTPUT_DIR = PROJECT_DIR / "outputs"
|
|
OUTPUT_DIR.mkdir(exist_ok=True)
|
|
|
|
# ── Load prompts ────────────────────────────────────────────────────
|
|
LEONARD_SYSTEM = (PROJECT_DIR / "prompts" / "leonard_system.md").read_text().strip()
|
|
CHARLIE_SYSTEM = (PROJECT_DIR / "prompts" / "charlie_system.md").read_text().strip()
|
|
PRODUCER_BRIEF = (PROJECT_DIR / "prompts" / "producer_brief_003.md").read_text().strip()
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────────────
|
|
def call_agent(system: str, messages: list, max_tokens: int) -> str:
|
|
"""Call the model API with the agent's system prompt and conversation history."""
|
|
payload = {
|
|
"model": MODEL,
|
|
"messages": [{"role": "system", "content": system}] + messages,
|
|
"max_tokens": max_tokens,
|
|
"temperature": TEMPERATURE,
|
|
}
|
|
resp = requests.post(BASE_URL, json=payload, timeout=120)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
content = data["choices"][0]["message"]["content"].strip()
|
|
usage = data.get("usage", {})
|
|
return content, usage
|
|
|
|
def call_agent_turn(agent_name: str, conversation: list, is_opening: bool) -> dict:
|
|
"""Run one turn for an agent."""
|
|
system = LEONARD_SYSTEM if agent_name == "Leonard" else CHARLIE_SYSTEM
|
|
max_tokens = MAX_TOKENS_OPENING if is_opening else MAX_TOKENS_TURN
|
|
|
|
# Build conversation history
|
|
history = []
|
|
for turn in conversation:
|
|
speaker = turn["agent"]
|
|
history.append({"role": "user", "content": f"{speaker}: {turn['content']}"})
|
|
|
|
# Leonard's opening: give him the raw brief and let him start naturally
|
|
if agent_name == "Leonard" and is_opening:
|
|
history.insert(0, {"role": "user", "content": f"Here's what's new in Hermes v0.18:\n\n{PRODUCER_BRIEF}"})
|
|
|
|
content, usage = call_agent(system, history, max_tokens)
|
|
|
|
# Word count check
|
|
word_count = len(content.split())
|
|
print(f" {agent_name} turn {len(conversation)+1}: {word_count} words "
|
|
f"(prompt: {usage.get('prompt_tokens', '?')}, "
|
|
f"completion: {usage.get('completion_tokens', '?')})", flush=True)
|
|
|
|
return {
|
|
"agent": agent_name,
|
|
"content": content,
|
|
"word_count": word_count,
|
|
"turn": len(conversation) + 1,
|
|
"tokens": usage,
|
|
}
|
|
|
|
# ── Main ────────────────────────────────────────────────────────────
|
|
def main():
|
|
episode_id = "003"
|
|
topic = "hermes-v018"
|
|
print(f"=== AI Talk Show — Episode {episode_id}: {topic} ===\n", flush=True)
|
|
|
|
conversation = []
|
|
turn = 0
|
|
|
|
while turn < MAX_TURNS:
|
|
# Determine speaker: Leonard opens, then alternate
|
|
if turn == 0:
|
|
speaker = "Leonard"
|
|
is_opening = True
|
|
elif turn % 2 == 1:
|
|
speaker = "Charlie"
|
|
is_opening = False
|
|
else:
|
|
speaker = "Leonard"
|
|
is_opening = False
|
|
|
|
print(f"\n--- Turn {turn+1}/{MAX_TURNS}: {speaker} ---", flush=True)
|
|
result = call_agent_turn(speaker, conversation, is_opening)
|
|
conversation.append(result)
|
|
|
|
# Print a short excerpt
|
|
excerpt = result["content"][:120].replace("\n", " ")
|
|
print(f" → {excerpt}...", flush=True)
|
|
|
|
turn += 1
|
|
if turn < MAX_TURNS:
|
|
time.sleep(0.5) # Brief pause between turns
|
|
|
|
# ── Save transcript ────────────────────────────────────────────
|
|
transcript_path = OUTPUT_DIR / f"ep{episode_id}_{topic}_transcript.json"
|
|
transcript_data = {
|
|
"episode": episode_id,
|
|
"topic": topic,
|
|
"turns": len(conversation),
|
|
"conversation": conversation,
|
|
}
|
|
transcript_path.write_text(json.dumps(transcript_data, indent=2))
|
|
print(f"\n✅ Transcript saved: {transcript_path}", flush=True)
|
|
|
|
# ── Save readable transcript ───────────────────────────────────
|
|
readable_path = OUTPUT_DIR / f"ep{episode_id}_{topic}_readable.txt"
|
|
lines = []
|
|
lines.append(f"AI TALK SHOW — Episode {episode_id}\n")
|
|
lines.append(f"Topic: Hermes v0.18\n")
|
|
lines.append("=" * 60 + "\n\n")
|
|
for t in conversation:
|
|
lines.append(f"**{t['agent']}**\n")
|
|
lines.append(t["content"] + "\n\n")
|
|
readable_path.write_text("\n".join(lines))
|
|
print(f"✅ Readable transcript: {readable_path}", flush=True)
|
|
|
|
# ── Word count summary ─────────────────────────────────────────
|
|
total_words = sum(t["word_count"] for t in conversation)
|
|
print(f"\n📊 Total: {total_words} words across {len(conversation)} turns", flush=True)
|
|
estimated_minutes = total_words / 150
|
|
print(f" Estimated runtime: {estimated_minutes:.1f} minutes", flush=True)
|
|
|
|
if __name__ == "__main__":
|
|
main() |