-
Notifications
You must be signed in to change notification settings - Fork 648
chore: upgrade google.golang.org/adk to v2.0.0 #2152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yanivmn
wants to merge
2
commits into
kagent-dev:main
Choose a base branch
from
yanivmn:chore/upgrade-go-adk-v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,287 @@ | ||
| # EP-XXXX: Support for ADK workflow agent types (Sequential, Parallel, Loop) | ||
|
|
||
| * Issue: [#XXXX](https://github.com/kagent-dev/kagent/issues/XXXX) | ||
| * Status: `provisional` | ||
|
Comment on lines
+1
to
+4
|
||
|
|
||
| ## Background | ||
|
|
||
| A kagent declarative agent is described by `DeclarativeAgentSpec` and compiled by | ||
| the Go runtime into a single Google ADK agent. Today that compilation is | ||
| hard-coded: [`CreateGoogleADKAgentWithSubagentSessionIDs`](../go/adk/pkg/agent/agent.go) | ||
| always calls `llmagent.New(...)`, so **every** declarative agent is an | ||
| `LlmAgent` — a single model reasoning over a tool set. | ||
|
|
||
| The Google ADK, however, ships several first-class agent types. As of the v2.0.0 | ||
| upgrade the following are available under `google.golang.org/adk/v2/agent`: | ||
|
|
||
| | ADK type | Package | Behavior | | ||
| |----------|---------|----------| | ||
| | `LlmAgent` | `agent/llmagent` | A model reasons over tools/instructions. **The only type kagent uses today.** | | ||
| | `SequentialAgent` | `agent/workflowagents/sequentialagent` | Runs sub-agents in order; state flows forward between them. | | ||
| | `ParallelAgent` | `agent/workflowagents/parallelagent` | Runs sub-agents concurrently in isolation; useful for multi-perspective fan-out. | | ||
| | `LoopAgent` | `agent/workflowagents/loopagent` | Runs sub-agents repeatedly until `MaxIterations` or a sub-agent escalates. | | ||
|
|
||
| The three workflow types (`Sequential`, `Parallel`, `Loop`) are *deterministic | ||
| orchestrators*. They do not call an LLM to decide control flow — they run their | ||
| `SubAgents()` according to a fixed policy and pass data between them through | ||
| session state (each `LlmAgent` can publish its result under an `OutputKey`, which | ||
| downstream agents read from state). | ||
|
|
||
| kagent already has **one** composition mechanism: an agent can reference other | ||
| agents as tools (`ToolProviderType_Agent` / `spec.declarative.tools[].agent` and | ||
| the `RemoteAgents` A2A wiring). That is *LLM-driven delegation* — the calling | ||
| model decides, at runtime and non-deterministically, whether and when to invoke a | ||
| sub-agent. It cannot express "always run A then B", "run A, B, and C at once and | ||
| merge", or "keep refining until the reviewer approves". | ||
|
|
||
| This EP proposes exposing the ADK workflow agent types through the declarative | ||
| `Agent` CRD so users can build deterministic multi-agent workflows natively. | ||
|
|
||
| ## Motivation | ||
|
|
||
| Multi-agent orchestration patterns are a core reason teams adopt an agent | ||
| framework, and the ADK already implements them robustly. Kagent leaves them | ||
| unreachable because the CRD → runtime path only knows how to emit an `LlmAgent`. | ||
|
|
||
| Concrete use-cases that are impossible or unreliable today: | ||
|
|
||
| - **Pipelines (Sequential):** "summarize → translate → fact-check", where each | ||
| stage must run, in order, exactly once. Agent-as-tool cannot guarantee the | ||
| model won't skip or reorder steps. | ||
| - **Fan-out / ensemble (Parallel):** run N specialist agents on the same input | ||
| and merge — e.g. three reviewers with different lenses, then a synthesizer. | ||
| - **Iterative refinement (Loop):** "generate → critique → revise" until a | ||
| reviewer agent signals completion or a bounded iteration cap is hit. | ||
|
|
||
| These are exactly the patterns the ADK's `SequentialAgent`, `ParallelAgent`, and | ||
| `LoopAgent` exist to serve. The gap is purely in kagent's declarative surface and | ||
| its runtime factory, not in the underlying ADK. | ||
|
|
||
| ### Goals | ||
|
|
||
| - Extend `DeclarativeAgentSpec` with an agent **kind** discriminator so an agent | ||
| can be declared as `LlmAgent` (default), `Sequential`, `Parallel`, or `Loop`. | ||
| - Allow a workflow agent to declare its ordered/unordered set of **sub-agents**, | ||
| referenced by name (other `Agent` resources in the same namespace). | ||
| - Compile the new kinds in the Go runtime via a factory that dispatches on kind | ||
| to the corresponding ADK constructor, attaching compiled sub-agents as | ||
| `SubAgents`. | ||
| - Support `maxIterations` for the `Loop` kind. | ||
| - Preserve full backward compatibility: any existing spec (no kind set) continues | ||
| to compile to an `LlmAgent` with identical behavior. | ||
|
|
||
| ### Non-Goals | ||
|
|
||
| - **BYO agents.** Users who need arbitrary orchestration in code already have the | ||
| `BYO` agent type (`spec.byo`), which runs a user-provided container. This EP is | ||
| about the *declarative* path only. | ||
| - **Replacing agent-as-tool / A2A delegation.** LLM-driven delegation remains and | ||
| is complementary; workflow agents are for deterministic control flow. | ||
| - **Remote agents as a declarative kind.** ADK's `remoteagent` overlaps with the | ||
| existing `RemoteAgents` A2A wiring and is out of scope here. | ||
| - **A visual workflow builder in the UI.** The UI will need to render/edit the new | ||
| fields, but graph-editing UX is a separate effort. | ||
| - **Python runtime parity in the first cut.** Initial implementation targets the | ||
| Go runtime (`runtime: go`); see Open Questions. | ||
|
|
||
| ## Implementation Details | ||
|
|
||
| ### API changes (`v1alpha2`) | ||
|
|
||
| Add a `kind` discriminator and a `subAgents` section to `DeclarativeAgentSpec`. | ||
| The model/instruction/tools fields remain and apply only when `kind: LlmAgent` | ||
| (the default). For workflow kinds those fields are unused and should be rejected | ||
| by a CEL validation rule. | ||
|
|
||
| ```go | ||
| // +kubebuilder:validation:Enum=LlmAgent;Sequential;Parallel;Loop | ||
| type DeclarativeAgentKind string | ||
|
|
||
| const ( | ||
| DeclarativeAgentKind_Llm DeclarativeAgentKind = "LlmAgent" | ||
| DeclarativeAgentKind_Sequential DeclarativeAgentKind = "Sequential" | ||
| DeclarativeAgentKind_Parallel DeclarativeAgentKind = "Parallel" | ||
| DeclarativeAgentKind_Loop DeclarativeAgentKind = "Loop" | ||
| ) | ||
|
|
||
| type DeclarativeAgentSpec struct { | ||
| // Kind selects the ADK agent type. Defaults to LlmAgent, which preserves | ||
| // the single-model behavior of all existing agents. | ||
| // +optional | ||
| // +kubebuilder:default=LlmAgent | ||
| Kind DeclarativeAgentKind `json:"kind,omitempty"` | ||
|
|
||
| // SubAgents lists the child agents composed by a workflow kind | ||
| // (Sequential/Parallel/Loop). Ignored for LlmAgent. | ||
| // Order is significant for Sequential and Loop. | ||
| // +optional | ||
| // +kubebuilder:validation:MaxItems=20 | ||
| SubAgents []SubAgentRef `json:"subAgents,omitempty"` | ||
|
|
||
| // Workflow holds kind-specific settings (e.g. Loop iteration bounds). | ||
| // +optional | ||
| Workflow *WorkflowConfig `json:"workflow,omitempty"` | ||
|
|
||
| // ... existing fields (SystemMessage, ModelConfig, Tools, ...) unchanged; | ||
| // they apply only when Kind == LlmAgent. | ||
| } | ||
|
|
||
| // SubAgentRef references another Agent resource to compose as a child. | ||
| type SubAgentRef struct { | ||
| // Name of an Agent resource in the same namespace. | ||
| Name string `json:"name"` | ||
| // OutputKey, when set, publishes this sub-agent's final output into | ||
| // session state under this key so later sub-agents can read it. | ||
| // +optional | ||
| OutputKey string `json:"outputKey,omitempty"` | ||
| } | ||
|
|
||
| type WorkflowConfig struct { | ||
| // MaxIterations bounds a Loop agent. 0 means run until a sub-agent | ||
| // escalates (maps to ADK loopagent MaxIterations). | ||
| // +optional | ||
| MaxIterations *uint `json:"maxIterations,omitempty"` | ||
| } | ||
| ``` | ||
|
|
||
| CEL validation to add on `DeclarativeAgentSpec`: | ||
|
|
||
| - `kind != LlmAgent` ⟹ `subAgents` is non-empty and `modelConfig`/`tools` are unset. | ||
| - `kind == Loop` may set `workflow.maxIterations`; other kinds may not. | ||
| - `kind == LlmAgent` ⟹ `subAgents` is empty. | ||
|
|
||
| Example — a sequential "research pipeline": | ||
|
|
||
| ```yaml | ||
| apiVersion: kagent.dev/v1alpha2 | ||
| kind: Agent | ||
| metadata: | ||
| name: research-pipeline | ||
| namespace: kagent | ||
| spec: | ||
| type: Declarative | ||
| declarative: | ||
| runtime: go | ||
| kind: Sequential | ||
| subAgents: | ||
| - name: researcher | ||
| outputKey: research_notes | ||
| - name: writer | ||
| outputKey: draft | ||
| - name: fact-checker | ||
| ``` | ||
|
|
||
| Example — a bounded refinement loop: | ||
|
|
||
| ```yaml | ||
| spec: | ||
| type: Declarative | ||
| declarative: | ||
| kind: Loop | ||
| workflow: | ||
| maxIterations: 5 | ||
| subAgents: | ||
| - name: drafter | ||
| outputKey: draft | ||
| - name: critic # escalates to end the loop when satisfied | ||
| ``` | ||
|
|
||
| ### Runtime changes (Go ADK) | ||
|
|
||
| Introduce a factory in [`go/adk/pkg/agent/agent.go`](../go/adk/pkg/agent/agent.go) | ||
| that dispatches on kind. Today the function body unconditionally builds an | ||
| `LlmAgent`; it becomes the `LlmAgent` branch of a switch: | ||
|
|
||
| ```go | ||
| switch cfg.Kind { | ||
| case adk.AgentKindSequential: | ||
| subs, err := buildSubAgents(ctx, cfg.SubAgents, log) | ||
| if err != nil { return nil, nil, err } | ||
| a, err := sequentialagent.New(sequentialagent.Config{ | ||
| AgentConfig: agent.Config{Name: agentName, Description: cfg.Description, SubAgents: subs}, | ||
| }) | ||
| // ... | ||
| case adk.AgentKindParallel: | ||
| // parallelagent.New(parallelagent.Config{AgentConfig: agent.Config{... SubAgents: subs}}) | ||
| case adk.AgentKindLoop: | ||
| // loopagent.New(loopagent.Config{AgentConfig: agent.Config{... SubAgents: subs}, MaxIterations: cfg.MaxIterations}) | ||
| default: // LlmAgent — existing path, unchanged | ||
| llmAgent, err := llmagent.New(llmAgentConfig) | ||
| // ... | ||
| } | ||
| ``` | ||
|
|
||
| `buildSubAgents` compiles each referenced sub-agent into an `agent.Agent` and | ||
| applies its `OutputKey` (via `llmagent.Config.OutputKey`, which the ADK writes to | ||
| session state as a `StateDelta`). The workflow constructors then orchestrate | ||
| `ctx.Agent().SubAgents()` per their policy. | ||
|
|
||
| Corresponding additions to the `adk.AgentConfig` transport type | ||
| ([`go/api/adk`](../go/api/adk)) — a `Kind` field, a `SubAgents []SubAgentConfig` | ||
| list, and `MaxIterations` — plus the controller/translator work in | ||
| [`adk_api_translator.go`](../go/core/internal/controller/translator/agent/adk_api_translator.go) | ||
| to populate them from the CRD. | ||
|
|
||
| ### Sub-agent resolution | ||
|
|
||
| Sub-agents are referenced **by name** (not inlined). Rationale, consistent with | ||
| the EP-685 tool-server decision: | ||
|
|
||
| - Sub-agents are reusable across workflows and independently testable. | ||
| - Each sub-agent is a normal `Agent` resource, so RBAC, model config, and tool | ||
| wiring are unchanged and per-agent. | ||
| - The controller resolves references at compile time; a missing/failed reference | ||
| surfaces on the parent agent's status. Sub-agent references participate in the | ||
| reconcile watch set so parent agents recompile when a child changes. | ||
|
|
||
| Inlining is considered and rejected in Alternatives. | ||
|
|
||
| ### Test Plan | ||
|
|
||
| - **Unit (Go runtime):** table-driven tests over the factory — each kind builds | ||
| the expected ADK agent with the right `SubAgents`/`MaxIterations`; `OutputKey` | ||
| is propagated; unknown kind errors cleanly. Mock sub-agent compilation. | ||
| - **Unit (API):** CEL validation tests (mirroring `agent_spec_validation_test.go`) | ||
| for the kind/subAgents/model-field exclusivity and Loop-only `maxIterations`. | ||
| - **Translator:** CRD → `adk.AgentConfig` mapping for each kind, including | ||
| sub-agent reference resolution and error on dangling references. | ||
| - **E2E:** deploy a Sequential pipeline and a bounded Loop; assert ordered | ||
| execution / state hand-off via `OutputKey`, and that the Loop terminates at | ||
| `maxIterations`. Backward-compat E2E: an existing kind-less agent behaves | ||
| identically to before. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| **Inline sub-agent definitions.** Embed full sub-agent specs inside the parent | ||
| rather than referencing named `Agent` resources. Rejected: it duplicates model/ | ||
| tool configuration, prevents reuse and independent RBAC, and bloats a single CRD — | ||
| the same reasons EP-685 favored references over inlining for MCP servers. Named | ||
| references keep each agent a first-class, independently reconciled object. | ||
|
|
||
| **Do nothing / lean on agent-as-tool.** Users can already expose agents as tools | ||
| and let a coordinator LLM delegate. Rejected as insufficient: delegation is | ||
| non-deterministic and cannot guarantee ordering, exhaustive fan-out, or bounded | ||
| iteration — the precise guarantees these workflow types provide. | ||
|
|
||
| **Only add Sequential.** Sequential covers the most common pipeline case with the | ||
| least surface area. Rejected as the primary plan because Parallel and Loop reuse | ||
| the identical `SubAgents` machinery; adding them together avoids a second API | ||
| break, though a phased rollout (Sequential first) is a reasonable delivery order. | ||
|
|
||
| ## Open Questions | ||
|
|
||
| - **Runtime parity.** Should the first cut require `runtime: go`, or must the | ||
| Python runtime reach parity before GA? The declarative surface is | ||
| runtime-agnostic; the Python compiler would need equivalent factory support. | ||
| - **State/output-key ergonomics.** Is per-sub-agent `outputKey` on the reference | ||
| the right place, or should it live on the sub-agent's own spec? Referencing it | ||
| from the parent keeps the child reusable but splits config across resources. | ||
| - **Nesting depth.** Workflow agents can contain workflow agents (a Sequential of | ||
| Parallels). Do we bound nesting depth, and how do we guard against reference | ||
| cycles at admission time? | ||
| - **Loop termination.** Beyond `maxIterations`, ADK loops end when a sub-agent | ||
| *escalates*. How do we let a declarative sub-agent signal escalation without | ||
| custom code — a convention on the reviewer agent, or a dedicated field? | ||
| - **A2A exposure.** Should a workflow agent be independently addressable over A2A | ||
| (`a2aConfig`) like an `LlmAgent`, and how are streaming events attributed to | ||
| sub-agents in the event author field? | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this design doc relevant to this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two commits to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we separate them? I'm happy to merge the bump, but would like time to look through the design
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure. will do