Preflight gates for irreversible agent actions

Before an agent moves money, writes to production or sends a message outside the building, make its last context compression a measured, signed event. The receipt gives you a byte pair, the worst checked window and a signature that verifies without trusting the vendor. Gate on the worst window: the average hides exactly the loss you are trying to catch.

The scenario below is an illustrative composite, not a customer. Every number in the table after it comes from a real call made on 2026-10-27, and the sample text plus both hashes are in the appendix, so the whole thing is reproducible.

The scenario

A payments agent issued a refund on an order that had already been refunded. Its context had been assembled from four sources: the order record, three support messages, the refund policy, and a summary of earlier tickets. The order record was authoritative. The ticket summary was eleven days stale and still described the refund as pending. The agent read both, preferred the summary, and acted.

The post-mortem took two days. The order record could be reconstructed from the database and the messages from the ticket export. The text the model actually saw could not: the assembly step compressed each source, kept no copy, and the compression receipt that existed recorded a ratio and a duration, nothing else.

The dropped sentence - the one stating the refund was already settled - had been removed as redundant, because a near-duplicate phrasing appeared in a document about a different order. That is not a compressor bug. The summary was informationally dense and the duplicate looked like duplication. It was a loss that nobody could see until the money had moved.

What the team needed at the moment of the incident was a number attached to the context they were about to act on, and a record they could hand to an auditor afterwards without trusting anyone's account of what had happened.

What a receipt records

This is the response from a verified-tier call on a 2,297-character sample:

fieldvaluewhat it means
output1,349 of 2,297 characters kept (0.5873)how much text survived
integrity.raw_sha25624871d57a162ea7a74b6b2f7281b10f9c6104ef9327a40b00c36893762cd966ethe input, anchored
integrity.output_sha2567b3ae3de27546f765a693e1cc68cbbd1fc81c000da18255ec0958ecb05d87006what the model would have seen
integrity.output_is_subsequencetrueno new text was produced; kept spans stay in input order
quality.fidelity_score0.9790mean preservation across the checked windows
quality.min_window_posterior0.8523the weakest single checked window
quality.coverage_fraction1.0every window was checked
quality.relaxed_ratio0.4506the first-attempt ratio its own window check rejected
quality.relax_steps2how many times it backed off before accepting
counts.windows_total19how many windows were scored
counts.windows_below_threshold0how many failed the acceptance threshold
counts.kept_span_count114how many spans survived
counts.pins_protectednegation 18, modality 10, numeric 3, temporal 2typed spans the compressor refuses to cut
key_ide6b78dfecceawhich signing key signed this

The call asked for a 0.45 target. The engine's first attempt at that ratio failed its own window check, so it backed off twice and kept 0.5873 instead. Both numbers are in the receipt: the ratio it tried and abandoned, and the ratio it shipped. A receipt that reported only the second one would not be a record.

The number to gate on

fidelity_score is a mean. On this call it is 0.9790, which is comfortable, while min_window_posterior is 0.8523. If your policy is "act when preservation is above 0.9, escalate otherwise", the mean passes and the weakest window fails. Only one of those is the answer you want before an irreversible step.

Coverage matters as much as the score. coverage_fraction is the share of the output that was actually checked; at 1.0 the score speaks for the whole output, and below that it speaks for less than you think. Treat partial coverage as no evidence rather than weak evidence.

What it does not prove

The receipt proves what happened to the bytes: what went in, what came out, what was measured, and that the record has not been altered. It does not prove that the context was complete, that the source documents were current, that the model reasoned well, or that the action was right. A gate on preservation catches silent loss during compression. It cannot catch a wrong decision made over perfectly preserved context - which is the failure in the scenario above, where the decisive information was already wrong before the compressor saw it.

Wiring it in

  1. Before the irreversible call, send the working context with squeeze_mode: verified and a target ratio.
  2. Store the receipt next to whatever the action produced, keyed by its own hashes so the pair joins the audit record.
  3. Gate on min_window_posterior against your threshold, and escalate to a human when it is below. Treat coverage_fraction below 1.0 as insufficient evidence if your tolerance is zero.
  4. Optionally verify the signature later through /v1/receipts/verify, passing the receipt and, if you kept them, the two texts. That is how an auditor checks the record without asking us anything.

The field-level detail, including which fields a strict gate still lacks, is in the preflight gate guide.

For agents

If you are an agent reading this page as text rather than a human reading it in a browser:

  • endpoint: POST https://api.nanotoll.dev/v1/compress, Bearer key, squeeze_mode one of deterministic (free), extractive ($0.0008) or verified ($0.0015 per call, USDC on Base L2)
  • response: {"compressed": "...", "receipt": {...}} - the fields above are the ones to store
  • verification: POST https://api.nanotoll.dev/v1/receipts/verify
  • live limits and caps: GET https://api.nanotoll.dev/v1/limits
  • a call that fails or exceeds its caps is not billed
curl -s https://api.nanotoll.dev/v1/compress \
  -H "Authorization: Bearer $NANOTOLL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "<context>", "squeeze_mode": "verified", "target_ratio": 0.45}'

Appendix: the sample, reproducible

Input, 2,297 characters, sha256 24871d57a162ea7a74b6b2f7281b10f9c6104ef9327a40b00c36893762cd966e (strip the quote markers and join the lines with single spaces to reproduce the bytes exactly):

Incident review, 2026-10-14. At 09:12 the payments agent issued a refund for an order that had already been refunded. The context it acted on had been assembled by the retrieval layer from four sources: the order record, the customer's last three support messages, the internal refund policy, and a summary of prior tickets. The order record was authoritative. The summary of prior tickets was stale by eleven days and stated the refund as pending. The agent read both, preferred the summary because it was shorter and appeared later in the context, and acted. The post-mortem took two days. The team could reconstruct the order record from the database and the support messages from the ticket export, but not the exact text the agent had seen: the assembly step had summarised and compressed each source, kept no copy, and the vendor's compression receipt recorded only a ratio and a duration. Nobody could say what had been dropped. The specific sentence that mattered, the one stating the refund was already settled, was not present in the version the model read. It had been dropped as redundant because a near-duplicate phrasing appeared in a different document that described a different order. The drop was not wrong in the sense of being a bug: the compressor kept the tokens it judged informative, and the stale summary was informationally dense. It was wrong in the sense that no one could see the loss until the money had moved. What the team wanted, at the moment of the incident, was a number attached to the context they were about to act on, and a record they could hand to an auditor afterwards without trusting anyone's account of what happened. They wanted the last compression before the action to be an event with an identity: input hash, output hash, a measured retention score, the count of windows that failed the retention threshold, and a signature that did not depend on the vendor being honest later. Everything else in this incident is ordinary engineering. The context assembly was ordinary. The stale summary was ordinary. The inability to reconstruct it afterwards was the part that turned a refund into a two-day investigation, and it is the part that a measured, signed compression step fixes, provided the measurement covers the worst window and not the average.

Output, 1,349 characters, sha256 7b3ae3de27546f765a693e1cc68cbbd1fc81c000da18255ec0958ecb05d87006, with output_is_subsequence: true against the input above.

Written with an LLM drafting assistant and revised by the operator. The incident is a composite illustration, not a customer story; the receipt numbers come from a live verified-tier call on 2026-10-27 and are reproducible from the appendix above.