eemario opened a new pull request, #1206: URL: https://github.com/apache/flink-agents/pull/1206
<!-- * Thank you very much for contributing to Flink Agents. * Please add the relevant components in the PR title. E.g., [api], [runtime], [java], [python], [hotfix], etc. --> <!-- Please link the PR to the relevant issue(s). Hotfix doesn't need this. --> Linked issue: #1139 ### Purpose of change A ReActAgent instance can now be registered directly as an `AGENT` resource and runs as an in-process internal sub-agent. The caller resolves it with `ctx.get_resource(name, ResourceType.AGENT)`, calls `submit(ctx, input)`, and awaits a Result holding the child's accumulated output; alternatively the parent's chat model calls it as a `_subagent_` tool, driven by the agent's declared description and input schema. Java and Python are at parity. The preceding internal sub-agent work runs an arbitrary registered Agent as a child but carries no metadata a parent model could use to delegate to it, its Python caller path had not been exercised end to end, and a child running its own reasoning/tool loop could not read back its first round inside the isolated memory view. This change closes those three gaps. #### Runtime flow 1. **Input normalization.** ReActAgent's start action accepts a String (bound to the declared input key), a Map/dict whose single key equals the input key (the value becomes the user message), or the existing Row/POJO forms. 2. **Metadata declaration.** ReActAgent implements `SubagentMetadataProvider` (Java interface / Python mixin) exposing a `(description, input_schema, input key)` triple with overridable defaults. The AGENT-resource compilation point in AgentPlan serializes the triple into the child's InternalSubagentSetup, symmetrically in both languages. 3. **Invocation.** Caller-driven `submit`/`await` and model-driven `_subagent_<name>` tool calls both funnel into the existing internal sub-agent envelope; the child's framework-driven loop (chat_model_action / tool_call_action) runs inside the child scope with isolated memory, and the child's OutputEvents accumulate into the caller's Result. 4. **Isolated memory view per call.** The child's memory view is created once per sub-agent call and shared by every action task of that call; the isolated store keeps its own writes for the call's lifetime (they never reach the caller's persistent state), so a multi-round reasoning/tool loop reads back its own earlier rounds. #### Key decisions - Metadata is read through the `SubagentMetadataProvider` capability interface rather than `instanceof ReActAgent` — any agent type can declare sub-agent metadata, and neither the plan nor the runtime layer depends on the concrete ReActAgent class. - Overridable defaults instead of required fields: caller-driven use needs no description or input schema, while model-driven delegation should override them; defaults do not soften any fail-fast validation. - The per-call memory view (replacing the per-action-task view) keeps the isolation contract: child writes die with the call and are excluded from the caller's persistent state, which is what makes sharing them across the loop safe. ### Behavioral Semantics #### Interaction decisions | | caller-driven | model-driven | |---|---|---| | String input | bound to the input key, becomes the user message | not applicable (the model supplies Map arguments) | | Map input, single key = input key | value becomes the user message | the shape the delegation path constructs | | Child registers its own tools | multi-round loop stays inside the child scope | same, tool schemas offered to the child's model | | No metadata declared | works with defaults | `_subagent_` tool carries empty schema; callers should override | #### Behavioral contracts - A caller-driven ReActAgent child runs its full reasoning/tool loop inside the child scope, and the caller's Result holds the child's final answer text. - A child carrying its own tools completes a multi-round loop: round N's tool results are readable in round N+1, the tool schemas offered to the child's model are exactly the child's registered tools, and tool results enter the conversation as TOOL-role messages. - A model-driven delegation exposes the declared description and input schema as the `_subagent_` tool schema, and the child receives the model's arguments as its input. - A failing child is reported through `Result.error` and the job proceeds. #### Failure behavior - A Map input with no prompt whose keys do not match the input-key form raises, as before — other Map shapes remain explicitly unsupported. - A child action failure surfaces as `SubagentResult.error` in the caller's Result (existing semantics, now covered end to end). - The child's memory view is per call and is not persisted across checkpoints; recovering an in-flight call replays the parent action per the existing recovery semantics, and persisting child-scope memory remains an open design question. ### Tests | Contract | Tests | |---|---| | Caller-driven child accumulates its loop output | `ReActAgentSubagentTest#callerDrivenCallRunsTheReActLoopInsideTheChildScope`; `test_caller_driven_react_subagent` | | Model-driven delegation routes to the child and closes the loop | `ReActAgentSubagentTest#modelDrivenDelegationRoutesToTheBareChildAndClosesTheLoop`; `test_model_driven_react_subagent_delegation` | | Child tool loop reads its own writes; schemas and TOOL messages as declared | `ReActAgentSubagentTest#toolCallLoopRunsInsideTheChildScope`; `test_child_tool_call_loop_runs_inside_the_child_scope` | | Existing internal sub-agent behavior unchanged (echo/failure/nested/relay) | `InternalSubagentCallTest` (4 tests); Python `internal_subagent_test.py` (3 tests) | Coverage by risk: the memory-lifecycle change is pinned end to end in both languages by the tool-loop tests, which fail without it; the metadata plumbing is exercised by the model-driven tests reading real tool schemas. **Not verified:** cross-language invocation (Java caller → Python-compiled child and vice versa — a separate tracked task); recovery of an in-flight child's memory across checkpoints (open design question, stated above); model-side schema violations (only the happy delegation path is e2e'd). ### API - New public surface: `SubagentMetadataProvider` (Java interface, Python mixin); ReActAgent constructors gain optional `description` / `input_schema` parameters in both languages. - Behavioral addition: the ReActAgent start action now accepts String and single-key-Map inputs where it previously raised for both; existing Row/POJO/prompt behavior is unchanged. - Internal sub-agent runtime change: the child memory view moved from per-action-task to per-call and the isolated store no longer discards its own writes on persist. Single-round children (all previously existing tests) cannot observe the difference — their writes die with the call either way. The Python caller-path config lookup bug fix changes no root-level behavior. - Unchanged: external SubagentSetup delegation, YAML-descriptor agents, and all previously existing internal sub-agent behavior. ### Documentation <!-- Do not remove this section. Check the proper box only. --> - [ ] `doc-needed` <!-- Your PR changes impact docs --> - [ ] `doc-not-needed` <!-- Your PR changes do not impact docs --> - [x] `doc-included` <!-- Your PR already contains the necessary documentation updates --> ### Was this patch authored or co-authored using generative AI tooling? <!-- Do not remove this section. Check the proper box only. --> - [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]
