When an LLM response arrives with finish_reason='length' and has_tool_calls
but blank text content (e.g. the model spent its whole output budget on a
tool call whose closing tag was truncated), the runner dropped the tool
calls and then misrouted the blank response into the empty-response retry
branch. Retrying the same prompt cannot recover from output-budget
exhaustion, so every retry hit the same length ceiling and the turn ended
in the generic apology.
The length-recovery branch was gated on 'finish_reason == length and not
is_blank_text(clean)', so a blank-but-truncated turn could never reach it.
- The empty-response retry branch now excludes finish_reason == 'length'
(in addition to 'error').
- The length-recovery branch no longer requires non-blank content, so a
blank-but-truncated turn enters recovery and appends
build_length_recovery_message (which handles a blank tail safely).
Adds a regression test asserting the length-recovery path is taken; it
fails on the unfixed code and passes with the fix.
Fixes#5133
Before this change, idle compaction is triggered every 1 second
if the incoming message stream is idle.
When triggered, it enumerates all session files, loads and parses them,
and then checks their expiration.
This becomes too CPU-intensive, especially on low-power devices like Raspberry Pi.
It's unlikely that you actually need to compact every second over the long time.
This change adds a configurable throttling for idle-compaction.
Default behavior is unchanged.
Reset both the recovered segments and retry budget whenever injected input starts a new logical answer. Cover fatal tool-error boundaries and rename the prompt test module so pytest can collect the full suite.
* test(agent): use python3 in ExecTool workspace scope tests (fixes#5062)
* test(agent): use python on Windows and python3 on POSIX in ExecTool workspace scope tests (fixes#5062)
* test(agent): share Python command fixture
---------
Co-authored-by: chengyongru <chengyongru.ai@gmail.com>