Overhaul agent quality — prompts, tools, config, compression
- Rewrite system prompt: structured sections, explicit tool descriptions with full SKILL.md descriptions, multi-agent awareness - Add write_file skill for creating/modifying workspace files - Per-template config passthrough: temperature, num_predict, context_size, compress settings, max_tool_rounds, max_response_lines - Bump defaults: 1024 output tokens (was 512), 500-char deque (was 200), 250-token summaries (was 150), compress threshold 16 (was 12), keep 8 (was 4) - Cache compression by content hash — no redundant summarization - Update all 5 templates with tuned settings per role
This commit is contained in:
@@ -8,6 +8,7 @@ import sys
|
||||
import time
|
||||
import signal
|
||||
import threading
|
||||
import urllib.request
|
||||
from collections import deque
|
||||
|
||||
from skills import discover_skills, execute_skill, set_logger as set_skills_logger
|
||||
@@ -34,8 +35,13 @@ CONTEXT_SIZE = CONFIG.get("context_size", 20)
|
||||
MAX_RESPONSE_LINES = CONFIG.get("max_response_lines", 50)
|
||||
TOOLS_ENABLED = CONFIG.get("tools", True)
|
||||
MAX_TOOL_ROUNDS = CONFIG.get("max_tool_rounds", 10)
|
||||
NUM_PREDICT = CONFIG.get("num_predict", 1024)
|
||||
TEMPERATURE = CONFIG.get("temperature", 0.7)
|
||||
WORKSPACE = "/workspace"
|
||||
SKILL_DIRS = ["/opt/skills", f"{WORKSPACE}/skills"]
|
||||
COMPRESS_ENABLED = CONFIG.get("compress", True)
|
||||
COMPRESS_THRESHOLD = CONFIG.get("compress_threshold", 16)
|
||||
COMPRESS_KEEP = CONFIG.get("compress_keep", 8)
|
||||
|
||||
RUNTIME = {
|
||||
"model": CONFIG.get("model", "qwen2.5-coder:7b"),
|
||||
@@ -149,22 +155,76 @@ class IRCClient:
|
||||
# ─── Message Handling ────────────────────────────────────────────────
|
||||
|
||||
|
||||
_compression_cache = {"hash": None, "summary": None}
|
||||
|
||||
|
||||
def compress_messages(channel_msgs):
|
||||
"""Summarize older messages, keep recent ones intact. Caches summary."""
|
||||
if not COMPRESS_ENABLED or len(channel_msgs) <= COMPRESS_THRESHOLD:
|
||||
return channel_msgs
|
||||
|
||||
older = channel_msgs[:-COMPRESS_KEEP]
|
||||
keep = channel_msgs[-COMPRESS_KEEP:]
|
||||
|
||||
lines = [f"<{m['nick']}> {m['text']}" for m in older]
|
||||
conversation = "\n".join(lines)
|
||||
conv_hash = hash(conversation)
|
||||
|
||||
# Return cached summary if older messages haven't changed
|
||||
if _compression_cache["hash"] == conv_hash and _compression_cache["summary"]:
|
||||
return [{"nick": "_summary", "text": _compression_cache["summary"], "channel": channel_msgs[0]["channel"]}] + keep
|
||||
|
||||
try:
|
||||
payload = json.dumps({
|
||||
"model": RUNTIME["model"],
|
||||
"messages": [
|
||||
{"role": "system", "content": "Summarize this IRC conversation in 3-5 sentences. Preserve key facts, decisions, questions, and any specific data mentioned. Be thorough but concise."},
|
||||
{"role": "user", "content": conversation},
|
||||
],
|
||||
"stream": False,
|
||||
"options": {"num_predict": 250},
|
||||
}).encode()
|
||||
req = urllib.request.Request(f"{OLLAMA_URL}/api/chat", data=payload, headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = json.loads(resp.read(500_000))
|
||||
summary = data.get("message", {}).get("content", "").strip()
|
||||
if summary:
|
||||
_compression_cache["hash"] = conv_hash
|
||||
_compression_cache["summary"] = summary
|
||||
log(f"Compressed {len(older)} messages into summary")
|
||||
return [{"nick": "_summary", "text": summary, "channel": channel_msgs[0]["channel"]}] + keep
|
||||
except Exception as e:
|
||||
log(f"Compression failed: {e}")
|
||||
|
||||
return channel_msgs
|
||||
|
||||
|
||||
def build_messages(question, channel):
|
||||
system = RUNTIME["persona"]
|
||||
|
||||
# Environment
|
||||
system += f"\n\n## Environment\nYou are {NICK} in IRC channel {channel}. This is a multi-agent system — other nicks may be AI agents with their own tools. Keep responses concise (this is IRC). To address someone, prefix with their nick: 'coder: can you review this?'"
|
||||
|
||||
# Tools
|
||||
if TOOLS_ENABLED and TOOLS:
|
||||
skill_names = [t["function"]["name"] for t in TOOLS]
|
||||
system += "\n\nYou have access to tools: " + ", ".join(skill_names) + "."
|
||||
system += "\nUse tools when needed rather than guessing. Your workspace at /workspace persists across restarts."
|
||||
system += "\n\n## Tools\nYou have tools — use them proactively instead of guessing or apologizing. If asked to do something, DO it with your tools."
|
||||
for t in TOOLS:
|
||||
fn = t["function"]
|
||||
system += f"\n- **{fn['name']}**: {fn.get('description', '')}"
|
||||
system += "\n\nYour workspace at /workspace persists across restarts. Write files, save results, read them back."
|
||||
|
||||
# Memory
|
||||
if AGENT_MEMORY and AGENT_MEMORY != "# Agent Memory":
|
||||
system += f"\n\nIMPORTANT - Your persistent memory (facts you saved previously, use these to answer questions):\n{AGENT_MEMORY}"
|
||||
system += f"\n\nYou are in IRC channel {channel}. Your nick is {NICK}. Keep responses concise — this is IRC."
|
||||
system += "\nWhen you want to address another agent or user, always start your message with their nick followed by a colon, e.g. 'coder: can you review this?'. This is how IRC mentions work — without the prefix, they won't see your message."
|
||||
system += f"\n\n## Your Memory\n{AGENT_MEMORY}"
|
||||
|
||||
messages = [{"role": "system", "content": system}]
|
||||
|
||||
channel_msgs = [m for m in recent if m["channel"] == channel]
|
||||
for msg in channel_msgs[-CONTEXT_SIZE:]:
|
||||
if msg["nick"] == NICK:
|
||||
channel_msgs = compress_messages(channel_msgs[-CONTEXT_SIZE:])
|
||||
for msg in channel_msgs:
|
||||
if msg["nick"] == "_summary":
|
||||
messages.append({"role": "system", "content": f"[earlier conversation summary] {msg['text']}"})
|
||||
elif msg["nick"] == NICK:
|
||||
messages.append({"role": "assistant", "content": msg["text"]})
|
||||
else:
|
||||
messages.append({"role": "user", "content": f"<{msg['nick']}> {msg['text']}"})
|
||||
@@ -245,6 +305,7 @@ def handle_message(irc, source_nick, target, text):
|
||||
TOOLS if TOOLS_ENABLED else [],
|
||||
SKILL_SCRIPTS, dispatch_tool,
|
||||
OLLAMA_URL, MAX_TOOL_ROUNDS,
|
||||
num_predict=NUM_PREDICT, temperature=TEMPERATURE,
|
||||
)
|
||||
|
||||
if not response:
|
||||
@@ -256,8 +317,8 @@ def handle_message(irc, source_nick, target, text):
|
||||
lines.append(f"[truncated, {MAX_RESPONSE_LINES} lines max]")
|
||||
|
||||
irc.say(reply_to, "\n".join(lines))
|
||||
recent.append({"nick": NICK, "text": response[:200], "channel": channel})
|
||||
save_message(db_conn, NICK, channel, response[:200], full_text=response)
|
||||
recent.append({"nick": NICK, "text": response[:500], "channel": channel})
|
||||
save_message(db_conn, NICK, channel, response[:500], full_text=response)
|
||||
except Exception as e:
|
||||
log(f"Error handling message: {e}")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user