sangkyoonnam opened a new pull request, #1240: URL: https://github.com/apache/flink-agents/pull/1240
Linked issue: #1190 ### Purpose of change #### User-visible outcome The built-in Python chat action now gives each chat model attempt the durable id `"chat"`. For attempts persisted with this identity, recovery under the same action call sequence reuses the saved outcome without another model invocation caused by a process-dependent argument digest. This fixes the built-in path; canonicalizing `_compute_args_digest` for calls without an explicit id remains a follow-up. #### Runtime flow `chat()` in `chat_model_action.py` passes `durable_id="chat"` to `durable_execute` or `durable_execute_async` on every attempt of its retry loop. The runtime wraps the callable with that id, and `durable_identity_for_call` returns it without computing the argument digest. Attempts stay distinct by their position in the action's durable call sequence. #### Key decisions - `"chat"` matches the Java `ChatModelAction`, which keys a plain chat model's calls with the same id (`ResolvedModelRoute.durableChatCallId`). An id containing the model resource name and retry attempt is unnecessary for this deterministic built-in flow: the call index separates attempts, and matching Java keeps one identity scheme for plain chat calls across languages. - The change is limited to the built-in chat path. User calls without a `durable_id` keep the current digest. ### Behavioral Semantics #### Interaction decisions | Mode | Attempt | Durable id | |---|---|---| | `durable_execute` (sync) | first and every retry | `"chat"` | | `durable_execute_async` | first and every retry | `"chat"` | #### Behavioral contracts 1. For a built-in chat attempt whose outcome was persisted under `"chat"`, recovery reuses the saved outcome without invoking the model again, provided the action follows the same deterministic call sequence. Process-dependent argument digests do not affect this match. 2. Retried attempts remain separate: recovery consumes their saved outcomes in durable call order. #### Failure behavior Unchanged. Ordinary provider failures are persisted as invocation outcomes and follow the action's retry policy. Invalid or incomplete responses and structured-output parsing failures also follow that policy; exhaustion emits a failed ChatResponseEvent. Resource lookup failures emit a failed response. Cancellation and errors from journal writes or recovery propagate; event-dispatch errors also escape the action. In the existing completion-only runtime path, outcome serialization failures are logged and leave the attempt unrecorded. ### Tests | Contract | Coverage | |---|---| | 1 | Indirect: `test_every_attempt_uses_the_chat_durable_id[False/True]` checks explicit-id forwarding. The production explicit-id path bypasses the digest; this patch adds no cross-process recovery test. | | 2 | The new test checks `"chat"` on both the failed first attempt and its retry, and that the retry produces the successful response. It does not restore and replay those two saved outcomes. | The fixture now consumes the reserved `durable_id` keyword before invoking the callable, matching the runtime. The new test fails on main (2 failed); the plan, runtime and api Python tests pass (1642 passed, 15 skipped), and `tools/lint.sh -c` is clean. Not verified by this patch's regression coverage: - Cross-process recovery with a real Java context and different `PYTHONHASHSEED` values. - Replay through the built-in action of successive `"chat"` outcomes, including structured-output retries and tool follow-up actions. - Recovery across the old digest identity to `"chat"`, including suffix invalidation and completed-action reuse. <details> <summary>Implementation invariants and supporting coverage</summary> Both durable execution modes pass `"chat"` on every attempt. `durable_identity_for_call` returns an explicit id before computing the function id or argument digest, so messages and `prompt_args` do not contribute to these calls' journal identities. `test_with_durable_id_overrides_derived_function_id` covers callable wrapping and derived function-id override; it does not test the `durable_identity_for_call` digest bypass. The existing `test_chat_failure_outcome_replays_without_provider_call` covers failure-outcome replay through `_invoke_chat` and a fake Java context, without this patch's explicit chat id. Java's `testMultipleCallResultRecovery` and `testMatchNextOrClearSubsequentCallResultMismatch` cover ordered slots and mismatch invalidation, using distinct ids. </details> ### API No public API signature change. Persisted chat attempts inside unfinished actions retain their old digest identities. When recovery reaches the first such attempt, `"chat"` does not match: the runtime discards that saved outcome and every later outcome in the action. Calls subsequently reached execute again under the normal retry policy, potentially repeating multiple model attempts. Actions whose completion was durably persisted remain unaffected: recovery skips their bodies and replays saved memory updates and output events. Calls without an explicit durable id retain the current digest behavior. This intentional identity change is acceptable under the project's 0.x compatibility policy. ### Documentation - [ ] `doc-needed` - [x] `doc-not-needed` - [ ] `doc-included` ### Was this patch authored or co-authored using generative AI tooling? - [x] Yes - [ ] No Generated-by: Claude Code 2.1.296 (Claude Opus 5.5) -- 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]
