AgentProvenance

verifiable agent observabilityDemos Documentation
Demos/External evaluatorOptional integration

LLM Judge — the judge is itself audited

English | 简体中文

An external LLM reads a captured run's full trajectory and delivers a structured security verdict; the verdict is written back into the store as graph-attached signals. The twist: the judge itself runs under agentprov record, and its own LLM requests/responses are attached to the judge's provenance run as tls_write/tls_read evidence and materialized into llm_call nodes — so "which LLM call produced this verdict" is itself verifiable provenance.

target run (e.g. snake-supply-chain)          judge run (recorded)
  EvalContext: ALL runtime events  ──────▶  judge.py
  lens summaries / risks / verify           │  chunks trajectory, calls LLM
                                            │  every request/response retained
  signals (quality dimension)  ◀────────────┘  sha256 + tls evidence
    llm_judge.verdict / llm_judge.finding.*     └─▶ llm_call nodes in the
    evidence: coverage + judge attestation           judge run's own DAG

Command language

Commands default to English. Add --lang zh-CN for Chinese help, progress, verdict labels and diagnostics. Evaluation prompts, original model prose, retained request/response bytes and exported JSON stay unchanged. The Chinese display marks original summaries and findings as such and explicitly identifies the keyless offline fixture.

python3 demo/llm-judge/judge.py --lang zh-CN --help
AGENTPROV_BIN="$PWD/agentprov" python3 demo/llm-judge/judge.py run --offline --lang zh-CN

Open from the portable CLI

From an extracted precompiled release:

./agentprov demo llm-judge

This opens the formatted offline guide. It does not run the evaluator or call a provider. The commands below are separate, explicit setup and evaluation steps.

Run it

With an extracted release, run the keyless example from the archive root:

AGENTPROV_BIN="$PWD/agentprov" python3 demo/llm-judge/judge.py run --offline

This uses a fixture verdict to exercise the integration; it is not a live model evaluation. For a source checkout, run the following from demo/llm-judge/.

One file, stdlib only — judge.py run orchestrates everything (builds the binary if AGENTPROV_BIN is unset, imports the bundle, re-executes itself as judge.py judge under agentprov record, attaches the LLM evidence, imports the verdict):

python3 judge.py run                            # judge the snake bundle end to end
python3 judge.py run --offline                  # keyless: offline fixture verdict
python3 judge.py run --run <id> --data-dir <dir>  # judge any existing run

Bring your own LLM

This demo is a pattern, not a product binding: any external LLM can act as the security judge, because the judge only needs (a) an HTTP chat endpoint and (b) the evidence contracts below. Two wire protocols cover essentially every hosted or local model; pick one with environment variables (the shared demo env file ~/.agentprov-demo/deepseek-claude.env is auto-loaded):

protocol endpoint env key env examples
anthropic ANTHROPIC_BASE_URL (default api.anthropic.com) ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY Claude, Anthropic-compatible proxies
openai OPENAI_BASE_URL (default api.openai.com) OPENAI_API_KEY OpenAI, Qwen, Moonshot, vLLM, Ollama (http://localhost:11434)
openai @ deepseek — DEEPSEEK_API_KEY (shortcut) DeepSeek native API

AGENTPROV_JUDGE_PROVIDER=anthropic|openai forces the protocol when several keys are set; AGENTPROV_JUDGE_MODEL picks the model. Whichever provider is used, the judge's requests/responses are sha256-attested in the verdict and attached to the judge run as tls evidence — swapping the model never weakens the audit trail. Without any token the demo completes in offline fixture mode (verdict derived from stored risk signals, clearly labeled).

Extensibility contract

  • No event-type filter anywhere. judge.py serializes every runtime_event in the EvalContext verbatim (type-agnostic payload compaction only). When the sensor grows new capture dimensions (dns_query, setuid, TLS plaintext, ...), they flow into the judge prompt with zero changes here.
  • Full trajectory, budgeted. If the trajectory exceeds the per-call budget it is chunked chronologically and map-reduced (per-chunk observations → final verdict). Nothing is silently dropped; the verdict records coverage (events_total / chunks / mode).
  • Self-audit deepens automatically. On this macOS path the judge's LLM exchange is app-asserted (telemetry ingest-jsonl --format native). On a Linux host with the eBPF sensor + SSL uprobe, the same HTTP call is captured at the kernel boundary instead — the demo's trust level upgrades with observability depth, no demo changes.

Contracts used (all pre-existing)

step surface
read evidence signal context --run (EvalContext), ai call verify_run/list_risks/get_signals, graph lens --json
write verdict signal import --run --file → signals table, quality dimension (note: the plural signal import-batch only validates, it does not persist)
judge self-audit record --json -- python3 judge.py judge ..., telemetry ingest-jsonl --format native, graph materialize-llm

Known gap (honest): imported quality signals show up in signals list, ai call get_signals, and the dashboard signals panel, but graph lens --lens security currently renders only risk_signals-table rows — the verdict is graph-referenced (graph_ref_kind=run) but not yet a lens node.

Optional Python 3 example in demo/llm-judge in the archive. Keyless fixture available; live judging requires a model endpoint and credentials.