Skip to content

feat(core): report once per session when tool calls were recovered from reply text - #226

Merged
Max17190 merged 1 commit into
mainfrom
fallback-recovery-receipt
Aug 19, 2026
Merged

feat(core): report once per session when tool calls were recovered from reply text#226
Max17190 merged 1 commit into
mainfrom
fallback-recovery-receipt

Conversation

@Max17190

@Max17190 Max17190 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Why

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. 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_calls reports whether the calls came out of the text.
  • Once per session (SessionData.fallback_recovery_reported), the loop rides one harness note on the first such reply's tool result via the existing append_and_emit_note path: transcript plus HarnessNote on the wire, naming the condition and what it means about the endpoint and model pairing.

Test Plan

  • New 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.
  • Existing resolve_tool_calls unit tests assert the new flag.
  • cargo test --workspace green; 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

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex authored the archive-failure validation harness to exercise the archive feature against failure scenarios.
  • T-Rex ran the archive-success baseline log to confirm the success path behaved as expected according to the baseline.
  • T-Rex reproduced a forced archive-failure scenario to validate the failure path is triggered and captured in logs.

View all artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
crates/core/src/agent.rs:440-441
**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.

Reviews (4): Last reviewed commit: "feat(core): report once per session when..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Comment thread crates/core/src/agent.rs Outdated
Comment thread crates/core/src/agent.rs Outdated
@Max17190
Max17190 force-pushed the fallback-recovery-receipt branch from a3bfefd to 237d74f Compare August 19, 2026 03:26
…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.
@Max17190
Max17190 force-pushed the fallback-recovery-receipt branch from 237d74f to 436f400 Compare August 19, 2026 03:34
Comment thread crates/core/src/agent.rs
Comment on lines +440 to +441
let fallback_recovery_reported = carries_fallback_recovery_note(&messages)
|| carries_fallback_recovery_note(&sessions::load_archive(core, session_id));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Archive-success baseline log

  • 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.

View artifacts

T-Rex 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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Max17190
Max17190 merged commit 332d41f into main Aug 19, 2026
4 checks passed
@Max17190
Max17190 deleted the fallback-recovery-receipt branch August 19, 2026 03:50
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