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]