# Compaction-loss audit — full documentation (P32) Provider: TextForAgents LLC · Support: support@textforagents.com ## What it does When an agent's context window fills and the harness compacts it, the summarizer can silently drop the goal, the acceptance criteria, or the safety rules the run was supposed to obey (measured safety-rule retention fell from 53% after one compaction round to 10% after five across 20 production configurations, and nobody logs what was dropped). This witness pins your invariants verbatim BEFORE compaction, diffs the post-compaction context span by span, and returns PRESERVED / DEGRADED / LOST per pinned span with the dropped text named. Lost or degraded spans are re-injected verbatim — or the audit returns HALT when a critical span cannot be restored. Every audit issues an Ed25519-signed compaction receipt. ## The flow (REST mirror; the same operations are MCP tools at POST /mcp) 1. Pin: POST /snapshot {event_id, spans[]} classifies each span INVARIANT / FACT / EPISODIC (explicit [invariant]/[fact]/[episodic] markers, else conservative prefix heuristics — goal:/objective:/ acceptance:/safety:/rule:/must/never/always/do not/shall — no model pass) and hash-pins the non-episodic spans verbatim. EPISODIC spans are recorded in the snapshot but excluded from verdicts. POST /snapshot returns the full snapshot object; keep it — an audit may present it inline, or reference it by event_id (Bearer token accounts only; MCP callers without a token get a snapshot_ref). 2. Write-time pinning: POST /pin {text} (Bearer) classifies and hash-pins ONE span at the moment it is written into context and returns its pin record {id: "pin-", text, cls, critical, sha}. Pass pinned_ids in a later POST /snapshot body to include those spans in the snapshot without re-classification. 3. Audit: POST /audit {event_id | snapshot | snapshot_ref, post_text} returns the signed receipt: per-span verdicts + counts + dropped text. One audit = one event (see Prices). 4. Re-inject: POST /reinject {event_id | snapshot | snapshot_ref, post_text, allow, receipt} — receipt is REQUIRED and must be the verified signed receipt that the audit of this event issued (tampered or foreign receipts are rejected). Returns {status, text}: status OK with the verbatim pinned block appended for every DEGRADED/LOST span, or status HALT (text unchanged) when allow=false and any DEGRADED/LOST span is critical. Re-injection is idempotent. 5. Verify a receipt any time: POST /receipts/verify {receipt} -> {valid}. Verification resolves the receipt's key_id against the service's published keyring (current + retired keys) — a key embedded in a receipt is never trusted on its own. ## Payload shapes (exact; see /openapi.json for schemas + examples) - snapshot: {"event_id": "evt-1", "spans": ["Goal: ...", "Safety: ..."], "pinned_ids": ["pin-", ...]} — spans is an array of STRINGS (the pre-compaction context, one string per span); pinned_ids optional. - pin: {"text": "Safety: never deploy without a signed receipt"} - audit: {"event_id": "evt-1", "post_text": ""} — or {"snapshot": {the snapshot object returned by /snapshot}, "post_text": "..."} — or, for tokenless MCP callers, {"snapshot_ref": "", "event_id": "evt-1", "post_text": "..."}. - reinject: {"event_id": "evt-1", "post_text": "...", "allow": true, "receipt": {the receipt object returned by /audit}} - A span is critical when it is an INVARIANT whose text mentions "safety" or "never" or carries a [critical] marker. ## Verdicts and the DEGRADED policy (carried as built, not retuned) PRESERVED: the span's normalized text (case/punctuation-insensitive) appears verbatim in the post-compaction context. DEGRADED: not verbatim, but token containment >= 0.6. LOST: below 0.6. A paraphrase of a safety rule is DEGRADED by design — the consequence is cheap verbatim re-injection, not a halt, unless the span is critical and re-injection is disallowed (allow=false), which returns HALT. ## Limits — stated plainly - V1 SURFACE is the hosted API/MCP service called at the compaction boundary. Harness hook integrations for real harnesses are a packet item and are NOT built or claimed in v1. - COVERAGE LIMIT: snapshot-time classification is marker/heuristic based. A goal stated with no marker and no heuristic shape can escape snapshot-time classification. Write-time pinning (POST /pin) is the mitigation for spans you control at write time; spans neither pinned at write time nor shaped at snapshot time are outside coverage. - The service only restores the operator's own pre-compaction text verbatim. It never selects/restricts/replaces context based on a content-quality or safety score, never substitutes curated context, and does no host file-change scanning. - Persistence: snapshots, pins, receipts and event counters are stored per-operator with strict account isolation (nothing is shared across accounts; there is no global corpus presented as your data) and are retained for 12 months, then purged. Deletion on request via support. - Request bodies over 2000000 bytes are refused (HTTP 413); at most 5000 spans per snapshot. ## Prices - Per-event API: $0.01 per event (10000 atomic USDC, x402, Base). One event = one audit; the snapshot, pin and reinject calls around it are included. An unpaid audit returns HTTP 402 with the x402 challenge; a payment must VERIFY (EIP-3009 signature, amount, payTo, validity window, unused nonce) — a header alone is never proof, and a spent nonce is rejected as a replay. Request validation runs BEFORE any payment is charged. - Solo: $9/mo per operator (9000000 atomic USDC) via POST /subscribe {tier: "solo"} — up to 200 events per calendar month included. - Fleet: $19/mo per operator (19000000 atomic USDC) via POST /subscribe {tier: "fleet"} — up to 2,000 events per calendar month, team receipt archive, HALT policy. Over-quota audits on an active subscription are charged per-event ($0.01) or refused with a 402 challenge. - Subscription term is 30 days; renewal extends from the later of now / current expiry. - TRIAL: the FIRST compaction audit is free, one time only — claim it with POST /trial/claim (Bearer token), enforced server-side per operator account AND per payer wallet: both keys are checked on every claim, so a second account with the same wallet does not re-grant it. ## Receipts and signing keys Receipts are Ed25519-signed. The current verification key (and every retired key still inside receipt validity windows) is published at GET / and GET /.well-known/x402 under receipt_signing. Receipts carry key_id, issued_at and valid_until (30-day validity window). Rotation retires the old key into the published list — receipts signed before a rotation keep verifying throughout their validity windows. ## Accounts and tokens POST /register {account_name, payer_wallet} returns {account_id, token}. Registration is rate-limited per IP. The token is shown ONCE; only its SHA-256 hash is stored. Identity comes only from the token — never from a body or header field. GET /history returns only your own account's receipts; GET /events returns only your own event counter. POST /tokens/revoke {token} revokes a token under your own account only. Lost-token recovery is a manual support@ step. ## Routes GET / · GET /docs · GET /openapi.json · GET /llms.txt · GET /.well-known/x402 · GET /health (all public, no auth) POST /register · POST /pin (Bearer) · POST /snapshot (Bearer, or MCP tokenless with snapshot_ref) · POST /audit (Bearer subscription/trial, or x402 per-event) · POST /reinject (signed receipt required) · POST /receipts/verify · POST /trial/claim (Bearer) · POST /subscribe (Bearer, x402) · POST /mcp (MCP JSON-RPC) · GET /history (Bearer) · GET /events (Bearer) · GET /subscription (Bearer) · POST /tokens/revoke (Bearer)