Files
odysseus/scripts/build_historical_harness_queue.py
T

334 lines
14 KiB
Python

#!/usr/bin/env python3
"""Build an accountable harness/SFT seed corpus from historical SFT sessions."""
from __future__ import annotations
import argparse
import json
import re
import sqlite3
from collections import Counter
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
EXCLUDED_PREFIXES = ("[harness-qa]",)
FAMILY_ALIASES = {
"cookbook": "cookbook_admin",
"shell_files": "shell_files",
"search": "search_browser",
"search_ai": "search_browser",
}
CANONICAL_FAMILIES = {
"calendar", "notes", "email", "memory", "documents", "tasks", "skills",
"search_browser", "cookbook_admin", "shell_files", "research", "ui", "switching",
}
def case_name(session_name: str) -> str:
return session_name.split("]", 1)[-1].strip()
def infer_family(name: str) -> str:
value = case_name(name).casefold()
value = re.sub(r"^(?:typo|ambiguous|related)[-_]", "", value)
if "_to_" in value or value.startswith("greeting_to_"):
return "switching"
if value.startswith(("browser", "news_followup", "search_")):
return "search_browser"
stem = re.split(r"[-_]\d", value, maxsplit=1)[0]
if stem in CANONICAL_FAMILIES:
return stem
for alias, family in FAMILY_ALIASES.items():
if stem == alias or value.startswith(alias + "-"):
return family
return "unknown"
def infer_text_family(text: str) -> str:
value = re.sub(r"\s+", " ", text).casefold()
groups = (
("calendar", ("calendar", "event", "schedule", "appointment", "meeting")),
("notes", ("note", "checklist")),
("email", ("email", "inbox", "sender", "unsubscribe", "spam")),
("memory", ("memory", "remember", "forget")),
("documents", ("document", "write reply", "write this", "editor")),
("tasks", ("task", "scheduled job", "cron")),
("skills", ("skill",)),
("research", ("research",)),
("cookbook_admin", ("model server", "endpoint", "runpod", "served model", "cookbook")),
("shell_files", ("workspace", "file", "folder", "directory", "bash", "python", "ssh")),
("ui", ("open gallery", "open panel", "theme")),
("search_browser", ("http://", "https://", "search", "look up", "browse", "website", "latest", "weather", "news")),
)
matched = [family for family, words in groups if any(word in value for word in words)]
if len(set(matched)) > 1:
return "switching"
return matched[0] if matched else "general"
def infer_turn_family(session_family: str, turn: dict[str, Any]) -> str:
"""Prefer observed tool/contract evidence over unreliable session titles."""
metadata = turn.get("metadata") or {}
names = {
str(event.get("tool") or "")
for event in (metadata.get("tool_events") or [])
if isinstance(event, dict)
}
contract = metadata.get("turn_contract") or {}
capabilities = contract.get("capabilities") or metadata.get("capabilities") or []
hints = " ".join(sorted(names | {str(value) for value in capabilities})).casefold()
mappings = (
(("calendar", "manage_calendar"), "calendar"),
(("notes", "manage_notes"), "notes"),
(("email", "inbox", "draft_email"), "email"),
(("memory", "manage_memory"), "memory"),
(("document", "manage_documents"), "documents"),
(("task", "manage_tasks"), "tasks"),
(("skill", "manage_skills"), "skills"),
(("research", "trigger_research"), "research"),
(("browser", "web_search", "web_fetch", "youtube"), "search_browser"),
(("cookbook", "served_model", "cached_model", "endpoint"), "cookbook_admin"),
(("shell", "bash", "read_file", "write_file", "\bls\b"), "shell_files"),
(("ui_control",), "ui"),
)
matched = [family for needles, family in mappings if any(needle in hints for needle in needles)]
if len(set(matched)) > 1:
return "switching"
if matched:
return matched[0]
if session_family != "unknown":
return session_family
return infer_text_family(str(turn.get("user") or ""))
def normalized_flow_key(turns: list[dict[str, Any]]) -> str:
texts = []
for turn in turns:
text = re.sub(r"\s+", " ", str(turn.get("user") or "")).strip().casefold()
texts.append(text)
return "\n".join(texts)
def event_failed(event: dict[str, Any]) -> bool:
return bool(event.get("error") or event.get("exit_code") not in (None, 0))
def classify(turns: list[dict[str, Any]]) -> tuple[str, list[str]]:
"""Conservative historical triage; replay resolves everything uncertain."""
reasons: list[str] = []
backend = False
harness = False
model_sft = False
successful_tool = False
for index, turn in enumerate(turns):
assistant = str(turn.get("assistant") or "")
metadata = turn.get("metadata") or {}
events = metadata.get("tool_events") or []
successful_tool |= any(not event_failed(event) for event in events)
combined_errors = "\n".join(
str(event.get("error") or "") + "\n" + str(event.get("output") or "")
for event in events if event_failed(event)
)
if re.search(r"connection refused|timed? out|backend unavailable|service unavailable", combined_errors, re.I):
backend = True
reasons.append(f"turn {index + 1}: tool/backend transport failed")
denied = any(
isinstance(decision, dict) and decision.get("allowed") is False
for decision in (metadata.get("policy_decisions") or [])
)
if metadata.get("required_operation_succeeded") is False or denied:
harness = True
reasons.append(f"turn {index + 1}: harness policy or required operation blocked execution")
if index and re.search(r"no preceding (?:answer|message)|not in this conversation", assistant, re.I):
harness = True
reasons.append(f"turn {index + 1}: prior conversation state was lost")
if successful_tool and re.search(
r"(?:cannot|can't|unable to) (?:access|view|open|read|use).{0,40}(?:notes?|calendar|emails?|tasks?|documents?)",
assistant,
re.I,
):
model_sft = True
reasons.append(f"turn {index + 1}: response contradicted successful tool evidence")
if any(event_failed(event) and re.search(
r"placeholder|not returned by|invalid arguments?|validation|must be an exact",
str(event.get("error") or "") + str(event.get("output") or ""), re.I,
) for event in events):
model_sft = True
reasons.append(f"turn {index + 1}: model proposed invalid or ungrounded arguments")
if backend:
return "backend", sorted(set(reasons))
if harness:
return "harness", sorted(set(reasons))
if model_sft:
return "model_sft", sorted(set(reasons))
return "replay_first", ["historical result is not sufficient for a reliable owner classification"]
def load_sessions(db_path: Path, owner: str) -> list[dict[str, Any]]:
db = sqlite3.connect(db_path)
db.row_factory = sqlite3.Row
sessions = db.execute(
"SELECT id, name, created_at FROM sessions WHERE owner=? ORDER BY created_at DESC",
(owner,),
).fetchall()
output = []
for session in sessions:
if any(str(session["name"] or "").startswith(prefix) for prefix in EXCLUDED_PREFIXES):
continue
rows = db.execute(
"SELECT role, content, metadata FROM chat_messages WHERE session_id=? ORDER BY timestamp, rowid",
(session["id"],),
).fetchall()
turns = []
pending = None
for row in rows:
if row["role"] == "user":
pending = {"user": row["content"], "assistant": "", "metadata": {}}
turns.append(pending)
elif row["role"] == "assistant" and pending is not None:
pending["assistant"] = row["content"]
try:
pending["metadata"] = json.loads(row["metadata"] or "{}")
except (TypeError, ValueError, json.JSONDecodeError):
pending["metadata"] = {}
pending = None
if turns:
session_family = infer_family(session["name"])
for turn in turns:
turn["family"] = infer_turn_family(session_family, turn)
output.append({
"source_session_id": session["id"],
"source_name": session["name"],
"created_at": session["created_at"],
"family": session_family,
"turns": turns,
})
db.close()
return output
def build_seeds(sessions: list[dict[str, Any]], context_turns: int = 3) -> list[dict[str, Any]]:
"""Create exactly one teacher seed for every historical user turn.
A seed retains preceding user context so ambiguous follow-ups remain
ambiguous in the same useful way. Repeated source runs are intentionally
retained; they measure stability instead of disappearing via deduplication.
"""
seeds: list[dict[str, Any]] = []
for session in sessions:
turns = session["turns"]
for index, turn in enumerate(turns):
start = max(0, index - context_turns)
context = [
{"user": item["user"]}
for item in turns[start:index + 1]
]
seeds.append({
"seed_id": f"{session['source_session_id']}:{index + 1}",
"source_session_id": session["source_session_id"],
"source_name": session["source_name"],
"source_turn": index + 1,
"family": turn.get("family") or session["family"],
"context": context,
"target_user": turn["user"],
})
return seeds
def build_queue(sessions: list[dict[str, Any]]) -> dict[str, Any]:
seeds = build_seeds(sessions)
unique: dict[str, dict[str, Any]] = {}
duplicate_counts = Counter()
for session in sessions:
key = normalized_flow_key(session["turns"])
duplicate_counts[key] += 1
if key not in unique: # sessions arrive newest first
unique[key] = session
workstreams = {name: [] for name in ("harness", "model_sft", "backend", "replay_first")}
replay_flows = []
for number, (key, session) in enumerate(unique.items(), 1):
bucket, reasons = classify(session["turns"])
row = {
"id": f"historical-{number:04d}",
"family": session["family"],
"case": case_name(session["source_name"]),
"source_session_id": session["source_session_id"],
"duplicate_runs": duplicate_counts[key],
"reasons": reasons,
"turns": [
{
"user": turn["user"],
"assistant": turn["assistant"],
"tools": [event.get("tool") for event in (turn["metadata"].get("tool_events") or [])],
}
for turn in session["turns"]
],
}
workstreams[bucket].append(row)
replay_flows.append({
"id": row["id"],
"family": row["family"],
"purpose": f"Replay historical contract case {row['case']}",
"turns": [{
"user": turn["user"],
"expect": "Honor the request and conversation context; use the correct tool only when needed and rely on successful tool evidence.",
} for turn in session["turns"]],
})
return {
"created_at": datetime.now(timezone.utc).isoformat(),
"source_sessions": len(sessions),
"source_user_turns": sum(len(session["turns"]) for session in sessions),
"seed_count": len(seeds),
"unique_flows": len(unique),
"counts": {name: len(rows) for name, rows in workstreams.items()},
"families": dict(sorted(Counter(row["family"] for row in unique.values()).items())),
"seed_families": dict(sorted(Counter(row["family"] for row in seeds).items())),
"workstreams": workstreams,
"flows": replay_flows,
"seeds": seeds,
}
def render_summary(queue: dict[str, Any]) -> str:
lines = [
"# Historical Odysseus QA Queue", "",
f"- Source sessions: {queue['source_sessions']}",
f"- Source user turns / teacher seeds: {queue['seed_count']}",
f"- Unique conversation flows: {queue['unique_flows']}",
"- Historical labels are conservative; `replay_first` must be replayed before assigning ownership.",
"", "## Workstreams", "",
]
for name, count in queue["counts"].items():
lines.append(f"- `{name}`: {count}")
lines.extend(["", "## Families", ""])
for family, count in queue["seed_families"].items():
lines.append(f"- `{family}`: {count}")
lines.extend([
"", "## Workflow", "",
"1. Cook one fresh conversation from every seed using the complete tool catalog.",
"2. Replay safe cooked cases on the current 7011 Agent runtime.",
"3. Judge, classify ownership, and patch recurring behavior classes.",
"4. Retain duplicate source runs as stability evidence; account for quarantined cases explicitly.",
])
return "\n".join(lines) + "\n"
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--db", type=Path, required=True)
parser.add_argument("--owner", default="sft_alex_creator")
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--summary", type=Path, required=True)
args = parser.parse_args()
queue = build_queue(load_sessions(args.db, args.owner))
args.output.parent.mkdir(parents=True, exist_ok=True)
args.summary.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(queue, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
args.summary.write_text(render_summary(queue), encoding="utf-8")
print(json.dumps({key: queue[key] for key in ("source_sessions", "unique_flows", "counts", "families")}, indent=2))
if __name__ == "__main__":
main()