Files
nisse/scripts/classifier-evals.py
dongarandClaude Opus 5.5 e351d0a74e
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m37s
CI / e2e (pull_request) Successful in 4m5s
nisse-core: group modules into packages by architecture section
A pure move; behaviour unchanged.

- models (L9, L7): llm, catalog, probe, render, render_anthropic, context
- log (L7, L6): canonical, views
- state (L8): store (was state), reconcile, worker, cancel, dump
- knowledge (L10, L6.4): vault, memory
- conversation (L5): transport, matrix
- execution (L13): ssh (was execution), testing
- authority (P3, P4, L4): actions
- gitea, oplog, envfile stay at the top until each gains a second module.

Every import, monkeypatch target, script and doc reference rewritten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 21:16:09 +00:00

62 lines
1.9 KiB
Python

"""Export the action classifier's judgements from a state store (L4, M3.2).
uv run python scripts/classifier-evals.py STATE_DB [...] > cases.jsonl
uv run python scripts/classifier-evals.py STATE_DB --overridden
One JSON line per distinct judgement: exactly what the classifier was shown,
its verdict and reason, the model, what happened (ran, asked, allowed,
refused), and whether the operator overrode it. Overridden cases are the
ones worth adding to tests/integration/classifier_cases.yaml, with the
verdict the operator's choice implies.
The store is opened read-only, so a live agent's database is safe to read.
"""
import argparse
import json
import sqlite3
import sys
from dataclasses import asdict
from pathlib import Path
from nisse_core.log.canonical import StoredEvent, from_payload
from nisse_dev.evals import classifier_cases
def events(path: Path) -> list[StoredEvent]:
conn = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
rows = conn.execute(
"SELECT id, ts, type, payload, work_item, actor FROM canonical_events "
"WHERE type = 'action_verdict' ORDER BY id"
).fetchall()
conn.close()
return [
StoredEvent(
id=row[0],
ts=row[1],
event=from_payload(row[2], json.loads(row[3])),
work_item=row[4],
actor=row[5],
)
for row in rows
]
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("stores", nargs="+", type=Path)
parser.add_argument(
"--overridden", action="store_true", help="only cases the operator overrode"
)
args = parser.parse_args()
for store in args.stores:
for case in classifier_cases(events(store)):
if args.overridden and not case.overridden:
continue
print(json.dumps({"store": str(store), **asdict(case)}))
return 0
if __name__ == "__main__":
sys.exit(main())