Here is a field set that fits in a JSON object and reads well as a rendered ticket. Treat it as a contract: if the gate cannot populate a field, it must write an explicit null with a reason rather than omitting the key, so the reviewer knows the difference between "not applicable" and "lost."
{
"ticket_id": "esc-2026-04-1187",
"run_id": "run_9f3a2c11-b7",
"gate": {
"name": "citations_resolvable",
"version_ref": "gate-def@sha256:4e1b…",
"check": "every cited URL returns 200 and matches registered domain",
"observed": "3 of 41 citations returned 404",
"threshold": "0 unresolved citations permitted",
"trip_point": "citations[17], citations[28], citations[39]"
},
"input": {
"digest_sha256": "b1c9…",
"source_ref": "s3://kb/2026/04/inputs/b1c9.json",
"byte_size": 184223,
"redactions_applied": ["customer_email"]
},
"output_excerpt": {
"span": "citations[15:20]",
"text": "…as documented in the vendor guide (https://…/retired-page)…"
},
"tool_trace": [
{"step": 14, "tool": "http_fetch", "args_digest": "77ae…", "status": 404},
{"step": 15, "tool": "http_fetch", "args_digest": "91bd…", "status": 404}
],
"environment": {
"agent_image": "agent@sha256:0c2d…",
"knowledge_snapshot": "kb-2026-04-01",
"tool_registry_rev": "tr-8821",
"region": "us-east-2"
},
"reproduce": "quantarepro run --ticket esc-2026-04-1187 --offline-cache",
"proposed_decision": {
"action": "reject",
"reason": "citations point to pages retired after knowledge snapshot; refresh snapshot or drop claims",
"confidence": "medium",
"would_accept_if": "all cited URLs resolve against kb-2026-04-15"
},
"severity": "medium",
"owner": "content-verification",
"resolution_code": null
}
Three fields deserve explanation because they are the ones teams skip.
The input digest is a hash of the exact bytes the agent saw, plus a pointer to where those bytes live. Without it, two runs with the same prompt but different retrieved passages look identical in the queue, and the reviewer cannot tell whether a re-run will reproduce the failure. Hashing the input rather than storing it inline also keeps sensitive material out of the ticket body, which matters because handoff guidance warns against putting unrestricted copies of private records into a handoff note per Salted.
The reproduce field must be one command that a reviewer can paste. Idempotency guidance for long-running agents makes the same distinction that applies here: keep separate identifiers for the business operation, the execution attempt and the provider's reference, because they answer different questions and collapsing them into one ambiguous request ID makes a restart untraceable per PADISO's idempotency tutorial. The ticket ID, run ID and gate definition hash are exactly those three identities.
The proposed decision is the agent's own recommendation, stated as a decision rather than a summary, with a would_accept_if field that tells the reviewer what evidence would flip it. This is the cheapest way to make the gate auditable: the reviewer either accepts the proposal or records why not, and both outcomes are useful signal.
| Field | Why the reviewer needs it | What breaks without it |
|---|
| run and gate identifiers | Ties the ticket to a reproducible execution and a specific gate definition | Gate changes cannot be correlated with failure patterns |
| check and threshold | Tells the reviewer which rule tripped and how far off it was | Reviewer cannot distinguish a near miss from a systemic break |
| input digest | Confirms the reviewer is judging the same bytes the agent saw | Re-runs silently test different inputs |
| output excerpt | Shows the failing text in context, not just a flag | Reviewer must fetch the whole output to understand one line |
| tool trace | Exposes wrong arguments and failed calls | Silent partial success is invisible |
| environment fingerprint | Separates "the model failed" from "the environment changed" | Configuration drift gets misattributed to the model |
| reproduce command | One paste to observe the failure | Reviewer re-runs discovery by hand |
| proposed decision | Gives the reviewer a default to accept or reject | Every ticket starts from zero |