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
120 lines
4.3 KiB
Python
120 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Autonomous Agent — base class for Leonard and Charlie.
|
|
Each agent runs independently, reads the shared conversation log,
|
|
generates a response, and appends it to the log.
|
|
"""
|
|
|
|
import json
|
|
import time
|
|
import sys
|
|
import requests
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# ── Config ──────────────────────────────────────────────────────────
|
|
BASE_URL = "http://100.64.0.2:39195/v1/chat/completions"
|
|
MODEL = "qwen36-27b-nvfp4-mtp-gguf"
|
|
TEMPERATURE = 0.85
|
|
PROJECT_DIR = Path(__file__).parent.parent
|
|
OUTPUT_DIR = PROJECT_DIR / "outputs"
|
|
PROMPTS_DIR = PROJECT_DIR / "prompts"
|
|
|
|
class Agent:
|
|
def __init__(self, name: str, conversation_path: Path, topic_brief: str):
|
|
self.name = name
|
|
self.conversation_path = conversation_path
|
|
self.topic_brief = topic_brief
|
|
self.system_prompt = (PROMPTS_DIR / f"{name.lower()}_system.md").read_text().strip()
|
|
self.is_opener = (name == "Leonard")
|
|
|
|
def read_conversation(self) -> list:
|
|
"""Read the current conversation log."""
|
|
if not self.conversation_path.exists():
|
|
return []
|
|
data = json.loads(self.conversation_path.read_text())
|
|
return data.get("messages", [])
|
|
|
|
def write_message(self, content: str) -> None:
|
|
"""Append a message to the conversation log."""
|
|
messages = self.read_conversation()
|
|
message = {
|
|
"agent": self.name,
|
|
"content": content,
|
|
"turn": len(messages) + 1,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
}
|
|
messages.append(message)
|
|
self.conversation_path.write_text(json.dumps({"messages": messages}, indent=2))
|
|
|
|
def build_context(self, is_first_turn: bool) -> list:
|
|
"""Build the message context for the model call."""
|
|
conversation = self.read_conversation()
|
|
|
|
# Build conversation history
|
|
history = []
|
|
for msg in conversation:
|
|
speaker = msg["agent"]
|
|
history.append({"role": "user", "content": f"{speaker}: {msg['content']}"})
|
|
|
|
# First turn: inject topic brief
|
|
if is_first_turn and self.is_opener:
|
|
topic_msg = {
|
|
"role": "user",
|
|
"content": (
|
|
f"You're on the show now. Here's your topic:\n\n"
|
|
f"{self.topic_brief}\n\n"
|
|
"Welcome your listeners, introduce yourself and Charlie, "
|
|
"and start talking about the topic."
|
|
),
|
|
}
|
|
history.insert(0, topic_msg)
|
|
elif is_first_turn and not self.is_opener:
|
|
# Charlie's first turn after Leonard's opener
|
|
history.insert(0, {
|
|
"role": "user",
|
|
"content": "Leonard just opened the show. Respond naturally to what he said."
|
|
})
|
|
|
|
return history
|
|
|
|
def speak(self) -> dict:
|
|
"""Generate and post a response."""
|
|
messages = self.read_conversation()
|
|
is_first_turn = len(messages) == 0 if self.is_opener else (
|
|
len(messages) == 1 and messages[0]["agent"] == "Leonard"
|
|
)
|
|
|
|
history = self.build_context(is_first_turn)
|
|
|
|
# Max tokens: 250 for opener, 200 for others
|
|
max_tokens = 250 if is_first_turn and self.is_opener else 200
|
|
|
|
payload = {
|
|
"model": MODEL,
|
|
"messages": [{"role": "system", "content": self.system_prompt}] + history,
|
|
"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()
|
|
|
|
word_count = len(content.split())
|
|
turn_num = len(messages) + 1
|
|
|
|
print(f" {self.name} turn {turn_num}: {word_count} words", flush=True)
|
|
excerpt = content[:100].replace("\n", " ")
|
|
print(f" → {excerpt}...", flush=True)
|
|
|
|
self.write_message(content)
|
|
|
|
return {
|
|
"agent": self.name,
|
|
"content": content,
|
|
"word_count": word_count,
|
|
"turn": turn_num,
|
|
}
|