backblaze-proofframe / scripts /agent_handoff_check.py
ADJCJH's picture
Sync judge crosswalk public demo
7700633 verified
Raw
History Blame Contribute Delete
14.6 kB
#!/usr/bin/env python3
"""Verify repo handoff metadata for Agent Bus and future Codex sessions."""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "docs" / "assets" / "agent-handoff-report.json"
DEFAULT_MD = ROOT / "docs" / "assets" / "agent-handoff-report.md"
SCHEMA = "proofframe.agent_handoff.v1"
EXPECTED_BRANCH = "feature/backblaze-proofframe"
EXPECTED_TEAM_ID = "proofframe-hackathon-58c62c50"
BUS_CLI = Path.home() / ".codex" / "tools" / "codex-agent-bus" / "bin" / "agent-bus"
WORK_PATH_RE = re.compile(r"Work only inside `([^`]+)`")
BRANCH_RE = re.compile(r"Current branch:\s*`([^`]+)`")
TEAM_ID_RE = re.compile(r"Team id:\s*`([^`]+)`")
def utc_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def normalize_path(raw_path: str) -> Path:
return Path(raw_path).expanduser().resolve(strict=False)
def running_in_ci() -> bool:
return os.environ.get("GITHUB_ACTIONS") == "true" or os.environ.get("CI") == "true"
def documented_repo_name_matches(root: Path, work_path: str | None) -> bool:
return bool(work_path) and normalize_path(str(work_path)).name == root.name
def extract(pattern: re.Pattern[str], text: str) -> str | None:
match = pattern.search(text)
return match.group(1) if match else None
def check_item(check_id: str, label: str, ok: bool, detail: str, evidence: str) -> dict[str, Any]:
return {
"id": check_id,
"label": label,
"ok": ok,
"detail": detail,
"evidence": evidence,
}
def load_agents_text(root: Path) -> tuple[str, bool]:
path = root / "AGENTS.md"
try:
return path.read_text(encoding="utf-8"), True
except FileNotFoundError:
return "", False
def run_bus_json(root: Path, args: list[str]) -> tuple[dict[str, Any] | None, str | None]:
result = subprocess.run(
[str(BUS_CLI), *args],
cwd=root,
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return None, result.stderr.strip() or result.stdout.strip() or "agent-bus command failed."
try:
return json.loads(result.stdout), None
except json.JSONDecodeError:
return None, "agent-bus command did not return JSON."
def resolve_agent_summary(root: Path, session_id: str) -> dict[str, Any]:
payload, error = run_bus_json(root, ["resolve", session_id])
if error:
return {
"session_id": session_id,
"resolved": False,
"cwd": None,
"cwd_matches_repo": False,
"status": "resolve_error",
"detail": error,
}
agent = (payload or {}).get("agent") or {}
cwd = agent.get("cwd")
cwd_matches = bool(cwd) and normalize_path(str(cwd)) == root.resolve()
return {
"session_id": session_id,
"resolved": True,
"agent_id": agent.get("agent_id"),
"name": agent.get("name"),
"cwd": cwd,
"cwd_matches_repo": cwd_matches,
"status": agent.get("status"),
"stale": bool(agent.get("stale")),
"last_seen": agent.get("last_seen"),
"detail": (
"Agent registry cwd matches this repo."
if cwd_matches
else "Agent registry cwd is stale, missing, or unresolved."
),
}
def active_role_summaries(root: Path, roles: dict[str, Any]) -> list[dict[str, Any]]:
summaries: list[dict[str, Any]] = []
for role_name, role in sorted(roles.items()):
session_id = role.get("session_id") or role.get("thread_id")
if role.get("status") != "active" or not session_id:
continue
summary = resolve_agent_summary(root, str(session_id))
summary.update(
{
"role": role_name,
"role_status": role.get("status"),
"thread_id": role.get("thread_id"),
}
)
summaries.append(summary)
return summaries
def bus_team_summary(root: Path, team_id: str = EXPECTED_TEAM_ID) -> dict[str, Any]:
if not BUS_CLI.exists():
return {
"checked": True,
"ok": False,
"status": "unavailable",
"team_id": team_id,
"detail": f"Agent Bus CLI not found at {BUS_CLI}.",
"active_roles": [],
"active_role_cwd_ok": False,
}
payload, error = run_bus_json(root, ["team", "show", team_id])
if error:
return {
"checked": True,
"ok": False,
"status": "error",
"team_id": team_id,
"detail": error,
"active_roles": [],
"active_role_cwd_ok": False,
}
team = (payload or {}).get("team") or {}
project = team.get("project")
project_matches = bool(project) and normalize_path(str(project)) == root.resolve()
active_roles = active_role_summaries(root, team.get("roles") or {})
active_role_cwd_ok = bool(active_roles) and all(role.get("cwd_matches_repo") for role in active_roles)
return {
"checked": True,
"ok": project_matches,
"status": "current" if project_matches else "stale",
"team_id": team.get("team_id") or team_id,
"project": project,
"expected_project": str(root.resolve()),
"active_roles": active_roles,
"active_role_cwd_ok": active_role_cwd_ok,
"detail": (
"Agent Bus team project path matches this repo."
if project_matches
else "Agent Bus team project path is stale or missing; AGENTS.md is the repo authority."
),
}
def build_report(
root: Path = ROOT,
*,
check_bus: bool = False,
strict_bus: bool = False,
team_id: str = EXPECTED_TEAM_ID,
) -> dict[str, Any]:
root = root.resolve()
agents_text, agents_present = load_agents_text(root)
work_path = extract(WORK_PATH_RE, agents_text) if agents_present else None
branch = extract(BRANCH_RE, agents_text) if agents_present else None
documented_team_id = extract(TEAM_ID_RE, agents_text) if agents_present else None
expected_root = str(root)
normalized_work_path = str(normalize_path(work_path)) if work_path else None
ci = running_in_ci()
work_path_matches_repo = (
documented_repo_name_matches(root, work_path)
if ci
else bool(work_path)
and normalized_work_path == expected_root
)
work_path_exists = (
documented_repo_name_matches(root, work_path)
if ci
else bool(work_path)
and normalize_path(work_path).exists()
)
work_path_detail = (
f"AGENTS path is {work_path!r}; CI workspace is {expected_root!r}; repo name matches."
if ci and work_path_matches_repo
else f"AGENTS path is {work_path!r}; expected {expected_root!r}."
)
work_path_exists_detail = (
f"AGENTS path is {work_path!r}; CI cannot require the developer-local absolute path to exist."
if ci and work_path_exists
else f"AGENTS path is {work_path!r}."
)
checks = [
check_item(
"agents_present",
"AGENTS.md is present",
agents_present,
"AGENTS.md found." if agents_present else "AGENTS.md is missing.",
"AGENTS.md",
),
check_item(
"work_path_matches_repo",
"AGENTS work path matches this repo",
work_path_matches_repo,
work_path_detail,
"AGENTS.md",
),
check_item(
"work_path_exists",
"AGENTS work path exists",
work_path_exists,
work_path_exists_detail,
"AGENTS.md",
),
check_item(
"branch_matches",
"AGENTS branch matches current project branch",
branch == EXPECTED_BRANCH,
f"AGENTS branch is {branch!r}; expected {EXPECTED_BRANCH!r}.",
"AGENTS.md",
),
check_item(
"team_id_matches",
"AGENTS team id matches ProofFrame team",
documented_team_id == team_id,
f"AGENTS team id is {documented_team_id!r}; expected {team_id!r}.",
"AGENTS.md",
),
]
bus = (
bus_team_summary(root, team_id)
if check_bus
else {
"checked": False,
"ok": None,
"status": "skipped",
"team_id": team_id,
"detail": "Pass --check-bus for local durable Agent Bus metadata inspection.",
"active_roles": [],
"active_role_cwd_ok": None,
}
)
if strict_bus:
checks.append(
check_item(
"bus_project_matches_repo",
"Durable Agent Bus project path matches this repo",
bool(bus.get("ok")),
str(bus.get("detail")),
"agent-bus team show",
)
)
ok = all(item["ok"] for item in checks)
return {
"schema": SCHEMA,
"created_at": utc_now(),
"ok": ok,
"mode": "handoff_ready" if ok else "handoff_mismatch",
"repo_root": expected_root,
"environment": {
"ci": ci,
"github_actions": os.environ.get("GITHUB_ACTIONS") == "true",
},
"agents": {
"path": "AGENTS.md",
"work_path": work_path,
"normalized_work_path": normalized_work_path,
"branch": branch,
"team_id": documented_team_id,
},
"bus": bus,
"checks": checks,
"next_actions": next_actions(checks, bus),
}
def next_actions(checks: list[dict[str, Any]], bus: dict[str, Any]) -> list[str]:
actions: list[str] = []
failed = {item["id"] for item in checks if not item["ok"]}
if "work_path_matches_repo" in failed or "work_path_exists" in failed:
actions.append("Update AGENTS.md so Work only inside points to the current repo path.")
if "branch_matches" in failed:
actions.append("Update AGENTS.md Current branch when the long-lived feature branch changes.")
if "team_id_matches" in failed:
actions.append("Update AGENTS.md Team id if a new ProofFrame Agent Bus team is created.")
if bus.get("status") == "stale":
actions.append("Do not trust stale durable Agent Bus project metadata; use AGENTS.md as repo authority.")
if bus.get("checked") and bus.get("active_roles") and not bus.get("active_role_cwd_ok"):
actions.append("Reattach active Agent Bus roles with --cwd pointing to the current repo path.")
if not actions:
actions.append("Handoff metadata is ready for future Codex or Agent Bus sessions.")
return actions
def render_markdown(report: dict[str, Any]) -> str:
lines = [
"# ProofFrame Agent Handoff Report",
"",
f"Mode: `{report['mode']}`",
f"OK: `{str(report['ok']).lower()}`",
f"Created: `{report['created_at']}`",
f"Repo root: `{report['repo_root']}`",
"",
"## AGENTS.md",
"",
f"- Work path: `{report['agents']['work_path']}`",
f"- Branch: `{report['agents']['branch']}`",
f"- Team id: `{report['agents']['team_id']}`",
"",
"## Agent Bus",
"",
f"- Checked: `{str(report['bus']['checked']).lower()}`",
f"- Status: `{report['bus']['status']}`",
f"- Detail: {report['bus']['detail']}",
f"- Active role cwd ok: `{str(report['bus'].get('active_role_cwd_ok')).lower()}`",
"",
"### Active Roles",
"",
"| Role | Session | Status | CWD matches repo | CWD |",
"| --- | --- | --- | --- | --- |",
]
if report["bus"].get("active_roles"):
for role in report["bus"]["active_roles"]:
lines.append(
"| "
f"{role.get('role')} | "
f"`{role.get('session_id')}` | "
f"`{role.get('status')}` | "
f"`{str(role.get('cwd_matches_repo')).lower()}` | "
f"`{role.get('cwd')}` |"
)
else:
lines.append("| none | `n/a` | `n/a` | `n/a` | `n/a` |")
lines.extend(
[
"",
"## Checks",
"",
"| Status | Check | Detail | Evidence |",
"| --- | --- | --- | --- |",
]
)
for item in report["checks"]:
status = "OK" if item["ok"] else "FAIL"
lines.append(f"| {status} | {item['label']} | {item['detail']} | `{item['evidence']}` |")
lines.extend(["", "## Next Actions", ""])
lines.extend(f"- {action}" for action in report["next_actions"])
lines.append("")
return "\n".join(lines)
def write_outputs(report: dict[str, Any], json_path: Path, markdown_path: Path) -> None:
json_path.parent.mkdir(parents=True, exist_ok=True)
markdown_path.parent.mkdir(parents=True, exist_ok=True)
json_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
markdown_path.write_text(render_markdown(report), encoding="utf-8")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Verify ProofFrame Agent Bus handoff metadata.")
parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON)
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD)
parser.add_argument("--check-bus", action="store_true", help="Inspect local durable Agent Bus metadata.")
parser.add_argument("--strict-bus", action="store_true", help="Fail if local durable Bus project path is stale.")
return parser
def main() -> None:
args = build_parser().parse_args()
report = build_report(ROOT, check_bus=args.check_bus, strict_bus=args.strict_bus)
write_outputs(report, args.json_out, args.markdown_out)
print(
json.dumps(
{
"ok": report["ok"],
"mode": report["mode"],
"json": str(args.json_out),
"markdown": str(args.markdown_out),
"bus_status": report["bus"]["status"],
"failed_checks": [item["id"] for item in report["checks"] if not item["ok"]],
},
indent=2,
)
)
if not report["ok"]:
raise SystemExit(1)
if __name__ == "__main__":
main()