sots-re/tools/check_agent_config.py

85 lines
4 KiB
Python

#!/usr/bin/env python3
"""Check role/model and context-policy drift; optionally validate the live OpenCode loader."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parent.parent
def check(root: Path, resolved: bool = False) -> list[str]:
config = json.loads((root / "opencode.json").read_text())
registry = json.loads((root / "campaign/models.json").read_text())
errors = []
if config.get("$schema") != "https://opencode.ai/config.json":
errors.append("OpenCode schema reference is missing")
if config.get("model") != "openai/gpt-5.6-sol":
errors.append("project loop model must be GPT-5.6 Sol")
for role, model in registry["roles"].items():
name = registry["agents"][role]
agent = config.get("agent", {}).get(name, {})
if agent.get("model") != model:
errors.append(f"{role}: registry/config model mismatch")
if agent.get("steps") != registry["max_steps"]:
errors.append(f"{role}: quantum size mismatch")
prompt = agent.get("prompt", "")
if not prompt.startswith("{file:./campaign/agents/") or not prompt.endswith("}"):
errors.append(f"{role}: prompt must come from versioned campaign/agents")
elif not (root / prompt[6:-1]).is_file():
errors.append(f"{role}: missing prompt file")
if role not in ("lead", "architecture-review") and agent.get("permission", {}).get("task") != "deny":
errors.append(f"{role}: nested delegation is not disabled")
for role in ("lead", "architecture-review", "analyst", "implementer", "verifier", "lab"):
if registry["roles"].get(role) != "openai/gpt-5.6-sol":
errors.append(f"{role}: normal loop must use GPT-5.6 Sol")
if registry["roles"].get("resolver") != "openai/gpt-6-astra":
errors.append("resolver: problem resolution must use GPT-6 Astra")
compaction = config.get("compaction", {})
if not compaction.get("auto") or compaction.get("reserved", 0) < 30000:
errors.append("automatic compaction requires at least 30000 reserved tokens")
if compaction.get("tail_turns", 99) > 4:
errors.append("compaction retains more than four recent turns")
if resolved:
env = os.environ.copy()
env["OPENCODE_CONFIG"] = str(root / "opencode.json")
# Caller overrides must not make a checked configuration look correct by accident.
env.pop("OPENCODE_CONFIG_CONTENT", None)
data = json.loads(subprocess.check_output(
["opencode", "debug", "config"], cwd=root, env=env, text=True))
for role, model in registry["roles"].items():
actual = data.get("agent", {}).get(registry["agents"][role], {})
if actual.get("model") != model:
errors.append(f"{role}: resolved loader model mismatch")
if "{file:" in actual.get("prompt", "") or not actual.get("prompt"):
errors.append(f"{role}: loader did not resolve prompt")
available = set(subprocess.check_output(["opencode", "models"], cwd=root,
env=env, text=True).splitlines())
for model in set(registry["roles"].values()):
if model not in available:
errors.append(f"unavailable model: {model}")
return errors
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--resolved", action="store_true")
args = parser.parse_args()
try:
errors = check(ROOT, args.resolved)
except (OSError, ValueError, KeyError, subprocess.SubprocessError) as exc:
print(f"agent configuration failed: {exc}", file=sys.stderr)
return 1
for error in errors:
print(error, file=sys.stderr)
if not errors:
print("Agent routing, prompts and context policy: PASS" + (" (live loader and models checked)" if args.resolved else ""))
return bool(errors)
if __name__ == "__main__":
sys.exit(main())