eemario commented on code in PR #1206:
URL: https://github.com/apache/flink-agents/pull/1206#discussion_r4256093145


##########
docs/content/docs/development/react_agent.md:
##########
@@ -332,3 +344,187 @@ RowTypeInfo myRowTypeInfo =
 
 {{< /tabs >}}
 
+## As a General-Purpose Sub-Agent
+
+A ReActAgent can also run as a sub-agent of a parent agent. Register it as an 
`AGENT` resource of the parent and it runs as an internal sub-agent — an 
isolated, durable agent inside the same job — that keeps its own event loop, 
resources and conversation. The parent reaches it through two paths: the parent 
model delegating to it as a tool, or a parent action submitting a prompt 
directly.
+
+### Register the Child Agent
+
+Register the child under a name, like any other resource. The parent can be 
any agent, including another ReActAgent.
+
+{{< tabs "Register Sub-Agent Child" >}}
+
+{{< tab "Python" >}}
+```python
+reviewer = ReActAgent(
+    chat_model=ResourceDescriptor(
+        clazz=ResourceName.ChatModel.OLLAMA_SETUP,
+        connection="reviewer_connection",
+        model="qwen3:8b",
+    ),
+)
+# The connection is registered on the child itself, keeping it self-contained.
+reviewer.add_resource(
+    "reviewer_connection",
+    ResourceType.CHAT_MODEL_CONNECTION,
+    ResourceDescriptor(clazz=ResourceName.ChatModel.OLLAMA_CONNECTION),
+)
+
+parent = ReActAgent(chat_model=parent_chat_model_descriptor)
+parent.add_resource("reviewer", ResourceType.AGENT, reviewer)
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+ReActAgent reviewer =
+        new ReActAgent(
+                
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_SETUP)
+                        .addInitialArgument("connection", "reviewerConnection")
+                        .addInitialArgument("model", "qwen3:8b")
+                        .build(),
+                null,
+                null);
+// The connection is registered on the child itself, keeping it self-contained.
+reviewer.addResource(
+        "reviewerConnection",
+        ResourceType.CHAT_MODEL_CONNECTION,
+        
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_CONNECTION)
+                .build());
+
+ReActAgent parent = new ReActAgent(parentChatModelDescriptor, null, null);
+parent.addResource("reviewer", ResourceType.AGENT, reviewer);
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+{{< hint warning >}}
+Keep the child self-contained and register the chat model connection it uses 
on the child itself, as above. This is required in Python, where a child 
resolves resources against its own plan only — a connection registered solely 
on the parent is not visible to the child.
+{{< /hint >}}
+
+### Model-Driven Delegation
+
+Declare the child's name under `subagents` of the parent's chat model, and the 
parent model can delegate to it: the child is presented as a `_subagent_<name>` 
tool whose description and input schema come from the child agent.
+
+{{< tabs "Model-Driven Delegation" >}}
+
+{{< tab "Python" >}}
+```python
+parent = ReActAgent(
+    chat_model=ResourceDescriptor(
+        clazz=ResourceName.ChatModel.OLLAMA_SETUP,
+        connection="my_ollama_connection",
+        model="qwen3:8b",
+        subagents=["reviewer"],
+    ),
+)
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+ReActAgent parent =
+        new ReActAgent(
+                
ResourceDescriptor.Builder.newBuilder(ResourceName.ChatModel.OLLAMA_SETUP)
+                        .addInitialArgument("connection", "myOllamaConnection")
+                        .addInitialArgument("model", "qwen3:8b")
+                        .addInitialArgument("subagents", List.of("reviewer"))
+                        .build(),
+                null,
+                null);
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+A ReActAgent child works with the defaults out of the box. The default input 
schema declares a single `input` string field, and the framework routes that 
field to the child as its user message — directly when the child declares no 
prompt, or through the child's `{input}` placeholder when it declares one. The 
child's answer flows back to the parent model as the tool observation, so the 
parent can continue reasoning over it.
+
+### Caller-Driven Delegation
+
+A parent action can submit a prompt to the child directly and await its 
outcome.
+
+{{< tabs "Caller-Driven Delegation" >}}
+
+{{< tab "Python" >}}
+```python
+class DelegatingAgent(Agent):
+    @action(InputEvent.EVENT_TYPE)
+    @staticmethod
+    async def delegate(event: Event, ctx: RunnerContext) -> None:
+        child = ctx.get_resource("reviewer", ResourceType.AGENT)
+        future = await child.submit(ctx, "review the diff")
+        result = await future
+        if result.success:
+            ctx.send_event(OutputEvent(output=result.result[0]))
+        else:
+            ctx.send_event(OutputEvent(output=f"failed: 
{result.error_message}"))
+```
+{{< /tab >}}
+
+{{< tab "Java" >}}
+```java
+public class DelegatingAgent extends Agent {
+
+    @Action(EventType.InputEvent)
+    public static void delegate(Event event, RunnerContext ctx) throws 
Exception {
+        SubagentSetup child =
+                (SubagentSetup) ctx.getResource("reviewer", 
ResourceType.AGENT);
+        SubagentResult result = child.submit(ctx, "review the diff").await();
+        if (result.isSuccess()) {
+            String answer = String.valueOf(((List<?>) 
result.getResult()).get(0));
+            ctx.sendEvent(new OutputEvent(answer));
+        } else {
+            ctx.sendEvent(new OutputEvent("failed: " + 
result.getErrorMessage()));
+        }
+    }
+}
+```
+{{< /tab >}}
+
+{{< /tabs >}}
+
+The awaited value is a `SubagentResult`. On success, its `result` field 
(`getResult()` in Java) is the list of outputs the child emitted — a ReActAgent 
child emits a single final answer, so the first element is what you usually 
want. On failure, `success` is `False` / `isSuccess()` is `false`, and 
`error_message` / `getErrorMessage()` carries the reason.

Review Comment:
   The list wrapper is intentional. `SubagentResult.result` consistently 
represents all outputs emitted by the child, and flattening only the 
single-output case would make the observation shape depend on output 
cardinality. I added a note clarifying that model-driven delegation 
JSON-encodes the same result list as the tool observation, even for a 
ReActAgent that emits one final answer.



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