kaxil opened a new pull request, #76790:
URL: https://github.com/apache/airflow/pull/76790

   Under `run_durably`, a failure the model can correct (a SQL statement naming 
a missing table, say) reaches an OpenAI Agents SDK agent as `{"error": ...}` 
from an `AirflowTools` tool and is recorded in the durable journal like any 
other result. On a task retry it replays without the tool running, and each 
tool's retry budget starts every run at zero. A retry therefore gave the model 
`max_retries` new corrections on top of the ones it had already used. Pydantic 
AI runs count them, because `durable/capability.py` raises a replayed 
`ModelRetry` again.
   
   Each `AirflowTools` call now records, next to its result, the count it left 
in its tool's retry budget and when it finished. A replay restores the 
latest-finished count, so across tries the tool gets `max_retries` corrections 
in total.
   
   A run that uses the limit up now fails on the model's account: 
`_RetryLimitReached` joins the SDK's model-at-fault errors, and the retry asks 
the model again from its last request. The Pydantic AI path behaves the same 
way, giving the model one more answer per retry with the rejections it already 
got in its history.
   
   ## Design rationale
   
   **The count is recorded, not read from the `{"error": ...}` text.** 
`ToolFailed`, which `ObjectStorageToolset` raises for a missing file, reaches 
the model as `{"error": ...}` too and never uses up a correction. A tool can 
also succeed with a result that has an `error` key.
   
   **The replay restores a count rather than re-applying each call's outcome.** 
The OpenAI adapter has no model-turn id, so a success resets the count, and 
parallel calls reach the replay in the order they started rather than the order 
they finished. A slow success next to a fast failure would leave a different 
count on replay than it did live. A call that runs live always outranks 
anything an earlier try recorded.
   
   **Only the tool the adapter called records a count.** A custom `AirflowTool` 
that wraps a toolset tool would otherwise inherit the inner call's count, and 
its replay would run the wrapper with empty arguments. Likewise, only an 
`AirflowTools` step treats a used-up limit as the model's fault: an agent 
called through `as_tool` that runs out of corrections fails that call, and the 
outer model's answer still replays.
   
   **Journals written by 1.1.0 still replay.** An `AirflowTools` result is 
always text, so the new `{"output", "retry"}` form is told apart by being a 
dict, and older plain-text entries replay as before, without a count.
   
   ## Known issues
   
   - A live call that counts before a replayed call of the same turn restores 
is ordered by wall-clock time. That holds while workers' clocks differ by less 
than the time between tries, which the Execution API's token checks already 
require.
   - Strands durable runs have the same gap: they resume from a checkpoint with 
a fresh budget. Fixing that needs the count saved with the checkpoint, so it is 
a separate change.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to