Skip to content

Retry on what happened, not on what the answer said about it - #270

Merged
adamjohnwright merged 2 commits into
mainfrom
010-t023-live-failure-signal
Sep 20, 2026
Merged

adamjohnwright merged 2 commits into
mainfrom
010-t023-live-failure-signal

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

T023, and it closes the loop on something the website session and I both flagged: an exception that is caught, logged, stringified into a prompt and paraphrased has been through a lossy channel by design. Matching on the far end recovers nothing.

The bug being fixed

The sweep decided whether to retry by matching the answer's prose, and one marker was "could not find out" — which is the wording src/reactome_mcp/answer.py instructs the model to use for a legitimate empty result:

"If the tools do not answer the question, say plainly what you could not find out."

So the marker matched correct answers by construction. Since a match triggers a retry, a real regression got a second attempt and could pass. The gate forgave exactly what it exists to catch.

The fix, where the information is discarded

answer_from_live_services takes an optional LiveReport and records the tool exception before stringifying it into the model's context — the last point at which it is still a fact rather than a paraphrase. The graph carries it out as live_tool_failed; the sweep retries on that.

TRANSIENT and _looks_transient are deleted, with a test asserting they do not come back. If prose matching returns it will return as a list of markers, and that is cheap to detect.

The report is optional, so none of the twelve existing call sites changed.

Verified by sabotage

  • Making the retry ignore the signal fails the retry test.
  • Making the report never populate fails the recording test.

And the retried case and the not-retried case now use the same answer text — "I could not find out." — so only the signal separates them. That property is what was missing before, and it is why the old test passed against the broken behaviour.

517 passed, 1 skipped; mypy over 139 files, ruff and ruff format clean.

🤖 Generated with Claude Code

adamjohnwright and others added 2 commits September 20, 2026 01:38
T023. The sweep decided whether to retry a question by matching the answer's
prose, and one of the markers was "could not find out" -- the wording
`src/reactome_mcp/answer.py` *instructs* the model to use for a legitimate
empty result. So the marker matched correct answers by construction, and since
a match triggers a retry, a real regression got a second attempt and could
pass. The gate forgave precisely what it exists to catch.

Matching further down the pipe could never fix that. The tool exception is
caught, logged, stringified into the model's context and then paraphrased; by
the time anything reads prose, "a lookup failed" and "there is genuinely
nothing" are the same sentence. The fix belongs where the information is
discarded.

`answer_from_live_services` now takes an optional `LiveReport` and records the
exception before it becomes text. The graph carries it out as
`live_tool_failed`, and the sweep retries on that. `TRANSIENT` and
`_looks_transient` are deleted, with a test asserting they do not come back:
if prose matching returns, it will return as a list of markers, and that is
cheap to detect.

The report is optional, so none of the twelve existing call sites changed.

Both halves verified by sabotage. Making the retry ignore the signal fails the
retry test; making the report never populate fails the recording test. And the
retried case and the not-retried case now use the *same* answer text, so only
the signal separates them -- which is the property that was missing before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamjohnwright
adamjohnwright merged commit e74fb6d into main Sep 20, 2026
10 checks passed
@adamjohnwright
adamjohnwright deleted the 010-t023-live-failure-signal branch September 20, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant