Repository navigation
A claim links to its source again. With gpt-6-luna at reasoning: 'low',
most answers printed a section id in brackets —
[editor-configuration/image-storage#connecting-your-own-server] — where the
link marker goes. The Sources list under the answer was right, and the claim had
no link. This release fixes the instruction, settles the markers in code for any
model, and makes eval fail on an answer without a link. Engine-spec 039.
It also stops a reader's first question from paying for what the package could
have known: two refused requests on every page load with OpenAI's reasoning
models, and a search step that brought back only what the turn already had.
Measured on ten live turns of one production site: 34 requests and 8 refusals
before, 27 and none after. Engine-spec 040.
Fixed
The citation rule is two sentences. The system prompt said: "Mark every
claim with a citation marker [1], [2] in the order of the citations array. It
holds section ids copied exactly from tool results, never marker numbers."
gpt-6-luna read the second sentence as a rule about the marker. It wrote the
id in brackets in 13 of 16 replays of one production turn, and in 5 of 6 live
turns. The rule now says: in the text, write numbers, and [1] is
citations[0]; section ids go only in citations. On the same replays: 0 of
24. On five questions of that site, 10 of 10 live turns linked every claim. The
prompt hash changes, so eval reports start a new history, and the next
docpilot index bakes the opener answers again.
The markers are settled with the citations. Before anything reads the
answer — the panel, the server's signed history, the turn log, the eval — the
turn checks every bracket in the text against the list it validated:
[section-id]becomes its[n]. A known id that only the text cites is
added to the end ofcitations; an earlier position never moves.- Several ids in one bracket,
[a#x, b#y], become[1][2], and so does
[1, 2], which the panel never linked. A continuation part,#anchor~2,
maps to its cited#anchor. - A number is a position in the list the model sent. When an invented id is
dropped from that list, the numbers after it are renumbered; before, each of
them linked the source one row down. A number that pointed at an invented id,
or past the end of the list, is deleted. - A bracket with an id that nobody handed out — a path, a
#, no spaces — is
deleted, as an invented citation's marker always was. - Code spans, fenced blocks, link text and image alt text are left alone.
A reply in prose, without the answer tool, that names known ids in brackets
now keeps them as citations and shows as a tentative answer. Before, it was
withdrawn as "I couldn't find this in the docs".
The first question of a page load spends no refusal on OpenAI. The
openai row now states the two rules OpenAI's migration guide gives for every
reasoning model, where 2.0.1 learned them from a 400 on each new page:
- a search step sends
reasoning_effort: 'none', because function tools on
Chat Completions take nothing else since GPT-5.4, and an omitted effort means
the model's own default,medium.chat.reasoning: 'auto'promises no
thinking on a search step, and on this service that has to be said; temperatureandtop_pare not sent beside any effort but'none', and
not beside silence either, which is the model's default effort.
gpt-6-luna pays nothing on its first question now, where it paid two refused
requests on every page load — 0.4 s and a request each. A model the up-front
word does not fit pays one: gpt-4.1 refuses the reasoning field, and the
transport stops sending it; gpt-5 and the o-series refuse the value 'none',
and the transport stops writing the word while the answer keeps your
chat.reasoning. That second refusal used to drop the reasoning field from
every answer of the session.
A search that finds nothing new says so. Every result already shown came
back as a row of its own — id, title, path and "shown in an earlier result" —
and that list was re-sent on every step after it. It is now one line with the
ids. The priming result names the query the gate ran, and the model asking for
that same search, by name or by leaving query out, is answered from the turn
like any repeated call. On the 2.1.0 prompt one step of every live turn ran a
search that returned exactly the primed excerpts; in ten live turns after the
fix none did, and the one question whose search found nothing new got the one
line, in both its turns.
An id is not painted while the answer streams. A section id the model
writes where [n] goes is turned into its marker when the turn settles; every
frame before it showed the id as plain text. It is now hidden until then, and
after a stop. ui-specs/020.
Added
A third hard gate: answers without a link. eval renders every answer to a
question the docs cover with the panel's own renderer. If one is shown with
sources and none of its [n] links, or it prints a section id in brackets, the
run fails with exit code 1, as on a hallucinated citation, and the report names
the records. Answers to negative records and an author's own openers are counted
on a separate line and fail nothing. The scorecard, docpilot status, the
server eval and the answer bench show the same gate.
A small model can fail it on the first run. In earlier reports,
gpt-4o-mini wrote no marker at all in about 30 % of its answers and qwen3:8b
in 35–55 %. Those answers showed the reader Sources and no link; the gate makes
that visible. Re-run with the new prompt before you read it as a regression.
index warns on a written answer with no marker. An opener you answered
in suggestions links its claims the same way: [1] is the first id in its
cite. One that links none still ships, with a warning.