Skip to content

8 min read

A conclusion without its basis invites the receiver to invent one

On this page

A freight forwarder ran 1,240 automated quotes a week through two agents. Pricing computed the number; approval checked it against margin floors and either released it to the customer or sent it to a human. Approval was rejecting 31% of quotes, roughly 385 a week, each landing in a queue where a commercial analyst spent about eight minutes reconstructing the quote and, in the large majority of cases, approving it unchanged. That is around 51 hours a week of human time spent agreeing with a machine.

The rejection reasons were specific and confident. A typical one:

text
REJECT quote Q-88213 (customer C-4471, $3,910 Rotterdam→Chicago, 2×40HC)
Reason: quoted rate is 14.2% below the standard lane rate of $4,558 and
implies a discount tier the account does not hold. No congestion surcharge
applied despite the Rotterdam surcharge being active from 2026-01-15.
Margin at quoted price falls below the 12% floor.

Every factual claim in that rejection is wrong about this quote and correct in general. C-4471 does hold a discount tier — it was upgraded in November under a volume commitment. The congestion surcharge was waived for this customer through March as part of a service credit after a missed sailing. The margin at $3,910 is 15.8%, because the carrier contract for that lane was renegotiated in December at a rate 9% below the one approval was working from. Pricing knew all four of those things when it produced the number. What crossed the boundary was the customer ID and $3,910.

The receiver does not have the option of not knowing

Approval's job is to check a number, and checking a number requires something to check it against. Given $3,910 and nothing else, it does the only thing available: it assembles a derivation from what it can reach — the published lane rate table, the default surcharge schedule, the margin floor policy — arrives at $4,558, and finds a $648 discrepancy it cannot account for. The rejection is not a malfunction. An auditor handed an invoice total with no line items behind it would do the same thing, and would be right to.

What makes this expensive is that the reconstruction is plausible rather than random. If approval produced nonsense, someone would have looked at the approval agent's prompt in the first week. Instead it produced a defensible alternative price, with reasoning that referenced real rate tables and real policy, which made the failure look like a disagreement between two competent components. The team spent three weeks tuning the approval agent's instructions — raising its tolerance band, telling it to defer to pricing on discount tiers, adding examples — and moved the rejection rate from 31% to 24% while making approval materially worse at catching the quotes that genuinely were mispriced. Every one of those changes was an attempt to make a receiver reason correctly about information it did not have.

Sequence: pricing agent → pricing agent (contract rate, tier, waiver, index); pricing agent → approval agent ({customer: C-4471, quote: 3910}); approval agent → approval agent (rebuild from list rate + default surcharge); approval agent → pricing agent (REJECT (below floor, tier not held)); pricing agent → pricing agent (recompute from the same inputs); pricing agent → approval agent ({customer: C-4471, quote: 3910}); approval agent → human queue (escalate after 2nd rejection).

The loop in the middle is the part that costs money twice. Pricing's response to a rejection is to recompute, and since its inputs have not changed, it produces the same number, so the second exchange is pure expense. Two extra model calls per rejected quote at roughly 9k input tokens each, plus the escalation, plus the analyst's eight minutes. The mechanism that would break the loop — approval telling pricing which assumption it disagreed with, and pricing correcting it — needs the two of them to be looking at the same basis, which is the thing that is missing.

Setting what was held against what arrived

The evidence that ended the three weeks of prompt tuning came from a sample rather than an argument. Someone pulled 40 rejected quotes, and for each one wrote down the specific assumption the rejection reason rested on and then checked whether pricing had held a contradicting value at the moment it produced the number. In 37 of the 40 it had. That single ratio reframed the question from "why is approval unreliable" to "why is approval working from assumptions when the sender has facts", and it took about two hours to produce. Sampling failures and attributing each one to a specific input is a cheap move that is easy to skip, because the failures individually look like judgement calls and judgement calls invite you to tune the judge.

Arguing about which agent is at fault goes nowhere, because both are behaving sensibly given their inputs. The way to make the problem concrete is to write down the sender's state and the received representation side by side and mark what happened to each item in between.

what pricing heldwhat arrivedlosseffect on approval's next action
carrier contract CN-2291, Rotterdam→Chicago, $2,840/40HC, valid to 2026-06-30nothingomissionrebuilds from the $3,120 list rate, inflating expected price by $560
customer tier: Gold since 2025-11-04, volume commitment 400 TEU/yrcustomer ID onlyomissionassumes Standard tier; treats the discount as unauthorised
congestion surcharge waived to 2026-03-31, credit ref SC-1187nothingomissionapplies the $180 surcharge it believes is missing
fuel index BAF 0.184 as of 2026-01-28 09:00nothingomission + staleness riskuses its own cached index, 0.191, from an unstated date
confidence: rate confirmed, tier confirmed, waiver confirmed by opsnothingomission of certaintytreats every input as equally open to challenge
"2×40HC" equipment string"2×40HC"ambiguity survivesreads as two 40ft high-cube containers, which is correct here and is not always
final price $3,910$3,910nonethe only thing it can check, and it cannot check it

Laid out this way the diagnosis stops being a matter of opinion. Five of the seven items pricing held did not cross at all, and the one item that did cross is the only one approval cannot independently verify. Every rejection reason in the message at the top of this chapter maps to a specific row, which means the fix is a schema change rather than a prompt change.

Omission is the dominant loss here, and it is worth separating from the three it gets grouped with. Truncation is a different failure — the information crossed and was cut, usually by a length limit, usually at the end, so a payload that fits ten evidence items and holds fourteen loses the last four in whatever order the sender happened to emit them. Staleness is a third: the fuel index row above is an omission today, and if pricing starts sending the index without an as-of timestamp it becomes a staleness problem instead, where approval uses a value that was true at 09:00 for a decision it makes at 16:40. Ambiguity is the fourth, and it is the only one that survives a perfect transmission: 2×40HC crossed intact and still requires the receiver to know a convention. Each of these needs a different fix, and calling all four "context loss" is how teams end up applying the wrong one.

What actually has to cross

The evidence the number was computed from, and the uncertainty attached to it. In the schema from chapter 1, that is the evidence list doing its job with real content in it:

json
{
  "schema_version": "1.1",
  "correlation_id": "q-88213",
  "goal": "approve or reject quote Q-88213 for customer C-4471",
  "conclusion": { "price_usd": 3910, "margin_pct": 15.8 },
  "evidence": [
    { "claim": "lane rate $2,840/40HC", "source": "contract:CN-2291",
      "as_of": "2026-01-28T09:00:00Z", "valid_until": "2026-06-30",
      "confidence": "stated" },
    { "claim": "customer tier Gold", "source": "crm:C-4471#tier",
      "as_of": "2026-01-28T08:55:00Z", "confidence": "stated" },
    { "claim": "congestion surcharge waived to 2026-03-31",
      "source": "credit:SC-1187", "as_of": "2026-01-12T00:00:00Z",
      "confidence": "stated" },
    { "claim": "BAF 0.184", "source": "index:baf_eu_na",
      "as_of": "2026-01-28T09:00:00Z", "confidence": "stated" }
  ],
  "open_questions": [
    { "question": "SC-1187 waiver has no written expiry in the credit note; "
                  "ops confirmed verbally on 2026-01-14",
      "blocks": false, "who_can_answer": "ops_desk" }
  ],
  "owner": "approval"
}

The payload grew from about 40 tokens to about 480. Rejections fell from 31% to 4% over the following month, and the rejections that remained were mostly genuine — two quotes priced from an expired contract, one where the tier lookup had returned a stale value. The human queue went from roughly 385 items a week to about 50, and the character of the remaining items changed from "explain to me why this is fine" to "this one is actually wrong."

The open_questions entry matters as much as the evidence. The waiver was confirmed verbally, and pricing knew that its own basis had a soft spot. Previously that knowledge simply evaporated at the boundary and approval treated all four inputs as equally solid, which is why it challenged the contract rate — the strongest input — as readily as the waiver. With the uncertainty transmitted, approval directs its scrutiny at the one input that deserves it, and a quote whose only weak point is a verbal waiver gets a targeted question to the ops desk rather than a blanket rejection.

Why this is not an argument for sending everything

Two neighbouring conclusions are wrong in ways that cost real money.

The first is that approval should have been given the ability to look things up itself — a tool that reads the contract table and the CRM. That is a reasonable capability and it does not solve this. Approval does not know that credit SC-1187 exists or that it applies to this quote, so it has nothing to look up; discovering the basis by search means re-deriving pricing's whole reasoning, which is both the expensive path and the one that reintroduces disagreement, because a second derivation from the same tables is a second opinion rather than a check. The check is only cheap when the sender says what it used.

The second is that if some evidence helped, all of the context would help more. Pricing's full working context on that quote runs to about 14k tokens: the customer's last eleven quotes, the carrier's entire rate card, three internal policy documents it retrieved and one it retrieved and discarded, its own step-by-step reasoning about equipment substitution. Sending all of it would have fixed the rejection problem too, and it would have made approval slower, more expensive, and worse at its job in a way that does not show up as a rejection rate. The evidence list in the payload above is 480 tokens because it holds the four values the price was computed from and nothing else, and that selection is a design decision someone made rather than a side effect of what happened to be in the sender's context.

Evidence also does not stay true. The BAF index, the bunker adjustment factor that tracks marine fuel prices and feeds into every freight rate, moves daily, and an as_of timestamp is what lets approval decide whether a nine-hour-old index is acceptable for a quote that expires in 48 hours or whether it needs refreshing. A payload that carries a value without saying when it was true has converted an omission into something harder to detect, because a stale number looks exactly like a current one and the receiver has no reason to question it. That is the general shape of the problem the next chapter takes up from the other direction: deciding what belongs in a payload is not a matter of including more, and the failure that comes from including too much is considerably harder to see than the one this chapter opened with.

Inter-agent communication — read the current version at https://daviddominguez.dev/learn/en/inter-agent-communication/a-conclusion-without-its-basis-invites-the-receiver-to-invent-one

Back to top