feat(core): report once per session when tool calls were recovered from reply text - #226
Conversation
a3bfefd to
237d74f
Compare
…om reply text fallback.rs reads tool calls out of a reply's text when the completion carries no API tool_calls (or only broken ones), so an endpoint whose tool-call parser lags a model keeps working. Nothing said when that path ran: a session on the recovery path was indistinguishable, in the transcript and on the wire, from one whose endpoint parses calls, so neither the model (whose contract is native calls), a frontend, nor anyone asking whether the path is still worth carrying could tell. resolve_tool_calls now says whether the calls came out of the text, and the loop rides one harness note on the first such reply's tool result, once per session, naming the condition and what it means about the endpoint and model pairing. Once per session means once per session's record: hydration reads the note back from the transcript, or from the compaction archive if a prune has since dropped that exchange, so a resumed session does not repeat it (review findings). Test: a scripted endpoint whose only calls are markup in text; both calls run, exactly one note reaches the wire and the transcript; a restart after a prune moved that exchange to the archive still says nothing. It fails with the emission disabled and with either rehydration source removed.
237d74f to
436f400
Compare
| let fallback_recovery_reported = carries_fallback_recovery_note(&messages) | ||
| || carries_fallback_recovery_note(&sessions::load_archive(core, session_id)); |
There was a problem hiding this comment.
Archive write failure repeats the recovery advisory
Compaction continues saving the pruned transcript after append_archive fails. If that pruned exchange contained FALLBACK_RECOVERY_NOTE, a later resume finds the note in neither the active transcript nor the unreadable or missing archive, resets fallback_recovery_reported, and emits the supposedly once-per-session advisory again on the next markup-recovered tool call. Preserve this state durably when archival fails, or prevent pruning the note-bearing exchange until its archive write succeeds.
Artifacts
Authored archive-failure validation harness
- A scratch-checkout test harness that executes recovered tool calls, compaction, archive success/failure, restart hydration, and a second recovered call without modifying production code.
- Executed baseline with a writable archive: compaction removed the note from the transcript but retained it in the archive, and restart emitted zero repeated advisories.
Forced archive-failure reproduction log
- Executed failure path with the archive path made a directory: archive warning occurred, the pruned transcript persisted without the note, and restart emitted one repeated advisory.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/core/src/agent.rs
Line: 440-441
Comment:
**Archive write failure repeats the recovery advisory**
Compaction continues saving the pruned transcript after `append_archive` fails. If that pruned exchange contained `FALLBACK_RECOVERY_NOTE`, a later resume finds the note in neither the active transcript nor the unreadable or missing archive, resets `fallback_recovery_reported`, and emits the supposedly once-per-session advisory again on the next markup-recovered tool call. Preserve this state durably when archival fails, or prevent pruning the note-bearing exchange until its archive write succeeds.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Not fixing this one, on purpose. The condition needs the archive write to fail (a degradation the compaction path already reports loudly), a prune to have dropped exactly the exchange carrying the note, and a restart; the cost is one ~45-token advisory repeated once. A separate durable flag for an advisory, or holding a prune hostage to a sidecar write, is more machinery than the condition is worth, and the note is advisory by contract (it changes no behavior). Recorded as accepted residual.
Why
fallback.rsreads tool calls out of a reply's text when the completion carries no APItool_calls(or only broken ones), so an endpoint whose tool-call parser lags a model keeps working. Nothing said when that path ran: a session on the recovery path was indistinguishable, in the transcript and on the wire, from one whose endpoint parses calls. The model (whose contract is native calls), a frontend, and anyone asking whether the path is still worth carrying could not tell.Summary
resolve_tool_callsreports whether the calls came out of the text.SessionData.fallback_recovery_reported), the loop rides one harness note on the first such reply's tool result via the existingappend_and_emit_notepath: transcript plusHarnessNoteon the wire, naming the condition and what it means about the endpoint and model pairing.Test Plan
recovered_tool_calls_run_and_are_reported_once_per_session: a scripted endpoint whose only calls are<tool_call>markup in text; both calls run, exactly one note reaches the wire and the transcript. Fails with the emission disabled.resolve_tool_callsunit tests assert the new flag.cargo test --workspacegreen; clippy at zero warnings.Greptile Summary
The change restores the fallback recovery advisory state from retained session history and the compaction archive. One reliability issue remains: if archival fails during compaction, the note-bearing exchange is still removed from the persisted transcript, allowing the advisory to repeat after a restart.
Confidence Score: 4/5
Not merge-safe until compaction preserves the once-per-session advisory state when its archive write fails.
There is exactly one accepted P1 finding, and it is not security-related; the required score is 4.
Files Needing Attention: crates/core/src/agent.rs
What T-Rex did
Prompt To Fix All With AI
Reviews (4): Last reviewed commit: "feat(core): report once per session when..." | Re-trigger Greptile