Files
awoooi/scripts/export-openclaw-incumbent-replay.py
Your Name cfb866d055
Some checks failed
Ansible Lint / lint (push) Successful in 35s
CD Pipeline / tests (push) Failing after 13s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 11s
feat(governance): add agent market automation surfaces
2026-06-04 21:50:55 +08:00

180 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""
Export OpenClaw incumbent replay JSONL from existing AWOOOI audit tables.
This script is read-only: it queries agent_sessions, auto_repair_executions, and
incident_evidence, then writes candidate_id=openclaw_incumbent records that can
be scored by scripts/ai-agent-replay-scorecard.py.
"""
from __future__ import annotations
import argparse
import asyncio
import json
import sys
from datetime import timedelta
from pathlib import Path
from sqlalchemy import and_, func, select
ROOT = Path(__file__).resolve().parents[1]
API_SRC = ROOT / "apps" / "api"
sys.path.insert(0, str(API_SRC))
from src.db.base import get_db_context # noqa: E402
from src.db.models import AgentSession, AutoRepairExecution, IncidentEvidence # noqa: E402
from src.services.agent_replacement_evaluator import ( # noqa: E402
build_openclaw_incumbent_record,
)
from src.utils.timezone import now_taipei # noqa: E402
async def main_async() -> int:
parser = argparse.ArgumentParser(
description="Export OpenClaw incumbent replay JSONL from DB."
)
parser.add_argument("--output", required=True, help="Output JSONL path")
parser.add_argument("--limit", type=int, default=100, help="Max incidents")
parser.add_argument("--days", type=int, default=30, help="Lookback days")
parser.add_argument(
"--run-id",
default=f"openclaw-incumbent-{now_taipei().strftime('%Y%m%d%H%M%S')}",
help="Replay run id",
)
args = parser.parse_args()
cutoff = now_taipei() - timedelta(days=args.days)
records = []
try:
async with get_db_context() as db:
incident_ids = await _incident_ids(db, cutoff=cutoff, limit=args.limit)
for incident_id in incident_ids:
coordinator = await _latest_coordinator(db, incident_id)
if coordinator is None:
continue
execution = await _latest_execution(db, incident_id)
evidence = await _latest_evidence(db, incident_id)
turn_count = await _agent_turn_count(db, incident_id)
records.append(
build_openclaw_incumbent_record(
run_id=args.run_id,
incident_id=incident_id,
coordinator_output=coordinator.output_json,
execution_success=(
execution.success if execution is not None else None
),
verification_result=(
evidence.verification_result if evidence is not None else None
),
audit_trace_complete=turn_count >= 3,
latency_ms=float(coordinator.latency_ms or 0),
coordinator_degraded=bool(coordinator.degraded),
)
)
except Exception as exc:
print(
json.dumps(
{
"error": "openclaw_incumbent_export_failed",
"detail": str(exc),
"output": args.output,
"run_id": args.run_id,
},
ensure_ascii=False,
sort_keys=True,
)
)
return 2
output = Path(args.output)
with output.open("w", encoding="utf-8") as handle:
for record in records:
handle.write(json.dumps(record.__dict__, ensure_ascii=False, sort_keys=True))
handle.write("\n")
print(
json.dumps(
{
"output": str(output),
"records": len(records),
"run_id": args.run_id,
},
ensure_ascii=False,
sort_keys=True,
)
)
return 0
async def _incident_ids(db, *, cutoff, limit: int) -> list[str]:
stmt = (
select(AgentSession.incident_id)
.where(
and_(
AgentSession.agent_role == "coordinator",
AgentSession.created_at >= cutoff,
)
)
.distinct()
.order_by(AgentSession.incident_id.desc())
.limit(limit)
)
result = await db.execute(stmt)
return [str(row[0]) for row in result.all()]
async def _latest_coordinator(db, incident_id: str):
stmt = (
select(AgentSession)
.where(
and_(
AgentSession.incident_id == incident_id,
AgentSession.agent_role == "coordinator",
)
)
.order_by(AgentSession.created_at.desc())
.limit(1)
)
result = await db.execute(stmt)
return result.scalar_one_or_none()
async def _latest_execution(db, incident_id: str):
stmt = (
select(AutoRepairExecution)
.where(AutoRepairExecution.incident_id == incident_id)
.order_by(AutoRepairExecution.created_at.desc())
.limit(1)
)
result = await db.execute(stmt)
return result.scalar_one_or_none()
async def _latest_evidence(db, incident_id: str):
stmt = (
select(IncidentEvidence)
.where(IncidentEvidence.incident_id == incident_id)
.order_by(IncidentEvidence.collected_at.desc())
.limit(1)
)
result = await db.execute(stmt)
return result.scalar_one_or_none()
async def _agent_turn_count(db, incident_id: str) -> int:
stmt = select(func.count()).select_from(AgentSession).where(
AgentSession.incident_id == incident_id
)
result = await db.execute(stmt)
return int(result.scalar() or 0)
def main() -> int:
return asyncio.run(main_async())
if __name__ == "__main__":
raise SystemExit(main())