pltbkd opened a new pull request, #1299:
URL: https://github.com/apache/flink-agents/pull/1299

   Linked issue: #1213
   
   ### Purpose of change
   
   The core goal is to support subgraph execution on the non-coroutine parallel 
engine, with two derived secondary goals: converge chat and sub-agent execution 
onto subgraph, and improve the parallel engine's lifecycle semantics.
   
   #### Why
   
   A caller must release operator ownership for its child graph to progress. 
Sharing the call protocol lets continuations and execution-lock handoff provide 
that capability without separate chat and sub-agent schedulers. Explicit 
suspend/resume keeps shared contexts and listeners aligned with ownership 
changes.
   
   #### Runtime flow
   
   `SubgraphExecution` registers a call and reads its durable terminal slot: 
hit → reuse; miss → bootstrap. Java drives `await`/`finish`; Python yields 
between `tryFinish` polls. Terminal consumption follows retirement of scheduled 
child actions and records only new results. The Python internal-call bridge 
returns null while pending, or a JSON terminal envelope decoded into Python 
`SubagentResult`.
   
   The parallel engine dispatches bootstrap before the caller releases its lock 
to wait. Dependent tasks bypass root FIFO execution/commit ordering. Resume 
restores contexts before preparation callbacks. Checkpoint drain stops new root 
pulls but allows outstanding/dependent work to commit; recovery rebuilds 
dependent work from roots and reuses available child state.
   
   #### Key decisions
   
   - Share dispatch/state/recovery; `SubgraphDefinition` retains routing, 
result, scope and failure policy. Internal calls are not deferred calls.
   - Use action-owned IDs. Only required bootstrap may dispatch early; ordinary 
outputs wait for action completion.
   - Keep one pool: core one, ceiling `max(10, 2 * num-async-threads)`. 
Headroom is not a general deadlock solution; input topology still limits safe 
depth/fan-out.
   - Defer serial/parallel chain convergence and system-wide exception 
classification. Historical state/semantic compatibility is not required.
   
   ### Behavioral Semantics
   
   #### Interaction decisions
   
   | Caller / runtime | No recorded terminal result | Recorded terminal result |
   |---|---|---|
   | Java, JDK 21+ continuation | Yield continuation; graph wait holds no 
worker | Return recorded result; no bootstrap |
   | Java, JDK < 21 parallel, all-Java plan | Dispatch bootstrap, wait on 
caller worker, hand off lock | Return recorded result; no bootstrap |
   | Java, JDK < 21 without parallel engine | Reject before bootstrap | Same 
capability guard rejects |
   | Python, serial or continuation runtime | Poll and yield on 
interpreter/mailbox thread | Return recorded result without polling yield |
   | Python action in parallel engine | Unsupported; non-Java child plans are 
rejected at open | Not an alternate replay path |
   
   On a recovery slot miss, the graph restarts and may reuse child state; 
without durable state it recomputes. Captured internal-child failures wait for 
scheduled siblings to retire. Raw chat-action failures fail the task, rather 
than completing a failed response for the caller. Cross-action ordering and 
exactly-once external effects are not promised.
   
   #### Behavioral contracts
   
   - **C1:** Java chat/internal calls, including nesting, work on the parallel 
engine within available capacity.
   - **C2:** Recorded terminal results skip child execution and are not 
recorded again. Python internal-call results use the same JSON value 
representation on fresh execution and replay.
   - **C3:** Recovery reuses durable state or recomputes deterministic work; 
sink delivery may repeat.
   - **C4:** Graph events and synchronous chat responses stay private to the 
call.
   - **C5a:** Ordinary internal child-action exceptions become 
`SubagentResult.error`.
   - **C5b:** Raw chat-action failures fail the task; failed chat responses 
raise to the caller.
   - **C6:** Chat uses caller resources/memory; internal calls use child 
resources and isolated sensory/short-term stores, not isolated long-term memory.
   - **C7:** Generated IDs are deterministic per action/target and survive 
transfer. Explicit session IDs remain caller-owned.
   - **C8:** Callbacks are operator-serialized, not thread-affine. Preparation 
enters a phase; transfer may retain the same task.
   - **C9:** `onActionFailed` reports processing failure, not phase exit; it 
need not follow a new preparation.
   - **C10:** Record callbacks follow admission/retirement, including no-action 
and resumed records; failure need not emit record finish.
   - **C11:** Drain preserves outstanding work; a matching checkpoint abort 
interrupts its wait, while an older abort is ignored.
   
   #### Failure behavior
   
   Unsupported Java waiting fails before bootstrap; non-Java child plans fail 
parallel-operator open. Duplicate IDs, invalid graph lifecycle operations and 
chat output counts other than one raise protocol errors. Existing provider/tool 
timeout and business-error handling remain; no whole-subgraph timeout is added.
   
   Runtime folding excludes cancellation, JVM errors and faults still marked 
`UncapturableException`. Suspend failure retains the lock and skips 
supplier/resume; resume failure is primary, suppressing any supplier failure. 
Interrupted lock reacquisition preserves interruption and skips resume. Normal 
listener failures may propagate; failure-notification fan-out collects them 
without replacing the original error.
   
   **Technical debt:** generic handlers can translate framework faults into 
business results. Local shell guards are not an end-to-end guarantee against 
persistence or a guarantee of failover. This system-wide classification gap is 
not specific to subgraph/tool calls.
   
   **Usage limits:** await internal and deferred handles in separate batches. 
Normally omit session ID; explicit IDs require caller-guaranteed uniqueness. 
Reuse a generated ID only for the same conversation where continuation is 
supported; **internal sub-agents do not support session continuation**. 
Finite-pool exhaustion remains possible. Drain has no independent deadline: 
interruption of a stalled drain depends on Flink's matching checkpoint-abort 
notification.
   
   ### Tests
   
   Earlier verification, before the trace and JSON-bridge fixes: JDK 11 **217 
passed / 12 skipped**; JDK 21 core **190 passed / 42 skipped**, adjacent suites 
**99 passed / 14 skipped**; Python's six focused runtime suites **65 passed**. 
These suites had no failures or errors, but were not all rerun after the fixes.
   
   Focused verification of the reviewed fix snapshot: JDK 11 **43 passed / 7 
skipped**; Python's seven focused runtime suites **71 passed**; 
real-Pemja/PyFlink internal-subagent e2e **4 passed** (success, failure result, 
nesting and structured payload). Spotless and Ruff pass. These are separate 
runs, not cumulative totals or a full-suite verification of the subsequently 
rewritten branch. Java tests use JDK-gated paths: a skipped parallel test on 
JDK 21 is not parallel-engine coverage.
   
   | Contract | Tests / evidence boundary |
   |---|---|
   | C1 | `SubgraphParallelExecutionTest`: chat, internal, nested, concurrent 
and single-async-thread calls; JDK 21 `ChatCallDeadlockTest` |
   | C2 | `ChatCallTest.terminalResponseReplaysWithoutBootstrap`; 
`InternalSubagentBridgeTest.terminalResultReachesTheBridgeIdenticallyAfterAFailover`:
 successful/failed terminal JSON survives an `ActionStateSerde` round trip, 
with no bootstrap or second journal write (not a Flink checkpoint restart). 
Python polling tests and JDK 11/21 native probes cover conversion; the four e2e 
cases do not restore a checkpoint |
   | C3 | `SubgraphParallelFailoverTest.recoversGraphCallAfterTaskFailure` 
(chat/internal/nested success); 
`SubgraphParallelExecutionTest.restoresAndContinuesAfterCheckpoint` |
   | C4 | `SubgraphParallelExecutionTest` root leakage guard and positive 
control; nested-scope privacy also relies on routing structure |
   | C5a | 
`SubgraphInvocationTest.failedActionOutcomeSurvivesActionStateRoundTrip`; 
`InternalSubagentBridgeTest` failed-terminal journal round trip; real-Pemja 
`test_python_internal_subagent_failure_surfaces_via_result`; no failing-child 
Flink checkpoint replay test |
   | C5b | `ChatCallTest` failed-response replay; no dedicated raw-chat-action 
fault test |
   | C6 | `ActionTaskContextManagerTest`, `SubgraphCallTest`, 
`InternalSubagentMatchTest`; no fresh native cross-language isolation run |
   | C7 | `DeterministicIdAllocatorTest`, 
`ActionTaskContextManagerTest.idCountersRestoreActiveCallAndSurviveContinuationTransfer`,
 `BaseSubagentSetupTest`, `DeferredSubagentSubmitTest` |
   | C8 | `ContinuationActionExecutorTest`, 
`TaskLifecycleListenerNotificationTest`, Python lifecycle/base-subagent suites |
   | C9 | `TaskLifecycleListenerNotificationTest` failure paths and Python 
lifecycle tests; no claim that failure reopens a phase |
   | C10 | `TaskLifecycleListenerNotificationTest` no-action and resumed-record 
cases; coverage is engine-dependent |
   | C11 | `ParallelExecutionCoordinatorTest` dependent/drain cases, 
`ActionExecutionOperatorDrainDeadlockIntegrationTest`, capacity integration 
tests |
   
   Local native Pemja probes on JDK 11 and 21 verified identical fresh/replayed 
Python values for integer-key maps, nested JSON and null outputs, plus 
empty-output and failed-result conversion. JDK 11 parallel trace probes 
verified `_internal_subgraph` event records and their producer/upstream 
metadata under both default event-only and all-record logging, without 
malformed action records. These probes are not repository regression tests; 
parallel trace coverage remains a repository test gap.
   
   Coverage targets ownership handoff, bounded-capacity progress, terminal 
replay, successful-call recovery and native Python bridge conversion. **Not 
verified:** full Java/Python suites on the final tree; native Python 
checkpoint-restart recovery or cross-language memory isolation; every 
partial-journal or failing-sibling checkpoint window; unbounded/deep workload 
liveness; exhaustive serial/parallel callback equivalence. The system-wide 
exception-classification gap and unsupported mixed batches/session continuation 
remain explicit limitations, not successful recovery claims.
   
   <details>
   <summary>Implementation invariants and focused verification details</summary>
   
   - Mutations require mailbox/execution-lock ownership. Preparation reads the 
ordered durable cursor once. Default IDs include the target; explicit session 
IDs gain no implicit resource discriminator.
   - Invocations count logical actions through durable-output dispatch. 
Captured failures drop later sibling outputs but wait for sibling retirement. 
Internal calls remain registered until record cleanup; chat calls are removed 
at terminal consumption.
   - Dependents remain outstanding through commit. Drain returns root permits 
and resubmits them afterward, without parking workers. Executor tracking 
retains only live threads.
   - Parallel subgraph dispatch creates an event trace context for event 
notification and child-action creation, preserving producer/upstream metadata 
rather than reusing the caller's action trace context.
   - Java/Python bindings retain identity/handle state across suspension. The 
internal-call bridge serializes the terminal `SubagentResult` with the 
journal's JSON mapper and preserves its success/result/error fields; Python 
decodes that envelope rather than converting a native Java result list.
   
   Earlier Java runs used JDK-specific `JAVA_HOME`, then `mvn --offline -pl 
runtime -am -Dspotless.skip=true -Dsurefire.failIfNoSpecifiedTests=false 
-Dtest=<selectors> test-compile jar:test-jar test`. An earlier JDK 11 rerun had 
**39 passed** across `SubgraphParallelExecutionTest`, 
`SubgraphParallelFailoverTest`, `TaskLifecycleListenerNotificationTest` and 
`DeterministicIdAllocatorTest`, after rebuilding stale JDK 21 test classes.
   
   The fix-focused Java run used JDK 11 and `mvn --offline -B 
--no-transfer-progress -pl dist/common,dist/flink-2.2 -am -Dspotless.skip=true 
-Dsurefire.failIfNoSpecifiedTests=false 
-Dtest=SubgraphParallelExecutionTest,InternalSubagentBridgeTest,PythonBridgeContractTest,ChatCallTest,SubgraphInvocationTest,TaskLifecycleListenerNotificationTest
 test-compile jar:test-jar package`. Both matching `ChatCallTest` classes are 
included; the operator-level class accounts for the seven JDK-gated skips.
   
   Python focused tests: 
`test_{flink_runner_context,base_subagent,chat_call,internal_subagent,internal_subagent_descriptor,task_lifecycle_bridge,deferred_subagent}.py`,
 via the repository virtual environment's `python -m pytest`, with its 
site-packages on `PYTHONPATH`. The four e2e tests in 
`internal_subagent_test.py` ran with Python 3.11, JDK 11 and PyFlink 2.2.0 
against the reviewed Python source and freshly repackaged distribution JARs; 
`-Dmaven.jar.forceCreation=true` prevented reuse of a stale shaded bridge 
class. Format checks: `mvn --offline -pl runtime -am spotless:check`, Ruff 
`check --no-fix` and `format --check`.
   
   </details>
   
   ### API
   
   Public call shapes, lazy submission and action-local handles remain; 
submitted sub-agent handles must be resolved before action completion. No YAML 
option is added. JDK < 21 Java chat requires the all-Java parallel engine; JDK 
21 still requires `--add-exports=java.base/jdk.internal.vm=ALL-UNNAMED`.
   
   Python internal-call payloads follow JSON value semantics from the first 
result: object keys are strings, and nested containers/nulls are decoded as 
Python JSON values. Arbitrary native Python object types are not preserved. The 
Python-facing Java polling bridge returns a nullable JSON `String`; the 
Java-native internal-call result path is unchanged by this normalization.
   
   Internal inheritance, runtime bridges and lifecycle extension contracts 
change: `InternalSubagentSetup` no longer derives from `DeferredSubagent`, and 
chat-specific runtime managers are removed. There is no historical 
identity/state migration guarantee. Unrelated serial/parallel differences 
remain.
   
   ### Documentation
   
   - [ ] `doc-needed`
   - [ ] `doc-not-needed`
   - [x] `doc-included`
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   - [x] Yes
   - [ ] No
   
   Generated-by: Qoder
   


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