Up and down arrows move between paragraphs. Left and right move between actions.
A stage that hands forward a condensation of its input has made a choice on behalf
of every stage after it. Summarisation is a lossy encoding — one that discards
material it has no way of restoring later — and its loss function is "what a reader
would want in general". A downstream consumer with a particular need is relying on a
compression optimised for someone else, and no amount of prompt work on the consumer
brings back what the compressor dropped.
That is why a two-stage pipeline can score below the single prompt it replaced while
every component looks fine on its own terms. The summaries read well, the answering
step answers the question it was asked correctly, and the information left the system
at the boundary. A handoff contract is the set of decisions that stops this happening,
and it is a design activity rather than something to discover in an incident review.
Pull failures and read the intermediate next to the question, and the pattern arrives
immediately.
text
Q: "How many days do I have to appeal a denied claim?"Source (§7.3): "A member may request reconsideration of an adverse benefitdetermination within 180 calendar days of receiving written notice of denial.Expedited appeals for urgent care must be filed within 72 hours."Step 1 summary (excerpt): "The plan provides an appeals process for deniedclaims, with expedited handling available for urgent cases. Members shouldfile promptly after receiving notice."Step 2 answer: "You should file your appeal promptly after receiving thedenial notice. For urgent care, an expedited appeal is available."
Step two did nothing wrong. Given a summary that says "promptly", the answer it
produced is the correct answer to the question it was asked, and it has no mechanism
for knowing that 180 and 72 ever existed. Step one was asked to summarise a document
without being told what the summary was for, so it had to compress for the average of
every possible question, and a good general-purpose summary of a policy document keeps
the structure and drops the specific numbers. Taking 8,300 tokens down to 400 is a 95%
reduction with no knowledge of which 5% matters.
Why the numbers in particular are the first thing to go is worth one more step,
because it is not an accident of this summariser. A summary is chosen to be
useful to a reader whose question is unknown, so it keeps what most questions
would want. What most questions want is the shape: that there is an appeals
process, that urgent cases are handled differently, that a deadline exists.
A specific figure is the opposite kind of content — it answers exactly one
question and is dead weight for every other, and it cannot be inferred from
anything else in the document if it is dropped. So a compressor optimising for
the average question systematically discards precisely the material that a
single specific question needs, and it does this most reliably when it is
working well. The failing intermediate above is not a bad summary. It is a good
summary of the wrong thing.
The reduction figure follows from the two token counts: 400 out of 8,300 is
under one part in twenty, so nineteen parts in twenty were discarded. The choice
of which nineteen was made by a step that had never seen the question.
A one-line change tests the diagnosis, and it is the cheapest thing to try before
redesigning anything:
text
Summarise the sections of this policy document that bear on the member'squestion. Preserve every number, deadline, dollar amount and named conditionverbatim. Question: {question}
Question-conditioning treats the symptom. The design decision underneath is what the
intermediate is for, and four questions have to be answered explicitly.
Contract dimension
The failing design
A design that holds
What the intermediate carries
Free prose, unspecified length
Typed fields plus provenance: section ids and verbatim quotes
Whether it is validated
No; whatever comes back is passed on
JSON Schema, validated before step two runs, retried on failure
Whether step two sees the source
No
Yes — the intermediate points into a document step two also holds
What step two does with a bad intermediate
Undefined; it answers anyway
Defined: abstain — return no answer and say why — escalate, or fall back to the single-call path
The third row is the one that changes the accuracy number, so it is worth being concrete
about what it means. Step two does not need the intermediate to contain everything; it
needs the intermediate to be a pointer into something it can still read. Restructured
that way, step one's job stops being compression and becomes selection with evidence:
json
{ "relevant_sections": [ { "section_id": "7.3", "why": "states appeal deadlines", "quote": "within 180 calendar days of receiving written notice of denial" } ], "insufficient_evidence": false}
Written as a schema the intermediate is enforced rather than requested, and the
constraints that matter are not the obvious ones:
The enum on section_id is generated per request from the document's own table of
contents, abbreviated above, which moves the check from validation into decoding: the
model cannot emit a section number the document does not have, because those tokens are
not available to it. That change alone removed the 0.9% of requests where step one cited
an invented section, at the cost of compiling a schema per document, which measured at
about 8 ms and is cached on document hash. The maxItems cap matters for a different
reason — an unbounded selection step drifts towards returning most of the document, and
capping it at four keeps step two's context predictable in size and cost.
The insufficient_evidence flag is not decoration. It is the field that gives step two
something to do other than answer, and without an explicit way for step one to say "I
found nothing", a selection step will always return its best guess.
Any abstention field needs calibrating against the queue it feeds, because the cost of the
flag is entirely downstream of the model. A flag that fires too readily converts answerable
questions into human work at whatever a human minute costs; one that fires too rarely leaves
the confident wrong answers the flag existed to prevent. Neither error is visible in an
accuracy number, because an abstention is scored as neither right nor wrong, so the
measurement to make is the false-abstention rate — sample the flagged questions, have a
reviewer say which of them the pipeline could in fact have answered, and multiply through by
traffic. That converts a threshold into a monthly figure in hours, which is the unit the
organisation absorbing the escalations reasons in.
The conversion is four multiplications long and every one of them is the kind a
review can check on a whiteboard, which is what makes it usable in a meeting
with people who do not read code. Start with the flag's fire rate. Sample the
flagged questions and have a reviewer say which of them genuinely had no answer;
the remainder is the false-abstention share of the flagged population. Multiply
that share by the fire rate and you have the false-abstention share of all
traffic, which is a much smaller number and the one that matters. Multiply that
by monthly volume for a count of unnecessary escalations, and by the handling
time for a figure in hours. Each step changes the unit, and only the last unit
is one anybody outside engineering can act on.
Every handoff needs a defined behaviour for a malformed intermediate, because "step two
answers anyway" is the default and it is the worst option available. Validation gives you
a branch point:
Diagram
Flowchart: Step 1: select sections + quotes → Valid against schema?; Valid against schema? → no; no → Retry once with the validator error; Retry once with the validator error → Valid now?; Valid now? → no; no → Fall back: single call over full document; Valid against schema? → yes; yes → insufficient_evidence?; Valid now? → yes; yes → insufficient_evidence?; insufficient_evidence? → yes; yes → Escalate to a human agent; insufficient_evidence? → no; no → Step 2: answer from quotes + source; Step 2: answer from quotes + source → Quotes present in source?; Quotes present in source? → no; no → Fall back: single call over full document; Quotes present in source? → yes; yes → Return answer.
Two things in that diagram are load-bearing beyond the validation itself. The fallback
path keeps the original single-call prompt alive as a degraded mode, which costs you the
maintenance of a prompt you hoped to delete and buys you a pipeline that cannot fail
closed on a schema error; it fired on 1.1% of requests in the first month. And the check
that the quotes actually appear in the source document is a cheap string containment test
that catches step one inventing a quote, which happened on 0.6% of requests and is the
failure that would otherwise produce a confidently wrong answer with a fabricated citation
attached to it.
Prompt decomposition — read the current version at https://daviddominguez.dev/learn/en/prompt-decomposition/step-two-cannot-fetch-what-step-one-s-condensation-threw-away