Repository navigation
Conversation
… internal sub-agent Registering an Agent instance -- rather than a SubagentSetup -- as an AGENT resource now compiles that child Agent into an in-process internal sub-agent. AgentPlan detects `value instanceof Agent`, compiles the child plan once through InternalSubagentCompilationHelper (child plans are reused across names and cycles are rejected) and serves it via InternalSubagentProvider; the runtime materializes it as InternalSubagentSetup. The caller drives the child directly with submit(ctx, prompt).await(): an InternalSubagentCallEvent is sent, the operator dispatches the child's actions in-process -- on a JDK 21 continuation for the Java caller path and through the await-only resolve for the Python path -- and the child's OutputEvents accumulate into the caller's Result. The child context is scope-aware, so a nested child resolves against the child plan and can itself register a grandchild. Calls are durable and failover-reproducible. Generated-by: Qoder Co-Authored-By: Qoder <noreply@qoder.com> AI-Contributed/Feature: 1959/1959 AI-Contributed/UT: 788/788
…gents Exercise the whole operator round-trip for a child Agent registered directly as an AGENT resource: the Python-compiled child plan is materialized into a Java InternalSubagentSetup, the call event is sent on the mailbox thread, the mailbox is released while the caller awaits, and the child's OutputEvents accumulate into the caller's Result. Covers the happy path, a failing child surfaced through the Result, and a nested grandchild resolved against the child plan. submit is called without identities, so the framework assigns the (session_id, call_id) pair deterministically from the executing action task and replays it identically after a failover. The Python caller path needs no JDK 21: a coroutine releases the mailbox by yielding at await. Generated-by: Qoder Co-Authored-By: Qoder <noreply@qoder.com> AI-Contributed/Feature: 0/0 AI-Contributed/UT: 183/183
…ask lifecycle A child Agent registered as an AGENT resource compiles to a PythonSerializableResourceProvider that the runtime treats as Java-owned, so registerSubagentSetups left pythonSetupRegistered false and never added the PythonTaskLifecycleListener. The caller-facing Python handle, built lazily during the action body, then never observed onActionPrepared, and a no-id submit(ctx, prompt) failed with "No prepared action task to assign sub-agent ids from". Register the bridge listener whenever the root plan declares a Python-compiled sub-agent (hasPythonCompiledSubagent), so the lifecycle also reaches child-scope actions. On the Python side, FlinkRunnerContext extracts the prepared namespace eagerly in notify_action_prepared/transferred (plain values, no pemja reference retained) and, in get_resource, wires a lazily materialized sub-agent handle as a listener and replays that namespace via the new BaseSubagentSetup.adopt_prepared_namespace. A no-id submit then mints deterministic (session_id, call_id) identities, replayed identically after a failover. Externally registered setups, already wired eagerly, are unaffected. Generated-by: Qoder Co-Authored-By: Qoder <noreply@qoder.com> AI-Contributed/Feature: 87/87 AI-Contributed/UT: 0/0
…che#1140 Generated-by: Codex CLI 0.154.0 (GPT-6)
… batches Schedule grouped internal calls on the mailbox after reserving durable slots. Persist each completed outcome before admitting queued work and reconstruct child scopes from the parent on recovery. Generated-by: Codex CLI 0.154.0 (GPT-6) Co-authored-by: Codex <noreply@openai.com>
Replace Flink 2.x legacy source and sink fixtures with the shared sequence source and async collector so Flink 1.20 can discover the JRE-gated tests. Keep the checkpoint-triggered restart and ordered batch assertions. Generated-by: Codex CLI 0.154.0 (GPT-6)
4dee980 to
3f06f34
Compare
|
Hi @dangzitou , #1138 has been merged, and some prerequisites this PR bases on might have been changed. Could you please rebase your PR on the latest code of main branch and resolve the conflicts? |
|
Hi @yunfengzhou-hub, thanks for the heads-up. After rebasing, I see that #1138 landed with a different internal-call model, and #1220 then moved internal sub-agent execution onto Instead of one ~4.9k-line PR, I'd like to split #1140 into smaller PRs:
Two questions before I start:
I'll convert this PR to a draft and keep it for reference until the first split PR is up. |
Hi @dangzitou, Flink Agents already has configurations that present an upper bound to the concurrency of sub-agent invocations, like num-async-threads or async.batch.parallelism. The "bounded parallelism" requirement of #1140 can be satisfied by these existing limits for now, without introducing a new upper bound — which also means neither of the two questions (scope and granularity) needs to be answered at this point. What do you think? And if we could set aside the code related to this upper bound, will this PR still be so big that it needs to be divided into multiple ones? |
|
My original goal in [#1348](#1348) was to move This overlaps with the Java batching work here. My preference is to handle execution differences within the Future implementations and share the batch scheduler and limits, without introducing subagent-specific branches in the scheduler. #1348 is Java-only for now; Python alignment will follow after it merges. Your review would be very welcome, especially around subagent batching and recovery. |
Linked issue: #1140
This draft is stacked on #1138 and includes its internal sub-agent framework prerequisite. The batch-specific change is 9ad3a7b1.
Purpose of change
Parents can combine internal sub-agent handles into a durable batch, run a bounded fan-out across the same or different children, and receive ordered per-call results. This removes serial resolution and shared-worker starvation from the grouped invocation path.
Runtime flow
submitallocates deterministic identities without starting internal children. Group resolution validates admission, prepares deferred handles, and gathers their durable futures. The runtime reserves every outstanding slot, starts a bounded window on the mailbox, suspends the parent, persists each terminal result before admitting its replacement, and returns results in handle order. Recovery replays the parent first to reconstruct child scopes and reuses child action journals.Key decisions
PENDINGslots rather than a second journal format.Behavioral Semantics
Interaction decisions
Behavioral contracts
subagent.parallelisminvocations at once and work with one shared async worker, including nested calls.Failure behavior
Admission errors raise. Java internal calls require JDK 21+ continuation support; Python callers must be async. Child action failures become
SubagentResult.errorafter quiescence and their summaries survive replay. Bootstrap, polling, serialization, and journal errors propagate rather than becoming child results. Unfinished external effects may replay under the existing durability model, so callers still need idempotency or a reconciler.Tests
InternalSubagentBatchTest, Pythontest_internal_subagent_batch, Java/Python batch E2EInternalSubagentRecoveryTest, Java checkpoint/restart E2E, Python partial-result replay testCoverage targets scheduling, worker starvation, durable slot indexing, checkpoint queue rotation, nested event replay, and the interval between child failure and parent result persistence.
Not verified: batch recovery against a production Kafka/Fluss journal, process/TaskManager loss, or rescaling across TaskManagers. The Python MiniCluster restart verification is local/manual and is not a committed CI regression.
Implementation invariants and verification evidence
All slots are reserved before bootstrap. Admission counts submitted slots until their completion callback persists them. Recovery drops checkpointed child tasks and regenerates their graph from the waiting parent, avoiding dispatch before transient scopes exist. Completed actions replay same-call forwarded events and outputs, but not nested bootstrap commands. Failed actions persist their failure summary and release discarded event counts. Nested AgentPlan JSON now closes its root object explicitly so resource-provider type markers remain in the correct object.
Core verification uses JDK 21, Python 3.12 and Flink 2.3. Distribution artifacts are rebuilt and copied to the Python library directories as in
tools/build.sh.mvn -B --no-transfer-progress -pl runtime -am -Dspotless.skip=true test: 2,190 tests, 39 skipped, no failures../tools/ut.sh -p: 1,670 passed, 14 skipped; final core regression: 1,289 passed, 14 skipped.InternalSubagentBatchE2ETest: both cases pass locally on Flink 1.20, 2.0 and 2.3 with JDK 21 and in CI on Flink 2.0/2.1/2.2; older Flink usesjava.util/java.niomodule opens. Fixtures use shared source/collector APIs. Java 17 + Flink 1.20 discovers both JRE-gated cases and passes 5 existing integration/serialization cases on three consecutive runs.internal_subagent_test.py: single call, failure, nested call and bounded 12-call mixed-target batch.[1,2,2,1], and ordered results/failures survive. The journal uses production ActionState serialization in the same JVM; a test-only close hook releases parked Python work during task shutdown. This fixture is uncommitted../tools/lint.sh -cwith JDK 17;./tools/check-license.sh;tools/check-agents-md.py.API
No invocation signatures change. Combining deferred handles now resolves one durable batch; sequential waits remain sequential. New Java/Python/YAML configuration keys are
subagent.parallelism(16) andsubagent.max-batch-size(1024), both positive. These bound call counts per group, not payload bytes or total job concurrency. Single internal waits use the same scheduler. External async submission remains unchanged.Documentation
doc-neededdoc-not-neededdoc-includedWas this patch authored or co-authored using generative AI tooling?
Generated-by: Codex CLI 0.154.0 (GPT-6)