From 49ff63b3c8bab29cf71d34cc1d41be91c1bfde6f Mon Sep 17 00:00:00 2001 From: Damian Momot Date: Wed, 27 May 2026 06:01:25 -0700 Subject: [PATCH] docs: clarify LlmAgent composition for workflow agents Document on ParallelAgent, SequentialAgent, and LoopAgent that they do not transfer control back to an LlmAgent orchestrator after they complete, and that placing the workflow agent and any follow-up agent as siblings inside a SequentialAgent (used as the root or transferred-to agent) is the recommended pattern. This matches the behavior of adk-python v1.x, which is the line that Java ADK currently tracks. A more robust orchestration will be available once Java reaches the Graph Workflows milestone aligned with adk-python v2.0. PiperOrigin-RevId: 922080205 --- .../java/com/google/adk/agents/LoopAgent.java | 24 ++++++++++++++ .../com/google/adk/agents/ParallelAgent.java | 32 +++++++++++++++++++ .../google/adk/agents/SequentialAgent.java | 27 +++++++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/google/adk/agents/LoopAgent.java b/core/src/main/java/com/google/adk/agents/LoopAgent.java index 743d569b9..c12387231 100644 --- a/core/src/main/java/com/google/adk/agents/LoopAgent.java +++ b/core/src/main/java/com/google/adk/agents/LoopAgent.java @@ -30,6 +30,30 @@ * *

The loop continues until a sub-agent escalates, or until the maximum number of iterations is * reached (if specified). + * + *

Composition with {@link LlmAgent}s: a {@code LoopAgent} does not transfer control back + * to a parent {@link LlmAgent}. To react to loop results, place the {@code LoopAgent} and the + * follow-up {@link LlmAgent} as siblings inside a {@link SequentialAgent}. Loop sub-agents publish + * via {@code outputKey} and the follow-up reads via {@code {key}} placeholders in its instruction: + * + *

{@code
+ * var refiner =
+ *     LlmAgent.builder()
+ *         .name("refiner")
+ *         .model("gemini-flash-latest")
+ *         .instruction("Refine: {draft?}")
+ *         .outputKey("draft")
+ *         .build();
+ * var publisher =
+ *     LlmAgent.builder()
+ *         .name("publisher")
+ *         .model("gemini-flash-latest")
+ *         .instruction("Publish: {draft}")
+ *         .build();
+ * var loop =
+ *     LoopAgent.builder().name("loop").subAgents(refiner).maxIterations(3).build();
+ * var root = SequentialAgent.builder().name("root").subAgents(loop, publisher).build();
+ * }
*/ public class LoopAgent extends BaseAgent { private static final Logger logger = LoggerFactory.getLogger(LoopAgent.class); diff --git a/core/src/main/java/com/google/adk/agents/ParallelAgent.java b/core/src/main/java/com/google/adk/agents/ParallelAgent.java index 1e98dbf50..e1382a317 100644 --- a/core/src/main/java/com/google/adk/agents/ParallelAgent.java +++ b/core/src/main/java/com/google/adk/agents/ParallelAgent.java @@ -34,6 +34,38 @@ *

This approach is beneficial for scenarios requiring multiple perspectives or attempts on a * single task, such as running different algorithms simultaneously or generating multiple responses * for review by a subsequent evaluation agent. + * + *

Composition with {@link LlmAgent}s: a {@code ParallelAgent} does not transfer control + * back to a parent {@link LlmAgent}. To follow a fan-out with an aggregation step, wrap both in a + * {@link SequentialAgent} (used as the root or transferred-to agent). Each parallel sub-agent + * publishes via {@code outputKey} and the aggregator reads via {@code {key}} placeholders in its + * instruction: + * + *

{@code
+ * var contacts =
+ *     LlmAgent.builder()
+ *         .name("contacts")
+ *         .model("gemini-flash-latest")
+ *         .instruction("List contacts.")
+ *         .outputKey("contacts")
+ *         .build();
+ * var schedule =
+ *     LlmAgent.builder()
+ *         .name("schedule")
+ *         .model("gemini-flash-latest")
+ *         .instruction("List schedule.")
+ *         .outputKey("schedule")
+ *         .build();
+ * var writer =
+ *     LlmAgent.builder()
+ *         .name("writer")
+ *         .model("gemini-flash-latest")
+ *         .instruction("Write: contacts={contacts}, schedule={schedule}")
+ *         .build();
+ * var gather =
+ *     ParallelAgent.builder().name("gather").subAgents(contacts, schedule).build();
+ * var root = SequentialAgent.builder().name("root").subAgents(gather, writer).build();
+ * }
*/ public class ParallelAgent extends BaseAgent { diff --git a/core/src/main/java/com/google/adk/agents/SequentialAgent.java b/core/src/main/java/com/google/adk/agents/SequentialAgent.java index b0b45a0ec..95ca50d16 100644 --- a/core/src/main/java/com/google/adk/agents/SequentialAgent.java +++ b/core/src/main/java/com/google/adk/agents/SequentialAgent.java @@ -22,7 +22,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** An agent that runs its sub-agents sequentially. */ +/** + * An agent that runs its sub-agents sequentially. + * + *

Composition with {@link LlmAgent}s: a {@code SequentialAgent} does not transfer control + * back to a parent {@link LlmAgent}. Use it as the root or transferred-to agent and place any + * follow-up {@link LlmAgent} as the next sibling. Upstream publishes via {@code outputKey} and + * downstream reads via {@code {key}} placeholders in its instruction: + * + *

{@code
+ * var draft =
+ *     LlmAgent.builder()
+ *         .name("draft")
+ *         .model("gemini-flash-latest")
+ *         .instruction("Draft a summary.")
+ *         .outputKey("draft")
+ *         .build();
+ * var reviewer =
+ *     LlmAgent.builder()
+ *         .name("reviewer")
+ *         .model("gemini-flash-latest")
+ *         .instruction("Polish the draft: {draft}")
+ *         .build();
+ * var pipeline =
+ *     SequentialAgent.builder().name("pipeline").subAgents(draft, reviewer).build();
+ * }
+ */ public class SequentialAgent extends BaseAgent { private static final Logger logger = LoggerFactory.getLogger(SequentialAgent.class);