Straight-through is the wrong number to lead with
A pipeline that processes 88% of documents without a human touch sounds like a solved problem, and in a demo it is. In an operation it describes where the cost is not. The 88% consumed compute and a few seconds; the 12% consumed a person, an email thread, a follow-up three days later, a second person who knows the counterparty, and a cycle time measured in business days rather than milliseconds. If you divide the team's payroll by the documents they actually touched, the number per exception is uncomfortable, and it is the number that decides whether automating documents changed anything about how the department is staffed.
This is why so many document programmes plateau after the first success. The initial deployment removes the easy volume, which is real value and shows up immediately. Then the residue stops shrinking, because the residue was never about the model. A missing insurance certificate does not become present when accuracy improves. A supplier's invoice that disagrees with the receipt does not resolve itself with a better parser. These are cases where the correct action is to go back to a human being who is not your employee and ask them for something — and no amount of extraction accuracy performs that step.
So the question worth asking about a document platform is not what its automation rate is. It is what happens to the documents that fail: who finds out, how quickly, what they are told, who is asked to fix it, in what words, and what the record looks like once it closes. That is the exception loop, and for most operations it is where the remaining money is.
What an exception actually is
"Exception" is used as a single bucket, which is the first mistake, because the classes have nothing in common operationally. They differ in who is able to resolve them, how long that takes, and whether the resolution is a correction or a decision. The useful taxonomy is by resolver, not by error type.
| Exception class | Example | Who can actually resolve it |
|---|---|---|
| Missing artifact | A required page, a signature, a supporting certificate, a second document referenced by the first. | Only the sender. Nothing inside your walls creates the missing thing, so the loop must leave the company. |
| Unreadable capture | A photo cropped mid-table, a scan at an angle, a page so compressed the digits are ambiguous. | The sender, and only if asked while they still have the original in front of them. |
| Contradicted value | Invoice total disagrees with the sum of lines; quantity disagrees with the goods receipt. | Sometimes internal — the receipt may be the thing that is wrong — and sometimes the counterparty. Routing this one incorrectly is the most common waste. |
| Out-of-policy value | An amount above an approval band, a payment term nobody agreed to, a currency the contract does not cover. | A named internal approver. This is not an error at all — it is a decision the system correctly refused to make alone. |
| Unknown counterparty | A vendor not in the master, new bank details, a first-time claimant. | Whoever owns the master data, plus a verification step that must not be performed over the same channel the document arrived on. |
| Suspected duplicate | The same invoice arriving twice through two channels, or a resubmission that looks like a new document. | Usually the system, with a rule — but only if it can see both records, which is a data problem rather than an extraction one. |
| Low confidence with no failed check | The model is uncertain, nothing contradicts it, and no rule fired. | An internal reviewer. This is the only class where "look at it again" is a coherent instruction. |
Two things fall out of that table immediately. The first is that most exception volume cannot be closed by looking harder at the document, which is what an internal review queue is designed to do — the resolution lives with someone outside the process. The second is that the last row, the one everybody builds for, is often the smallest. Teams instrument a review UI for uncertain fields and then discover the queue is full of missing attachments and unreachable suppliers, which the UI has no way to act on.
Worth naming the case that is not an exception at all: the out-of-policy value. A payment above an authority ceiling is the control working. If that lands in the same queue as a blurry scan, with the same urgency and the same interface, you have taught the operation to treat a governance checkpoint as a nuisance to clear — and authority that is proportional to consequence only survives if it is visibly different from a defect.
The loop, stated precisely
What follows is the sequence an agent has to run to close an exception that needs an external party. Each step is where a specific thing goes wrong, so it is worth being exact about them rather than describing this as "the agent handles it".
1. Detect, with the reason attached
An exception is raised by a check that failed, and the identity of that check is the payload. "Confidence 0.62" is not a reason; "the supplier tax identifier on page 1 does not match the registered value for this vendor" is. This is the difference between a queue item somebody has to investigate and a queue item that already contains its own resolution path. Systems that raise exceptions without carrying the failing check forward force every downstream step — routing, drafting, review — to re-derive what happened.
2. Classify and route by resolver
Routing decides who is asked, and getting it wrong is expensive in a way that does not show up in accuracy metrics: a contradicted quantity sent to the supplier when your own receipt was wrong burns a week and some goodwill. The routing rule should be explicit and inspectable per exception class per document type, because it encodes a commercial judgement — which side you assume is wrong by default — and that judgement differs between a strategic supplier and a marketplace seller onboarded yesterday.
3. Compose an ask that can be answered in one pass
Most chase messages fail because they are not specific enough to act on. "Please resend the invoice" produces the same invoice. What closes an exception in one round is a request that names the document, the field, the page, what was found, what was expected, and what an acceptable answer looks like — including the boring parts, like whether a photo is acceptable and where to send it. The generation step here is genuinely useful: turning a structured failure into clear prose, in the recipient's language, at the right level of formality for the relationship, is exactly what language models are good at.
4. Collect the response without trusting it
The reply arrives as an email with an attachment, a portal upload, or a message with a photo. It has to be bound back to the original exception — not just to the case, to the specific exception — and it has to be treated as untrusted input, because it is. A document supplied by an external party in response to your own automated request is the ideal delivery vehicle for instructions aimed at the pipeline reading it. The reply is data. It is never configuration.
5. Revalidate, not re-extract
The corrected document must run the whole check set, not the check that failed. This sounds obvious and is skipped constantly, because re-running one field is faster and the exception was about one field. But a resubmitted invoice is a different document: the total may now be legible and different, the date may have moved, a line may have been added while the sender was in there fixing things. Validating only the contested field is how a correction becomes a new error nobody looked for.
6. Close, supersede, or escalate — and record which
Closure has three shapes and they are not interchangeable. The exception resolved on the same document. The document was superseded by a new version, in which case both versions and the link between them belong in the record. Or the loop failed and a human takes it, which is a legitimate outcome and should be a first-class transition rather than an item ageing quietly in a queue.
The explanation is the part that has to be true
Everything above turns on one step: the system telling somebody what is wrong. That message goes outside the company, under your name, and it is the most visible artifact the pipeline produces. It is also the easiest place to generate something plausible and false.
The rule that keeps it honest is that the explanation must be derived from the failed check, not composed from the document. A model asked "explain why this invoice was rejected" will produce a fluent, confident, entirely reasonable-sounding reason — and it will sometimes be a reason the system did not actually apply. Sending a supplier a rejection whose stated cause does not correspond to any rule you run is worse than sending nothing: they fix the wrong thing, resubmit, fail again, and the second failure now looks like their fault. The generation step should be rendering a structured failure into prose, with the check identifier, the field, the location on the page and the expected condition supplied as inputs it may not contradict.
This is the same discipline as letting the extractor decline to answer, applied one layer out. An abstention that produces an honest "we could not read the amount on page 2, please send a clearer copy of that page" is more useful to a counterparty than a confident guess that quietly enters a payment run. And when the explanation is derived rather than invented, the message that was sent can be replayed against the check that caused it, which is what makes the exchange defensible months later.
One practical consequence: keep the sent message itself, not a template identifier plus parameters. The reconstruction that matters is "what did we tell this supplier on 4 March", and that question is not answered by a template version whose text has since been edited.
What the agent may do on its own
The moment an agent sends a message to somebody outside the company, the governance question stops being about extraction accuracy and starts being about authority. This is not the same conversation as an internal approval, because the failure mode is reputational and it compounds: nobody notices one bad email, and everybody notices four hundred.
| Question | Why it bites | What settles it |
|---|---|---|
| Under whose identity does it send? | A message from a named employee who never saw it attributes a statement to a person; a message from an obviously automated address gets ignored or filtered. | A dedicated, clearly identified sender with a real reply path to a human, and disclosure that the request was generated automatically. |
| Who is it allowed to contact? | Addresses harvested from the document itself are attacker-controlled, and contacting the wrong party can disclose commercial detail. | Recipients resolved from your own master data, never from the document under review. |
| How many times, and how far apart? | Automated follow-up has no natural fatigue. Left alone it produces a cadence no person would choose. | An explicit attempt ceiling and interval per exception class, with the counter visible on the case. |
| What may it say beyond the ask? | A message that negotiates, concedes a term or promises a date is doing commercial work under your letterhead. | A bounded scope: describe the defect, request the artifact, state the deadline, and hand anything else to a person. |
| Where does it stop? | The loop must terminate on cases it cannot close, or it turns into a machine that annoys people indefinitely. | A stop condition per class — attempts exhausted, ambiguity in the reply, a counterparty who disputes the premise — routing to a named owner. |
| What changed in a system of record? | Closing an exception often means writing something: a status, a corrected value, a released payment. | Write permissions scoped per action and per value band, with the same authority ceilings that govern any other committed decision. |
The pattern underneath the table is that an agent acting as a participant rather than a tool needs an identity, a scope and a stop condition, in the same way a junior employee does. The difference is that the employee has judgement about when a situation is unusual, and the agent has whatever stop conditions you wrote down.
Four ways this goes wrong
1. Automating the chase instead of removing the cause
The most seductive failure. A team builds an excellent loop for requesting missing certificates, closes thousands of them, reports the improvement — and never asks why the certificates are missing. Often the answer is a form that does not mark the field required, an onboarding step that never mentions it, or an upload widget that silently drops the second file. Exception automation should make the cause more visible, not less: if a single supplier or a single intake channel generates a disproportionate share of one exception class, that is a defect report, and catching the problem at capture beats chasing it afterwards every time.
2. Closing by timeout
An exception that ages past a threshold and disappears — auto-approved, auto-cancelled, or moved to a state nobody watches — converts a visible problem into an invisible one. This usually arrives as a well-intentioned cleanup rule for a queue that grew too long. If a case must close without resolution, it should close as an explicit, recorded decision with an owner, because that is the version that can be found later when somebody asks why a payment went out without the supporting document.
3. The loop that never terminates
The counterparty replies with something that does not resolve the exception. The agent asks again, slightly rephrased. They reply again. In a system without a stop condition this can run for weeks, and each iteration looks locally reasonable. Two guards fix it: a hard attempt ceiling, and a check that the reply actually changed something — a new artifact, a new value, a new fact. A reply that adds nothing should escalate, not restart the loop.
4. Measuring exceptions closed
Volume closed is the metric everyone reaches for and it rewards the wrong behaviour: it goes up when exceptions are created unnecessarily and when they are closed without being resolved. It is also perfectly compatible with an operation getting worse. The honest metrics are cost per exception actually resolved, first-contact resolution rate, the reopen rate within a month, and the ageing distribution rather than its mean — because a queue with a good average and a long tail is a queue with a few very unhappy counterparties in it.
The evidence the loop has to leave behind
An exception that touched an external party is a small negotiation with commercial consequences, and it needs a record that can be reconstructed without the people involved. The test is the same one that applies to any automated decision: given a case reference from a year ago, can you rebuild it.
- The failing check — which rule fired, on which field, at which location on the page, against which expected condition, under which configuration version.
- The routing decision — which resolver class was chosen and why, so a misrouted case can be diagnosed as a rule problem rather than a judgement call.
- The message as sent — the actual text, the recipient, the channel, the timestamp, and each follow-up, kept as content rather than as a template reference.
- The response as received — the artifact, its provenance, when it arrived, and the binding back to the exception it answers.
- The revalidation result — every check re-run on the corrected document, including the ones that had passed before, with the diff against the prior version.
- The closing transition — resolved, superseded or escalated; by the system or by a named person; and what was written to which system of record as a result.
None of that is exotic. It is the audit trail problem for non-deterministic systems with an extra participant, and the extra participant is the reason it matters more here than in a purely internal workflow: the other side has their own record of the exchange, and it will be produced in any dispute whether or not you kept yours.
The trade-off, stated honestly
Two arguments against automating this, both of which are sometimes right.
Some exceptions should be a phone call. A strategic supplier with a contested figure, a claimant in a bad situation, a customer whose third submission has failed — these are relationships, and routing them through an automated chase, however well written, is a decision to spend goodwill to save minutes. The reasonable design is not "automate everything with a polite tone". It is to segment: automate the high-volume, low-emotion, clearly-defined asks, and route the rest to a person early rather than after three failed rounds. Getting that boundary right requires knowing which of your counterparties are relationships and which are transactions, which is commercial knowledge the pipeline does not contain.
The loop is also more expensive to build than the extraction. External messaging means identity, deliverability, language coverage, reply parsing, threading, retries and a support path when it goes wrong — a genuinely larger surface than reading a field off a page, and one with far more of its complexity in the parts that are not machine learning at all. A team that automates exception handling before its extraction is stable will spend the effort chasing corrections for errors its own pipeline created, which is the pilot-to-production gap arriving with an outbound mailbox attached.
And there is a floor on how much of this disappears. A share of exceptions will always need a person, because they are not defects but judgements — whether to accept a document that is technically wrong, whether a discrepancy is worth pursuing, whether this counterparty gets an exemption. The goal is not zero exceptions. It is that the ones reaching a person are the ones that genuinely need one, arriving with the context already assembled.
Where to start
A short sequence, ordered by information gained per hour spent.
- Classify one month of exceptions by resolver — not by error type; count how many could only be closed by someone outside your company, and you will usually find it is most of them.
- Time the tail, not the mean — measure the ageing distribution of open exceptions and look at the oldest 10%, which is where the cost and the complaints are.
- Read ten chase messages your team actually sent — check whether each names the field, the page, what was found and what is expected; the ones that do not are the ones that took three rounds.
- Pick the single highest-volume, lowest-judgement class — usually a missing standard artifact — and automate only that loop end to end, including the stop condition.
- Write the stop conditions before the messages — attempt ceiling, interval, what counts as a non-answer, and who owns the case when the loop gives up.
- Run one reconstruction — take a closed exception and rebuild the whole exchange from the record alone; whatever you cannot reconstruct is what to instrument next.
- Feed the causes back — report exception rate by intake channel and by counterparty monthly, and fix the top source rather than chasing its output.
Closing thought
Extraction was the part of document work that looked hard and turned out to be tractable. What remains is the part that always looked like administration: finding out something is missing, working out who can supply it, asking clearly, checking what comes back, and leaving a record that survives the people involved. It is unglamorous, it is where the cycle time lives, and it is the difference between a pipeline that processes documents and an operation that finishes cases.
At Cogneris we treat the failed document as a first-class outcome rather than an error state: checks that name what failed and where on the page, abstention recorded as a result instead of a guess, revalidation that re-runs the full check set on a corrected submission, and audit metadata that records what was extracted, what was checked, what was superseded and who released it. If your straight-through rate is healthy and your cycle time is not, the exception queue is where the answer is. See how the API models these boundaries, the pricing page, or talk to our team. The documents that pass were never the work.