-
Notifications
You must be signed in to change notification settings - Fork 722
Comparing changes
Open a pull request
base repository: google/adk-go
base: v1.4.0
head repository: google/adk-go
compare: v2.0.0
- 16 commits
- 578 files changed
- 14 contributors
Commits on Jun 1, 2026
-
Configuration menu - View commit details
-
Copy full SHA for f771a33 - Browse repository at this point
Copy the full SHA f771a33View commit details
Commits on Jun 2, 2026
-
Context unification: switch all to not deprecated (#935)
* Replaced tool.Context with agent.ToolContext * fixed runnableTool
Configuration menu - View commit details
-
Copy full SHA for 81a63d8 - Browse repository at this point
Copy the full SHA 81a63d8View commit details
Commits on Jun 10, 2026
-
fix: bump x/net and otel OTLP exporters to patch govulncheck advisori…
…es (#994) Resolves the nightly govulncheck failures (exit 3) by updating the modules flagged in the call graph to their fixed versions: - golang.org/x/net v0.54.0 -> v0.55.0 (GO-2026-5026) - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 -> v0.19.0 (GO-2026-4985) - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 -> v1.43.0 (GO-2026-4985) Core go.opentelemetry.io/otel stays at v1.43.0; companion indirect deps (otel/log, otel/sdk/log, otlptrace, proto/otlp, grpc-gateway, x/sys) move forward via go mod tidy. Verified locally: go build ./... and go test -race -mod=readonly -count=1 -shuffle=on ./... both pass, and govulncheck no longer reports GO-2026-5026 or GO-2026-4985.
Configuration menu - View commit details
-
Copy full SHA for a9a99a7 - Browse repository at this point
Copy the full SHA a9a99a7View commit details -
chore: complete Node 24 migration for GitHub Actions (#996)
Bump the remaining actions still on the deprecated Node 20 runtime: - actions/setup-go v5.5.0 -> v6.4.0 (shared setup composite) - actions/cache v4.2.3 -> v5.0.5 (shared setup composite) - golang/govulncheck-action v1.0.4 -> master HEAD setup-go only moved to Node 24 in v6.2.0, so v5.5.0 was still Node 20. The govulncheck-action latest release (v1.0.4) internally pins actions/checkout@v4.1.1 and actions/setup-go@v5.0.0 (Node 20); its master branch already uses Node 24 actions but has not been tagged, so it is pinned to master HEAD with a TODO to re-pin once a release past v1.0.4 is cut. A previous pass only bumped actions/checkout, leaving these behind.
Configuration menu - View commit details
-
Copy full SHA for 38b11f7 - Browse repository at this point
Copy the full SHA 38b11f7View commit details
Commits on Jun 15, 2026
-
feat: add platform clock and UUID provider seams for deterministic ev…
…ent creation (#964) Introduce a platform package whose current time and UUID generation can be overridden per-context, mirroring the ContextVar-based seams in ADK-Python: - platform.Now(ctx) / platform.WithTimeProvider(ctx, fn) - platform.NewUUID(ctx) / platform.WithUUIDProvider(ctx, fn) Both fall back to time.Now and uuid.NewString when no provider is installed, so default behavior is unchanged. To honor the API stability policy (no breaking changes in a minor release), the existing session.NewEvent(invocationID) signature is preserved and marked deprecated; it delegates to the new session.NewEventWithContext(ctx, invocationID), which sources the event ID and timestamp from the platform seams. All in-repo event/session creation call sites are threaded with context and use NewEventWithContext, so a host runtime can make event creation deterministic and replay-safe (e.g. a workflow engine that must reproduce an execution exactly on replay).
Configuration menu - View commit details
-
Copy full SHA for c6f168f - Browse repository at this point
Copy the full SHA c6f168fView commit details
Commits on Jun 16, 2026
-
feat(agent): add StrictContextMock test double (#1019)
StrictContextMock implements the full ToolContext / CallbackContext / ReadonlyContext surface so it can be embedded in a test fake; embedders keep compiling as the interfaces grow, instead of breaking on every added method. Un-overridden methods panic with "not implemented" (a loud failure on unexpected calls), while the context.Context methods (Deadline/Done/Err/Value) delegate to the wrapped Ctx.
Configuration menu - View commit details
-
Copy full SHA for 9f6080f - Browse repository at this point
Copy the full SHA 9f6080fView commit details -
fix: Allow manual setting of session IDs (#721)
* fix/allow manual setting of session IDs * fixing missing return + session case issues for inevitable test replay * Changes in tests * Updated replays * check rpc status code instead of string match --------- Co-authored-by: Karol Droste <kdroste@google.com>
Configuration menu - View commit details
-
Copy full SHA for 90091e2 - Browse repository at this point
Copy the full SHA 90091e2View commit details -
docs: add AGENTS.md for AI coding agents (#1045)
Add an AGENTS.md with project context, exact build/test/lint commands, repository layout, idioms, a minimal example, framework-extension and testing guidance, and contribution boundaries for AI coding agents. GEMINI.md and CLAUDE.md are thin pointers to AGENTS.md so Gemini CLI and Claude Code pick up the same single source of truth.
Configuration menu - View commit details
-
Copy full SHA for bfab1bd - Browse repository at this point
Copy the full SHA bfab1bdView commit details
Commits on Jun 18, 2026
-
fix(agentengine): support Gemini Enterprise AgentSpace streams (#777)
* fix(agentengine): support Gemini Enterprise AgentSpace streams * refactor(agentengine): split streaming_agent_run_with_events handler * fix(agentengine): add MemoryService to streamingAgentRunWithEventsHandler runner configuration. * fix(adkrest): make debug telemetry span tests order-insensitive - The Go test check was failing in TestDebugTelemetryGetSpansBySessionID because the test asserted a fixed ordering for returned spans. In practice, the debug telemetry store can return the same set of spans in a different order depending on span/export timing, especially when spans are created and ended very close together. - This change keeps the production behavior unchanged and updates the tests to compare spans as a stable sorted set before diffing. The comparison still checks the span names, relevant attributes, and logs, but no longer treats incidental retrieval order as part of the contract. * chore(agentengine): clarify streaming agent run metadata Document the request_json payload for streaming_agent_run_with_events, mark the method as async_stream consistently, and replace realistic test fixture IDs with clearly fake values.
Configuration menu - View commit details
-
Copy full SHA for fea8596 - Browse repository at this point
Copy the full SHA fea8596View commit details -
Get rid of context.Background (#1062)
* Got rid of context.Background * Linter fixes
Configuration menu - View commit details
-
Copy full SHA for b304aaf - Browse repository at this point
Copy the full SHA b304aafView commit details
Commits on Jun 19, 2026
-
fix: update built-in load_memory tool for compatibility with VertexAi…
…SessionService (#793) * fix: update load_memory tool with json marshaling of memory search response into map[string]any to avoid downstream incompatibility with Go struct types * revert: undo initial changes to load_memory tool * fix: safely normalize all part.FunctionResponse.Response types via JSON round-trip before conversion to structpb. This prevents errors or panics in `structpb.NewStruct()` caused by raw Go types that are incompatible with Protocol Buffers. * feat: add utility to safely normalize Go types via JSON round-trip before conversion to structpb. This prevents errors or panics in structpb.NewStruct() caused by raw Go types that are incompatible with Protocol Buffers. * chore: rename and simplify toStructPB util function * chore: add broader vertexai sessions test coverage --------- Co-authored-by: nicholas@alisx.com <nicholas@alisx.com> Co-authored-by: Karol Droste <kdroste@google.com>
Configuration menu - View commit details
-
Copy full SHA for ca2e3d5 - Browse repository at this point
Copy the full SHA ca2e3d5View commit details -
* session test suite fix * Linter fixes
Configuration menu - View commit details
-
Copy full SHA for 44e3d67 - Browse repository at this point
Copy the full SHA 44e3d67View commit details
Commits on Jun 24, 2026
-
Configuration menu - View commit details
-
Copy full SHA for 5350266 - Browse repository at this point
Copy the full SHA 5350266View commit details
Commits on Jun 30, 2026
-
test(session/vertexai): add table-driven tests for FunctionCall/Respo…
…nse mapping (#739) Add TestAiplatformToGenaiContent_FunctionCallMapping, a table-driven test that verifies aiplatformToGenaiContent correctly preserves: - ID, Name, and Args for FunctionCall parts - ID, Name, and Response for FunctionResponse parts - empty-string IDs are passed through unchanged The table-driven format matches the style used elsewhere in this file and makes it easy to add further cases.
Configuration menu - View commit details
-
Copy full SHA for caf798a - Browse repository at this point
Copy the full SHA caf798aView commit details -
* feat(workflow): initial naive agent implementation Besides naive implementation, basic struct and interfaces were added. Includes the core workflow implementation, unit tests, and an example to run it in the web UI. * Adjust api of basic componentes after review (#769) * feat: implement workflow routing support with tagged events and conditional graph traversal (#768) * feat: implement workflow routing support with tagged events and conditional graph traversal * refactor: rename event Route field to Routes and update workflow traversal logic * feat: add MultiRoute generic type to support matching multiple route values (#781) feat: add tool node to workflow agents (#776) * feat: add tool node to worflow agents * update tool node so it's not converting the output to the types, but only validates the schema * check the tool is runnable inside constructor * nit: fix node names in test * feat: implement explicit Default route support in workflow graph traversal (#787) * feat: add EdgeBuilder to simplify workflow graph construction (#795) feat: add NodeConfig to the workflow nodes (#796) * feat: implement node name validation and update workflow construction to return errors (#797) * feat: add start node validation (#807) * feat: introduce scheduler-based engine with goroutine-per-node execution (#803) * workflow: introduce scheduler-based engine with goroutine-per-node execution Squashes the workflow engine WIP work onto the latest wolo/workflows base. The branch had 8 incremental commits; this single commit captures the cumulative state on top of upstream PR #795 (EdgeBuilder), PR #796 (NodeConfig), and PR #797 (node name validation). Architecture: * Goroutine-per-node execution model: each scheduled node runs in its own goroutine, pushing events into a buffered channel. * Single consumer goroutine (runState.run) drains the channel, applies state-side effects, yields events to the caller, and schedules successors when nodes complete. * Replaces the legacy in-process BFS in Workflow.Run with the new scheduler, removing findNextNodes and the inline event loop. New types and constructors: * BaseNode (and NewBaseNode) for shared Name/Description/Config bookkeeping; FunctionNode and toolNode now embed it. * Graph helpers in graph.go: indexed adjacency for O(1) successor lookup. * RunState (persistable) and runState (in-process scheduler bag) in state.go; node lifecycle map (NodeStatus + per-node accumulators). * NodeContext wraps InvocationContext with a per-node TriggeredBy accessor; agent.InvocationContext gains TriggeredBy() returning "" for non-workflow contexts (mocks updated accordingly). * Scheduler (scheduler.go): runNode goroutine wrapper, eventQueue, cancelAll, and the run consumer loop with sibling cancellation and per-node timeout. Routing and validation: * findSuccessors honours unconditional edges, concrete Routes, and the Default fallback. Silent dead-ends remain intentional per adk-python parity. * Workflow.New now returns (*Workflow, error) — picks up the name validation introduced upstream by PR #797. NodeConfig timeout shape: * Timeout is time.Duration (not *time.Duration), with zero meaning "inherit parent context". Matches net.Dialer.Timeout and the http.Server.*Timeout convention; keeps call sites free of pointer boilerplate. * Adds RerunOnResumeOr / WaitForOutputOr accessor helpers for pointer-typed pointer-typed tri-state fields. * Adds TestDefaultRetryConfig from upstream PR #796. Examples and tests: * examples/workflow/basic uses NodeConfig{RetryConfig: DefaultRetryConfig()} to demo the helper. * 7 New(edges) callers updated for the new (*Workflow, error) signature. Tests verified: go build ./..., go vet ./..., go test -race ./workflow/... ./agent/workflowagent/... all pass. Init function node in agent workflows (#810) * feat: implement default route validation to prevent multiple default routes per node (#814) * refactor: implement unconditional cycle detection in workflow validation. (#811) * feat: implement workflow validation to reject duplicate edges and remove redundant edge filtering logic (#808) * feat: implement workflow connectivity validation to ensure all nodes are reachable from start (#809) * workflow: add HITL pause primitives (RequestInput, waiting node) (#829) Adds the engine-side scaffolding for human-in-the-loop pauses: a workflow node can now emit a session.RequestInput, and the scheduler will park the node in NodeWaiting and stop scheduling its successors instead of finalising the run. This is the pause half of HITL only; the resume half (Workflow.Resume, the agent.Agent wrapper, schema validation, handoff vs. re-entry modes) is left for follow-up PRs. Type names and field names are aligned with adk-python's RequestInput in src/google/adk/events/request_input.py: interrupt_id / message / response_schema / payload. API additions: * session.RequestInput carries the prompt: InterruptID (stable correlation key), Message (UI text), ResponseSchema (reserved for the future validator), Payload (opaque UI context). * session.Event.RequestedInput field, parallel to Routes; populated by the node, consumed by the scheduler and forwarded to the UI surface unchanged. * workflow.NewRequestInputEvent(ctx, req) constructor, including UUID auto-generation when InterruptID is empty. * workflow.NodeState.PendingRequest, persisted on the per-node state when the waiting branch fires. * workflow.ErrMultipleInputRequests sentinel for the single-request-per-activation invariant. Scheduler changes: * nodeRun gains an inputRequest field with a setInputRequest method that mirrors the existing setRoutingEvent / setOutput pattern. * handleEvent dispatches on ev.RequestedInput exactly parallel to the existing dispatch on ev.Routes. * handleCompletion gains a waiting branch checked AFTER the error/cancel branches: a clean activation that recorded a request transitions to NodeWaiting, persists the request on NodeState, and skips successor scheduling. Failures take precedence so a node that recorded a request and then errored out lands in NodeFailed, not NodeWaiting. * The scheduler.run loop is unchanged: it terminates when the runsByName map empties, which now happens when every live node has either completed or moved into NodeWaiting. Tests: * TestScheduler_HitlNode_PausesAndForwardsRequest pins the happy-path single-waiting-node behaviour. * TestScheduler_HitlNode_AutoGeneratesInterruptID and TestScheduler_HitlNode_PreservesExplicitInterruptID lock in the InterruptID contract. * TestScheduler_HitlNode_MultipleRequestsFails surfaces ErrMultipleInputRequests at completion. * TestScheduler_HitlNode_ErrorAfterRequestFails pins the fail-over-park precedence in handleCompletion. * TestScheduler_HitlNode_ConcurrentBranches_PausesOnlyWhenAllNonRunning exercises a parallel-branch graph: the non-HITL branch finishes normally while the HITL branch parks; the workflow ends only when both reach a terminal state. All existing workflow tests still pass; the suite is race-free under go test -race. * workflow: add HITL resume API with handoff mode and session-state persistence (#847) Builds on the pause-side primitives in the previous PR by adding the resume half of the human-in-the-loop cycle: Workflow.Resume routes a user-supplied response back into a paused workflow, and the existing workflowagent.New wrapper learns to detect and dispatch resume turns automatically. Run state survives across processes by riding in session.State. Engine additions in workflow/: * Workflow.Resume(ctx, state, responses) iter.Seq2 — for each NodeWaiting whose InterruptID matches an entry in responses, validates the payload against PendingRequest.ResponseSchema (when non-nil), consumes PendingRequest before re-scheduling for idempotency, then routes the response to the asker's successors via findSuccessors so handoff reuses the normal routing / fan-out / fan-in path. ErrInvalidResumeResponse surfaces validation failures and leaves the node parked so the caller can retry. * newSchedulerFromState lets Resume seed a scheduler with a loaded RunState rather than a fresh one. * Workflow.SetName / Workflow.Name expose the persistence-namespacing identifier set by workflowagent.New. * Workflow.Run now persists the post-run state at the end of every invocation so a follow-up Resume can pick it up. * WorkflowInputFunctionCallName constant ('adk_request_workflow_input') and the synthesised FunctionCall part on RequestInputEvent — the same shape tool confirmation uses, lets the runner's generic ID-based dispatch route the user's follow-up FunctionResponse back to this agent without runner-side changes. * RunState / NodeState / RequestInput gain JSON tags. Persistence uses session.State.Get/Set with JSON marshalling; binary payloads must be stashed via agent.Artifacts and referenced by URI in Payload (mirrors how the Python Live API surfaces audio). Persistence helpers (workflow/persistence.go): * LoadRunState / SaveRunState exported so workflowagent (and any custom wrapper) can manage RunState lifecycle without taking a dependency on internal package details. * Both helpers handle nil session and nil session.State as no-ops for tests using minimal mocks. Wrapper changes in agent/workflowagent/: * workflowAgent.run dispatches between Workflow.Run and Workflow.Resume by inspecting ctx.UserContent for a FunctionResponse with the magic name. Stateless: every run state lives in session.State. * detectResume + decodeWorkflowInputResponse handle the dual wire format used by the existing tool-confirmation processor (ADK web wraps payloads as {response: <json string>}; other clients inline {payload: ...}). * MockSession in the existing workflow_test gains a State() method returning nil, supported by the persistence helpers' nil guards. Tests (agent/workflowagent/hitl_test.go, ~430 lines): * TestWorkflowAgent_RunThenResume_Handoff — canonical round-trip; pause on RequestInput, resume with payload, handler receives it as input. * TestWorkflowAgent_Resume_RestoresStateFromSession — fresh agent instance built from the same edges resumes successfully off the same session, verifying cross-instance persistence. * TestWorkflowAgent_Resume_Idempotent — two Resume calls with the same payload run the handler exactly once. * TestWorkflowAgent_Resume_NoMatchingResponse — Resume with an InterruptID that no longer matches a waiting node falls through cleanly without blocking on an empty scheduler. * TestWorkflowAgent_Resume_SchemaValidation_{Pass,Fail} — engine validates responses against ResponseSchema; invalid payloads surface ErrInvalidResumeResponse and leave the node parked, so a retry with a corrected payload still succeeds. * TestWorkflowAgent_Resume_FanOut — handoff into a multi-successor asker delivers the response to every successor. * TestWorkflowAgent_FreshTurn_NotMistakenForResume — a fresh user message that happens to share a session with leftover state from a prior workflow does not get misinterpreted as a resume. All existing workflow and workflowagent tests still pass; the suite is race-free under go test -race. * feat: add HITL re-entry resume mode for workflow agents (#832) Builds on #831 (HITL handoff resume) by adding re-entry mode: when a paused node is configured with `NodeConfig.RerunOnResume = true`, `Workflow.Resume` re-activates the asker itself instead of forwarding the response to its successors. The asker observes the response via `ctx.ResumedInput(interruptID)` and decides what to emit. Direct analogue of adk-python's `@node(rerun_on_resume=True)`. Re-entry preserves the asker's original input, so the node can recompute its decision with the same context it had on the first turn — only the user's response arrives separately. Successors fire only when the re-entry activation produces an output, not on the bare resume call. Support of multiple asks while executing one node will be added in a follow-up PR. - [x] `go build ./...` clean - [x] `go test ./workflow/... ./agent/workflowagent/...` — all pass - [x] `go test -race` clean - [x] 4 new tests covering the canonical re-entry round-trip, original-input preservation across re-entry, the no-successor-before-output invariant, and a regression guard pinning that handoff is still the default mode - [x] 1 new test (`TestNodeContext_ResumedInput`) pinning the per-node context-level contract * feat: remove unused TriggeredBy() interface method (#841) agent.InvocationContext.TriggeredBy() was added in the workflow scheduler PR (#803) as 'engine-supplied metadata available to nodes' but no production caller ever materialised: the method is called only by its own round-trip test and by zero workflow nodes, samples, agents, tools, callbacks, or telemetry sites in the repo. The godoc on the interface method is aspirational; the implementation behind it is a dead branch. This change drops the public API surface (one interface method plus six implementations: agent.invocationContext, internal/context.InvocationContext, and the four MockInvocationContext types in workflowagent, workflow, replayplugin, and llminternal). The unrelated NodeState.TriggeredBy field remains: scheduler.go populates it for resume bookkeeping (workflow.Resume reads it back when re-scheduling a paused node), and it is part of the JSON-serialised RunState so dropping it would break forward compatibility for anyone already running the engine. The TestNewNodeContext_TriggeredByRoundTrip test is removed (it exercised the now-deleted method); TestNodeContext_ResumedInput keeps its coverage of the surviving wrapper functionality. * fix: persist resume inputs across re-entry cycles (#844) * workflow: persist resume inputs across re-entry cycles A re-entry-mode node (NodeConfig.RerunOnResume = true) that yields RequestInput more than once across resume cycles previously lost prior responses on each subsequent resume: ctx.ResumedInput exposed only the response to the most recent InterruptID, and asking the node about an earlier ID returned (nil, false) even though the user had already answered it. The fix accumulates response payloads on NodeState across resume cycles. Each Resume call merges the new {InterruptID: response} entry into ns.ResumedInputs; the scheduler hands the full map to the per-node context on every re-entry activation. The node sees every prior response, not only the most recent one. feat: add retry config implementation (#853) * feat: add generic Output field to session events and persist via storage layer (#863) * feat: workflow - add JoinNode fan-in barrier (#856) Adds a fan-in primitive built on the orchestrator-aggregator model: the scheduler waits until every predecessor of a JoinNode has completed, assembles a map[string]any keyed by predecessor name, and triggers the JoinNode once with that aggregated input. The node itself is a pass-through that emits the input as its output. * feat: implement AgentNode (#840) * fix: align WorkflowInputFunctionCallName with adk-python (#875) The Go workflow runtime synthesises a FunctionCall part on every RequestInput event so the generic FunctionResponse-by-ID dispatch can route the user's follow-up reply back to the agent that issued the request. The synthesised call's Name was 'adk_request_workflow_input'. adk-python uses 'adk_request_input' for the equivalent constant (REQUEST_INPUT_FUNCTION_CALL_NAME in google/adk/workflow/utils/_workflow_hitl_utils.py). The divergence broke cross-runtime HITL workflows: a session recorded by Python (with function_call.name='adk_request_input' in the interrupt event) could not be replayed in Go, because Go-side conformance compare would see 'adk_request_workflow_input' and flag the mismatch. The reverse direction is broken too — a function_response addressed by name to 'adk_request_input' from a Python-authored spec.yaml could not be routed to a Go workflow agent's pending interrupt. This change renames the constant value to 'adk_request_input'. The exported symbol WorkflowInputFunctionCallName is unchanged, so no Go consumer is affected. The only behaviour difference is the literal value placed on the wire, which now matches Python. Discovered while preparing the first cross-language conformance test for graph-based Workflow + HITL. * refactor: replace StateDelta output tracking with direct Event.Output field usage (#872) * feat: add HITL console launcher support for workflow input prompts + sample (#845) * console: add HITL support for workflow input prompts Adds engine-agnostic HITL handling to the console launcher: detect interrupts emitted on the previous turn, render a prompt for the operator, and forward the typed reply as a FunctionResponse on the next turn. Detection is uniform across interrupt kinds: * collectPendingInterrupts walks the events yielded during a turn and returns one pendingInterrupt per FunctionCall part whose ID appears in Event.LongRunningToolIDs. The call's Name is only used for rendering and response shaping, never for detection. Workflow RequestInput and any future long-running call kind flow through the same path. Per-name dispatch (renderInterruptPrompt / buildInterruptResponse): * workflow.WorkflowInputFunctionCallName: prints '[HITL input]' with the message; pretty-prints the payload (the proposal / context attached by the asker node) and the JSON schema if either is present. Operator's reply is JSON-parsed first so structured replies (objects, arrays, scalars) round-trip as typed values; falls back to raw text. Wrapped under 'payload', the conventional key for workflow input responses. * Anything else: prints a generic banner and wraps the raw input as {result: <text>}. Lets e.g. a future adk_request_credential path be answered through this launcher without a code change here, even before its dedicated renderer exists. Tool confirmation (toolconfirmation.FunctionCallName) currently hits this generic fallback and works at the transport layer (the reply still routes back to the tool by FunctionCall.ID), but the {result: <text>} envelope does not match what ctx.ToolConfirmation() expects to read. A follow-up adds a dedicated renderer and yes/no parser. Main loop integration (console.go): * After every r.Run iteration drains, scan collectedEvents for pending interrupts. If any, render the head's prompt and skip the normal '\nUser -> ' banner. * The next stdin line is interpreted as the answer to that head; successive lines drain the rest. Once every interrupt has an answer, the assembled FunctionResponse parts are sent in one *genai.Content as the next 'turn' through the same r.Run path. The reply routes back to the agent by FunctionCall.ID. * Multiple parallel pauses (rare but legal — e.g. two parallel workflow branches both yielding RequestInput on the same turn) are collected together and answered one prompt at a time, then submitted as one Content with multiple FunctionResponse parts. Renamed the local 'session' variable to 'sess' inside Run to avoid shadowing the session package import once the new code path needs []*session.Event. Race-free under go test -race. * examples/workflow/hitl_simple: minimal HITL sample for console launcher verification A no-LLM, no-API-key sample that exercises the console launcher's pause/resume support end-to-end. Two workflow nodes: Start → ask_name → greet ask_name yields a RequestInput so the launcher renders the prompt. greet receives the user's reply as plain text and emits a greeting that the launcher prints. Useful for verifying that wolo/workflow_hitl_console produces the expected console output without any LLM streaming in the mix. Run with: go run ./examples/workflow/hitl_simple/ console User -> hello Agent -> What's your name? User -> Alice Agent -> Hello, Alice! * feat: add llmagent mode api (#900) * feat: add llmagent mode api feat: add new parallel worker node type to the adk workflows so it could process multiple inputs simultaneously (#862) * feat(telemetry): add functional test infrastructure and agent telemetry tests (#894) Adds shared infrastructure for telemetry functional tests: - internal/telemetry/functionaltest: end-to-end functional test package for asserting emitted span+log trees. - internal/telemetry/telemetrytest: helpers for in-memory log exporter, span/log digesting, and scenario runners. - internal/telemetry/telemetrytestcase: reusable expected-output fixtures keyed per scenario. Adds an end-to-end functional test for the canonical "llmagent with one FunctionTool" scenario, parametrized over OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT to cover both the elided (default) and full-content variants. Adds test hooks (OverrideTracerForTesting, OverrideLoggerForTesting) to internal/telemetry so functional tests can install in-memory exporters hermetically. Removes the WithGenAICaptureMessageContent option which aliased the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT env var toggle. The option introduced unnecessary global state which made hermetic testing difficult; getGenAICaptureMessageContent now reads the env var directly so tests can flip behaviour via t.Setenv. * feat: add FinishTaskTool (#901) * feat(workflow): publish NodeContext + add error sentinels and sub-scheduler skeleton (#889) Adds the foundation the dynamic-workflows track builds on: a public NodeContext interface with composite-path support, typed errors, and a per-activation sub-scheduler that runs one child and classifies the outcome. workflow.NodeContext is now an exported interface extending agent.InvocationContext, mirroring the interface-not-struct shape of agent.CallbackContext and the forthcoming agent.ToolContext so future context-unification can swap the backing type without breaking users. dynamicSubScheduler runs one child per call, forwards its events upstream via an emitUp callback, derives a composite child path, and classifies the outcome (HITL → ErrNodeInterrupted, runtime failure → ErrNodeFailed). Child run ids come from a per-(parent activation, child name) auto-counter protected by a mutex for concurrent errgroup-style use; user-supplied custom ids are rejected when empty, purely numeric, or containing the composite-path separators / and @. Precedence matches adk-python: a child that fails after emitting RequestedInput surfaces as ErrNodeFailed, not ErrNodeInterrupted. Resume/replay-skip, parent NodeWaiting persistence, parallel HITL detection, and the public NewDynamicNode / RunNode API stay deferred to follow-up PRs. * feat(workflow): add dynamic-node orchestration (#896) Wires the user-facing API for dynamic workflows on top of the sub-scheduler skeleton from the previous PR. A dynamic node's execution order is expressed as Go code (loops, branches, goroutines) that calls other nodes inline via RunNode, branches on their typed output, and pauses for HITL input. The public surface: workflow.NewDynamicNode[IN, OUT](name, fn, cfg) — orchestrator constructor; cfg.RerunOnResume defaults to &true (an explicit &false is respected). workflow.RunNode[OUT](ctx, child, input, opts...) — generic helper for scheduling a child. Returns its typed output, or errors.Is-matchable ErrNodeInterrupted / ErrNodeFailed. workflow.WithRunID(id) — option overriding the auto-counter with a stable id (rejected if empty, purely numeric, or containing / or @). session.NodeInfo — substruct on Event carrying the emitting node's composite path; shape mirrors adk-python's event.nodeInfo. The scheduler's handleEvent scopes per-activation Output/Routes invariants by NodeInfo.Path, so a dynamic node forwarding a child's terminal output plus its own no longer trips ErrMultipleOutputs. Descendant RequestedInput events are promoted onto the parent's accumulator so Workflow.Resume matches the InterruptID against the parent's NodeState.PendingRequest — enabling HITL inside a dynamic orchestrator. * examples: minimal dynamic-workflow sample (#897) * examples/workflow/dynamic/basic: minimal dynamic-workflow sample Runnable mirror of the "Get started" snippet from https://adk.dev/graphs/dynamic/: a parent dynamic node orchestrates a single FunctionNode child via workflow.RunNode and emits its output upstream. ~50 lines of actual logic, no API keys required. Sits alongside examples/workflow/basic/ and uses the same launcher integration so users can `go run` it and exercise the workflow through the standard CLI/UI. Stacked on the NewDynamicNode + RunNode public-API PR; intentionally minimal so reviewers can validate the API shape against the canonical Python example. Richer samples (loop, parallel, HITL) follow once the corresponding Go features (resume, parallel HITL detection) land. * examples/workflow/dynamic/llm: add LlmAgent-backed dynamic-workflow sample Smallest sensible composition that puts an LLM into a dynamic workflow: one llmagent.New (gemini-3.1-flash-lite, one-line greeting instruction) wrapped via workflow.NewAgentNode and invoked from a NewDynamicNode body through workflow.RunNode. Mirrors the existing examples/workflow/dynamic sample but replaces the trivial FunctionNode child with a real LlmAgent, demonstrating the agent->node->dynamic composition path. Verified end-to-end with 'echo hi | go run ./examples/workflow/dynamic/llm console': greeter responds with a one-sentence greeting via the deployed Gemini endpoint. * examples/workflow/dynamic/hitl: add HITL dynamic-workflow sample Smallest sensible composition that pauses for human input from inside a dynamic-node orchestrator: an inline askName node yields workflow.NewRequestInputEvent; the orchestrator drives it via workflow.RunNode, swallows ErrNodeInterrupted on the pause activation, and on resume re-entry reads the reply via NodeContext.ResumedInput (RerunOnResume defaults to &true for dynamic nodes). A mid-body emit publishes the greeting as Content so the console launcher renders it; the terminal Output carries the same string for downstream nodes. * feat: extend Node interface with JSON schema support and input validation methods (#895) * workflow: experimental NodeContext bridge for runnable tools (#907) Adds an opaque go-context value stash, populated by the scheduler at each per-node activation, that lets tools running inside an LlmAgent (which is itself running as a workflow node) recover the surrounding NodeContext via context.Value lookup. This is a temporary bridge to unblock NewSingleTurnTool (and any future runnable tool that needs to schedule sub-nodes) without modifying the public tool.Context interface. The longer-term solution is the CallbackContext / ToolContext unification tracked in workflow/node_context.go. Mechanism: tool.Context embeds context.Context (transitively via agent.CallbackContext -> agent.ReadonlyContext), so the value survives every downstream NewInvocationContext / WithContext call on the path scheduler -> AgentNode.Run -> LlmAgent.run -> Flow.handleFunctionCalls -> NewToolContext. No interface changes, no agent_node modifications. * feat(workflow): branch isolation across static, parallel, and dynamic schedulers (#906) Adds branch derivation across the three workflow schedulers so the LLM flow's branch-prefix history filter (already present in contents_processor.go) actually scopes events per parallel branch instead of seeing every node run on the empty root branch. Before this PR, Event.Branch and InvocationContext.Branch() existed and the filter respected them, but no scheduler ever derived a non-empty branch — so an LlmAgent wrapped by ParallelWorker (or by a hand-written errgroup fan-out) saw every sibling worker's events in its prompt history. The static scheduler now derives <successor>@1 sub-branches at fan-out, computes the longest common dot-prefix of predecessor branches for JoinNode, stamps Event.Branch when the node leaves it empty, and persists each activation's branch on NodeState for resume. The ParallelWorker replaces its single shared workerCtx with per-iteration sub-contexts derived as <parent>.<wrapped>@<i+1>. The dynamic sub-scheduler gains two opt-in RunNode options — WithUseSubBranch() and WithOverrideBranch(base) — for dynamic-node bodies that want to isolate child activations. * fix(examples): remove unused telemetry import in multipletools (#909) PR #894 (feat(telemetry): add functional test infrastructure) replaced the TelemetryOptions config in examples/tools/multipletools with an os.Setenv call but left the telemetry import behind. This broke 'go build ./...' on v2 with: examples/tools/multipletools/main.go:31:2: "google.golang.org/adk/telemetry" imported and not used Removes the import to unblock the v2 CI. * feat: add SingleTurnTool (#914) * feat: add SingleTurnTool * upd * feat: add TaskAgentTool (#915) feat: add the workflow node to allow the nested workflows (#910) * feat(telemetry): Instrument nodes and workflows (#891) Enables emission of the following spans: - invoke_workflow <workflow_name> - invoke_node <node_name> Additionally: - Adds a functional test for graph-based telemetry emission (workflow chain scenario). * feat(workflow): add WithMaxConcurrency option to cap graph-scheduled node parallelism (#917) Add a workflow-level concurrency cap that limits how many nodes may run concurrently within a single Workflow invocation. When the cap is reached, additional ready-to-run nodes enter NodePending and queue in FIFO order; the scheduler drains the queue as in-flight nodes complete. API: workflow.New(name, edges, workflow.WithMaxConcurrency(n)) - n > 0: cap is enforced - n == 0 (default): unlimited (no behavioural change for existing callers) - n < 0: clamped to 0 (unlimited) The cap applies to all top-level static scheduling paths: initial Start dispatch, fan-out from completed nodes, retry scheduling, and Resume. It explicitly does NOT apply to dynamic sub-nodes invoked via workflow.RunNode from inside a DynamicNode body — gating them would deadlock the parent that awaits the child inline. Same exclusion as adk-python (see _workflow.py:164 comment). * feat: add JSON schema validation support to BaseNode and update constructors accordingly (#911) Implements JSON schema support and default input validation for BaseNode and integrates them across all workflow node types. * feat(workflow): AgentNode synthesizes Event.Output from LLM text (#925) AgentNode now sets Event.Output from concatenated model text on final agent responses, so RunNode(agentNode, ...) returns the agent's reply instead of the zero value. Without this, dynamic workflows that wrap an LlmAgent via NewAgentNode and call RunNode[string] received "" and could not chain on the agent's output. Mirrors adk-python's process_llm_agent_output (workflow/_llm_agent_wrapper.py:251-279) minus the output_schema and output_key side-effects, which have no equivalent here yet. Partial streaming events are not promoted; thoughts are excluded from the concatenation; the synthesis is skipped when Output is already set so callers retain control. * feat(workflow): idempotent RunNode dispatch via WithRunID cache (#921) dynamicSubScheduler gains an in-memory cache keyed by the resolved childPath ("<parentPath>/<name>@<runID>"). A repeated RunNode call with the same WithRunID inside one parent activation returns the cached output without re-running the child, enabling idempotent dispatch for replayed or reorderable children. Failures and HITL interrupts are not cached. Cross-resume idempotency (rehydrating the cache from session events on parent re-run) is out of scope here; a follow-up CL will replicate adk-python's DynamicNodeScheduler._rehydrate_from_events via an OutputFor event annotation. * fix(typeutil): decode validation input into `any` to accept non-object JSON values (#933) ConvertToWithJSONSchema previously decoded the marshalled value into map[string]any before validating against the resolved schema. That worked for object-shaped inputs but failed for scalars, arrays, and booleans with: json: cannot unmarshal string into Go value of type map[string]interface {} * docs: add single_turn and task sub agent examples (#934) * fix(workflow): repair v2 build after rebase onto main (#942) * fix(workflow): repair v2 build after rebase onto main context unification Rebasing v2 onto main pulled in the context-unification change (#935) which removed internal/toolinternal.NewToolContext and added ResumedInput to the agent.InvocationContext interface. Three call sites in v2 workflow code broke as a result: - workflow/tool_node.go: switch toolinternal.NewToolContext to the public agent.NewToolContext (drops the internal import). - internal/workflowinternal/single_turn_tool_test.go: same swap. - recordplugin/record_plugin_test.go: add the now-required ResumedInput stub to MockInvocationContext (mirrors the existing replayplugin mock). go build ./..., go vet ./..., and the affected package tests pass. Note: plugin/TestCallTool fails on this branch but is a pre-existing v2 failure (reproduces on clean origin/v2, passes on main) unrelated to the rebase. * style(workflow): apply gofmt/goimports formatting Pure formatting cleanup surfaced by the linter after the rebase: regroup third-party imports (separate genai/runconfig from adk imports with a blank line) and remove gofmt-flagged field-alignment padding in AgentNode/FunctionNode struct literals. No functional changes. * fix: resolve golangci-lint findings Address the four stable linter findings surfaced by golangci-lint: - cmd/launcher/console/hitl_test.go: explicitly ignore w.Close() error in the stdout-capture test helper (errcheck). - examples/tools/multipletools/main.go: check os.Setenv error and log.Fatalf on failure (errcheck). - workflow/agent_node_test.go: drop the unused ErrorOutput type (unused). - workflow/parallel_worker_test.go: replace single-case select with a plain channel receive (staticcheck S1000). go build ./..., go vet ./..., golangci-lint run, and affected package tests all pass. * fix(typeutil): treat null input as empty object for object schemas #933 switched ConvertToWithJSONSchema to decode the validation input into `any` so scalar/array/bool inputs are accepted. That regressed a nil/absent input (e.g. a tool invoked with no arguments): a nil map marshals to JSON `null`, which an object schema rejects with `has type "null", want "object"`. The previous map[string]any decoding had validated a nil map as an empty object. Restore that behaviour narrowly: when the decoded value is nil and the resolved schema's root type is (or includes) "object", validate an empty object instead. Scalar/array/bool broadening from #933 is preserved; genuine type mismatches and null-against-non-object schemas still fail. Fixes plugin/TestCallTool (tool called with no args) which failed on v2 since #933. Adds typeutil unit tests covering the regression, the preserved scalar/array behaviour, and the negative cases. * refactor: create runner.getOrCreateSession method (#944) * Context unification: merge interfaces ToolContext and CallbackContext into Context. Introduce aliases (#945) * Created wrapper for logging nonsensical calls to ToolContext related methods for CallbackContexts * Renamed callbackContext to commonContext, Added aliases for CallbackContext and ToolContext * Fixes * Added tests for callback_wrapper * Linter fixes * Added README for v2 * Fixes for README-v2 * Added comments * Replaced deprecated tool.Context with agent.ToolContext (#951) Replaced deprecated tool.Context with agent.ToolContext * feat(workflow): add ValidateOutput method to Node interface (#929) Completes the symmetric input/output validation contract on the Node interface, alongside the existing ValidateInput. The scheduler is expected to invoke ValidateOutput on every yielded event whose output is non-nil before forwarding the event to the consumer (wired up in a follow-up). Interface and conformance - Add ValidateOutput(output any) (any, error) to the Node interface. - Add explicit stubs on the two implementations that do not embed BaseNode: startNode and the test-only dummyNode. - Extend the compile-time Node-conformance assertions in base_node_test.go to cover AgentNode, ToolNode, JoinNode, ParallelWorker, and WorkflowNode. Default implementation on BaseNode - BaseNode.ValidateOutput delegates to a shared defaultValidateOutput helper that validates the output against the node's outputSchema field (added in #911) when set, otherwise returns the output unchanged. - The default deliberately performs no type coercion or Content/JSON fallback handling; ToolNode will override ValidateOutput to add its FunctionTool {"result": X} unwrap fallback in a follow-up. * feat(console): add HITL support for tool confirmation prompts (#852) Extends the console launcher's HITL prompt dispatch to handle tool confirmation interrupts (toolconfirmation.FunctionCallName) alongside the workflow input path added in the previous commit. Detection path is unchanged — collectPendingInterrupts already walks events name-agnostically via Event.LongRunningToolIDs. This commit only adds a per-name case to the render and response switches: * renderToolConfirmationPrompt prints the confirmation hint after the standard "Agent -> " banner, or "Confirm <name>?" derived from the original function call as fallback. * toolConfirmationResponseFromUserInput maps yes/y/true/confirm (case-insensitive) to {"confirmed": true}, everything else (including blank lines) to {"confirmed": false}. Without this commit tool confirmation hits the generic fallback which wraps the reply as {"result": <text>} — the transport works (reply routes back by FunctionCall.ID) but the envelope does not match what ctx.ToolConfirmation() expects, so the confirmation is effectively unparseable. * feat(runner): HITL via long-running interrupts with history rehydra… (#960) * feat(workflow): HITL via long-running interrupts with history rehydration Workflow-engine support for human-in-the-loop, unified on a single mechanism — history rehydration — matching adk-python (no persisted run-state event, no PendingRequest field). - scheduler: per-event back-pressure handshake (a non-partial function-response is persisted before the node's flow rebuilds the next model request, fixing a non-deterministic re-issue race); pause a node on accumulated Event.LongRunningToolIDs (RequestInput rides on them); stamp NodeInfo.Path = node name on static node events so rehydration can attribute interrupts (dynamic children fold into their static ancestor). - persistence: ReconstructRunState ports adk-python's _reconstruct_node_states + _infer_node_state — per-node scan (interrupts, resolved user responses, schemas, output), status inference (WAITING / PENDING+ResumedInputs re-entry / COMPLETED+Output handoff), backward-edge predecessor input, and schema validation on the surviving (last-wins) response. - resume: single path over the rehydrated state, gated on the current turn's responses for idempotency; already-run handoff successors are skipped (RunState.completed). - state: NodeState.Interrupts + unexported interruptSchemas; RunState.completed; HasWaiting. No PendingRequest, no persisted run-state blob. - workflowagent: detectResume uses ReconstructRunState and surfaces reconstruction (schema-validation) errors. A node may raise multiple interrupts per activation. workflow and workflowagent suites pass with -race. * fix(session): persist workflow event fields (NodeInfo, RequestedInput, Routes) AppendEvent (in-memory) and the database storage layer dropped Event's workflow fields when persisting: the in-memory copy omitted NodeInfo, RequestedInput and Routes, and the database layer never serialized NodeInfo or RequestedInput. History-based resume attributes interrupts by NodeInfo.Path, so losing it broke HITL resume — a RequestInput workflow (e.g. examples/workflow/hitl_simple) would re-prompt instead of continuing after the reply. Persist all three fields in both backends and add round-trip regression tests for each. * feat(workflow): dynamic-node resume dedup + fix terminal-asker resume Two resume-correctness fixes for dynamic orchestrators and HITL. 1. Cross-resume dedup. A dynamic node body re-runs from the top on resume, so every RunNode before the pause point would re-execute its child. rehydrateCache rebuilds the sub-scheduler's resultByPath from session events (child terminal events carry NodeInfo.Path + Output), so completed children with a stable WithRunID are served from cache. Mirrors adk-python's _rehydrate_from_events / DynamicNodeScheduler. 2. Terminal handoff asker now resumes. Resume only bumped its scheduled counter per scheduled successor, so a single-asker workflow (no successors) wrongly returned ErrNothingToResume. A matched handoff asker now counts as an effective resume itself, gated on answeredThisTurn (from a per-interrupt resolvedCount during rehydration) so a duplicate resume stays an idempotent no-op. * feat(runner): drive LlmAgent through the ADK 2.0 node runtime with HITL (#961) * feat(runner): run LlmAgent through the node runtime with HITL support Detect an LlmAgent root and drive it through the workflow node runtime (the Go equivalent of adk-python's Runner._run_node_async, which is reached for an LlmAgent). Detection and wrapping are automatic and require no user configuration: after findAgentToRun, if the resolved agent is an LlmAgent it is wrapped in a node and run as a single-node workflow (START -> node) named "<AppName>/<agent name>". HITL bridge: adk-go pauses the workflow scheduler only on RequestedInput, whereas an LlmAgent's own HITL (long-running tools / tool confirmation) emits LongRunningToolIDs. The wrapping node bridges the two: when the agent emits a long-running call this turn did not answer, it yields a content-free pause event that sets only RequestedInput (keyed by the real long-running call ID), so the scheduler pauses and persists RunState without injecting a synthetic adk_request_input FunctionCall into the model conversation (which a real model rejects on resume). Also add WithYieldUserMessage() to optionally yield the user message event before node events (parity with adk-python yield_user_message); appendMessageToSession now returns the appended event to support this. * fix(console): dedup pending HITL interrupts across SSE partial events (#959) In SSE streaming mode the same long-running function-call event is emitted multiple times (partial chunks plus the final aggregated event), each carrying the same LongRunningToolIDs. collectPendingInterrupts queued one prompt per duplicate, so the user's reply was consumed against a phantom interrupt instead of resuming the run. Skip partial events and dedup by call ID so each interrupt surfaces exactly once, from the final aggregated event. * feat(workflow): RunNode WithUseAsOutput for child -> parent output delegation (#920) What RunNode gains WithUseAsOutput(), which promotes a dynamic child's output to the parent dynamic node's terminal output. The value returned by the orchestrator body is discarded in favour of the delegated child's. At most one delegating child per parent activation is permitted; a second attempt fails with ErrOutputAlreadyDelegated. When to use Use it for thin-dispatcher orchestrators: the parent picks one child (e.g. by routing logic) and that child's output — including its streamed tokens, when the child is an LlmAgent — is the orchestrator's output. Without delegation the parent would have to wait for the child to finish and re-emit a single string, collapsing streaming. * samples(workflow): routing samples (#979) Examples for routing in workflows * feat: introduce robust JSON-aware input validation in base node for string-encoded types (#946) * feat: introduce robust JSON-aware input validation in base node for string-encoded types * Merge remote-tracking branch 'upstream/v2' into validate_input * feat: setup LLMAgent mode in the runner before run (#983) Depending on the LLMAgent mode the agent which starts invocation should be different. Following python ref: python ref: https://github.com/google/adk-python/blob/62bcdd343c5ea12583f98fa479c265f3b108a50e/src/google/adk/runners.py#L990-L1017 * feat: implement input validation in scheduler and exclude ErrInputValidation from retries (#956) * feat: introduce robust JSON-aware input validation in base node for string-encoded types * feat: implement input validation in scheduler and exclude ErrInputValidation from retries * feat(llm): isolation_scope to filter agent prompt history by scope (#987) Add Event.IsolationScope plus an InvocationContext.IsolationScope() accessor and an exact-match filter in the content builder: an agent includes a session event in its LLM prompt history only when the event's scope equals the agent's scope (empty sees only empty). Mirrors adk-python's isolation_scope, including the include_contents="none" pivot gate so an out-of-scope event cannot start the agent's turn. This is the standalone filter mechanism; scope stamping on append and task-input reconstruction belong to the task-delegation layer that will consume it. * feat(workflow): derive node output from message via NodeInfo.MessageAsOutput (#966) Problem A workflow node's output normally lives in Event.Output. But an LlmAgent node in chat mode emits its answer as message content with no separate Output value — so anything consuming that node's output (a successor on a handoff, or a WithUseAsOutput delegation) would see no output. adk-python solves this with node_info.message_as_output; adk-go had no equivalent. Solution Add NodeInfo.MessageAsOutput. When set and Event.Output is nil, readers derive the node's output from the event's model text. This mirrors adk-python's _track_event_in_context (explicit Output wins; message text is the fallback). * feat(workflow): NodeInfo.OutputFor for delegation-chain output attribution (#969) Add NodeInfo.OutputFor: the node paths an event's Output counts for — the emitting node plus any WithUseAsOutput delegating ancestors. A delegating child emits a single event stamped OutputFor=[child, parent, ...] that flows up the whole chain, so the parent no longer re-emits a duplicate terminal output event (full suppression, matching adk-python's _output_delegated+ output_for). On resume, collectNodeOutputs attributes that one event's output to every static node named in OutputFor, and every output event records its own path as a minimum (mirroring adk-python _enrich_event). * feat(workflow): stamp isolation_scope on delegated node events (#988) Problem PR #987 added the isolation_scope filter (an agent's LLM history is restricted to events whose scope matches its own), but nothing set or stamped a scope — so the filter never engaged. In particular a task-mode LlmAgent run as a node had no way to isolate its multi-turn conversation from peer workflow nodes. Solution Add the producer side. RunNode gains WithIsolationScope (explicit value) and WithIsolationScopeFromNodePath (scope = the child's full node path, the task-mode case). The dynamic scheduler resolves the scope (explicit > node path > inherited), sets it on the child context, and stamps it onto every event the child emits; the agent wrapper stamps the direct (non-delegated) path. Using the full node path keeps scopes unique across nested workflows and reused node names. Mirrors adk-python _compute_isolation_scope_for_node and NodeRunner._enrich_event. Wiring the task-delegation dispatch to pass WithIsolationScopeFromNodePath is left as a TODO on TaskAgentTool. * feat: add ValidateInput override to JoinNode to validate individual predecessor outputs (#985) * refactor: Migrate existing nodes (Function/Tool/start + test nodes) to new Node interface (#984) * feat: add singleTurnNudge in contents processor (#989) Scoped agents (task/single_turn) are invoked by task-delegation FC. This commit handles proper content construction for such agents: making FC args visible to the agent + adding SingleTurnNudge text part for single_turn agent. * feat: reject task mode llmagents as static graph nodes (#995) * feat: add NewFunctionNodeFromState to support binding node inputs and state fields via struct parameters (#990) * feat: exclude task&single_turn from direct transfer (#998) task and single_turn agents are reached via FC delegation (the wrapper sniffs TaskAgentTool / SingleTurnTool function calls), not via transfer_to_agent * feat: add tool deferred response (#997) feat(workflow): Node-to-Node Data Flows (#943) feat(workflow): Support configurable workflows (#936) * feat(workflow): initial naive agent implementation Besides naive implementation, basic struct and interfaces were added. Includes the core workflow implementation, unit tests, and an example to run it in the web UI. * Adjust api of basic componentes after review (#769) * feat: implement workflow routing support with tagged events and conditional graph traversal (#768) * feat: implement workflow routing support with tagged events and conditional graph traversal * refactor: rename event Route field to Routes and update workflow traversal logic * feat: add MultiRoute generic type to support matching multiple route values (#781) * feat: add tool node to workflow agents (#776) * feat: add tool node to worflow agents * update tool node so it's not converting the output to the types, but only validates the schema * check the tool is runnable inside constructor * nit: fix node names in test * feat: implement explicit Default route support in workflow graph traversal (#787) * feat: add EdgeBuilder to simplify workflow graph construction (#795) * feat: add NodeConfig to the workflow nodes (#796) * feat: implement node name validation and update workflow construction to return errors (#797) * feat: add start node validation (#807) * feat: introduce scheduler-based engine with goroutine-per-node execution (#803) * workflow: introduce scheduler-based engine with goroutine-per-node execution Squashes the workflow engine WIP work onto the latest wolo/workflows base. The branch had 8 incremental commits; this single commit captures the cumulative state on top of upstream PR #795 (EdgeBuilder), PR #796 (NodeConfig), and PR #797 (node name validation). Architecture: * Goroutine-per-node execution model: each scheduled node runs in its own goroutine, pushing events into a buffered channel. * Single consumer goroutine (runState.run) drains the channel, applies state-side effects, yields events to the caller, and schedules successors when nodes complete. * Replaces the legacy in-process BFS in Workflow.Run with the new scheduler, removing findNextNodes and the inline event loop. New types and constructors: * BaseNode (and NewBaseNode) for shared Name/Description/Config bookkeeping; FunctionNode and toolNode now embed it. * Graph helpers in graph.go: indexed adjacency for O(1) successor lookup. * RunState (persistable) and runState (in-process scheduler bag) in state.go; node lifecycle map (NodeStatus + per-node accumulators). * NodeContext wraps InvocationContext with a per-node TriggeredBy accessor; agent.InvocationContext gains TriggeredBy() returning "" for non-workflow contexts (mocks updated accordingly). * Scheduler (scheduler.go): runNode goroutine wrapper, eventQueue, cancelAll, and the run consumer loop with sibling cancellation and per-node timeout. Routing and validation: * findSuccessors honours unconditional edges, concrete Routes, and the Default fallback. Silent dead-ends remain intentional per adk-python parity. * Workflow.New now returns (*Workflow, error) — picks up the name validation introduced upstream by PR #797. NodeConfig timeout shape: * Timeout is time.Duration (not *time.Duration), with zero meaning "inherit parent context". Matches net.Dialer.Timeout and the http.Server.*Timeout convention; keeps call sites free of pointer boilerplate. * Adds RerunOnResumeOr / WaitForOutputOr accessor helpers for pointer-typed pointer-typed tri-state fields. * Adds TestDefaultRetryConfig from upstream PR #796. Examples and tests: * examples/workflow/basic uses NodeConfig{RetryConfig: DefaultRetryConfig()} to demo the helper. * 7 New(edges) callers updated for the new (*Workflow, error) signature. Tests verified: go build ./..., go vet ./..., go test -race ./workflow/... ./agent/workflowagent/... all pass. * Init function node in agent workflows (#810) * feat: implement default route validation to prevent multiple default routes per node (#814) * refactor: implement unconditional cycle detection in workflow validation. (#811) * feat: implement workflow validation to reject duplicate edges and remove redundant edge filtering logic (#808) * feat: implement workflow connectivity validation to ensure all nodes are reachable from start (#809) * workflow: add HITL pause primitives (RequestInput, waiting node) (#829) Adds the engine-side scaffolding for human-in-the-loop pauses: a workflow node can now emit a session.RequestInput, and the scheduler will park the node in NodeWaiting and stop scheduling its successors instead of finalising the run. This is the pause half of HITL only; the resume half (Workflow.Resume, the agent.Agent wrapper, schema validation, handoff vs. re-entry modes) is left for follow-up PRs. Type names and field names are aligned with adk-python's RequestInput in src/google/adk/events/request_input.py: interrupt_id / message / response_schema / payload. API additions: * session.RequestInput carries the prompt: InterruptID (stable correlation key), Message (UI text), ResponseSchema (reserved for the future validator), Payload (opaque UI context). * session.Event.RequestedInput field, parallel to Routes; populated by the node, consumed by the scheduler and forwarded to the UI surface unchanged. * workflow.NewRequestInputEvent(ctx, req) constructor, including UUID auto-generation when InterruptID is empty. * workflow.NodeState.PendingRequest, persisted on the per-node state when the waiting branch fires. * workflow.ErrMultipleInputRequests sentinel for the single-request-per-activation invariant. Scheduler changes: * nodeRun gains an inputRequest field with a setInputRequest method that mirrors the existing setRoutingEvent / setOutput pattern. * handleEvent dispatches on ev.RequestedInput exactly parallel to the existing dispatch on ev.Routes. * handleCompletion gains a waiting branch checked AFTER the error/cancel branches: a clean activation that recorded a request transitions to NodeWaiting, persists the request on NodeState, and skips successor scheduling. Failures take precedence so a node that recorded a request and then errored out lands in NodeFailed, not NodeWaiting. * The scheduler.run loop is unchanged: it terminates when the runsByName map empties, which now happens when every live node has either completed or moved into NodeWaiting. Tests: * TestScheduler_HitlNode_PausesAndForwardsRequest pins the happy-path single-waiting-node behaviour. * TestScheduler_HitlNode_AutoGeneratesInterruptID and TestScheduler_HitlNode_PreservesExplicitInterruptID lock in the InterruptID contract. * TestScheduler_HitlNode_MultipleRequestsFails surfaces ErrMultipleInputRequests at completion. * TestScheduler_HitlNode_ErrorAfterRequestFails pins the fail-over-park precedence in handleCompletion. * TestScheduler_HitlNode_ConcurrentBranches_PausesOnlyWhenAllNonRunning exercises a parallel-branch graph: the non-HITL branch finishes normally while the HITL branch parks; the workflow ends only when both reach a terminal state. All existing workflow tests still pass; the suite is race-free under go test -race. * workflow: add HITL resume API with handoff mode and session-state persistence (#847) Builds on the pause-side primitives in the previous PR by adding the resume half of the human-in-the-loop cycle: Workflow.Resume routes a user-supplied response back into a paused workflow, and the existing workflowagent.New wrapper learns to detect and dispatch resume turns automatically. Run state survives across processes by riding in session.State. Engine additions in workflow/: * Workflow.Resume(ctx, state, responses) iter.Seq2 — for each NodeWaiting whose InterruptID matches an entry in responses, validates the payload against PendingRequest.ResponseSchema (when non-nil), consumes PendingRequest before re-scheduling for idempotency, then routes the response to the asker's successors via findSuccessors so handoff reuses the normal routing / fan-out / fan-in path. ErrInvalidResumeResponse surfaces validation failures and leaves the node parked so the caller can retry. * newSchedulerFromState lets Resume seed a scheduler with a loaded RunState rather than a fresh one. * Workflow.SetName / Workflow.Name expose the persistence-namespacing identifier set by workflowagent.New. * Workflow.Run now persists the post-run state at the end of every invocation so a follow-up Resume can pick it up. * WorkflowInputFunctionCallName constant ('adk_request_workflow_input') and the synthesised FunctionCall part on RequestInputEvent — the same shape tool confirmation uses, lets the runner's generic ID-based dispatch route the user's follow-up FunctionResponse back to this agent without runner-side changes. * RunState / NodeState / RequestInput gain JSON tags. Persistence uses session.State.Get/Set with JSON marshalling; binary payloads must be stashed via agent.Artifacts and referenced by URI in Payload (mirrors how the Python Live API surfaces audio). Persistence helpers (workflow/persistence.go): * LoadRunState / SaveRunState exported so workflowagent (and any custom wrapper) can manage RunState lifecycle without taking a dependency on internal package details. * Both helpers handle nil session and nil session.State as no-ops for tests using minimal mocks. Wrapper changes in agent/workflowagent/: * workflowAgent.run dispatches between Workflow.Run and Workflow.Resume by inspecting ctx.UserContent for a FunctionResponse with the magic name. Stateless: every run state lives in session.State. * detectResume + decodeWorkflowInputResponse handle the dual wire format used by the existing tool-confirmation processor (ADK web wraps payloads as {response: <json string>}; other clients inline {payload: ...}). * MockSession in the existing workflow_test gains a State() method returning nil, supported by the persistence helpers' nil guards. Tests (agent/workflowagent/hitl_test.go, ~430 lines): * TestWorkflowAgent_RunThenResume_Handoff — canonical round-trip; pause on RequestInput, resume with payload, handler receives it as input. * TestWorkflowAgent_Resume_RestoresStateFromSession — fresh agent instance built from the same edges resumes successfully off the same session, verifying cross-instance persistence. * TestWorkflowAgent_Resume_Idempotent — two Resume calls with the same payload run the handler exactly once. * TestWorkflowAgent_Resume_NoMatchingResponse — Resume with an InterruptID that no longer matches a waiting node falls through cleanly without blocking on an empty scheduler. * TestWorkflowAgent_Resume_SchemaValidation_{Pass,Fail} — engine validates responses against ResponseSchema; invalid payloads surface ErrInvalidResumeResponse and leave the node parked, so a retry with a corrected payload still succeeds. * TestWorkflowAgent_Resume_FanOut — handoff into a multi-successor asker delivers the response to every successor. * TestWorkflowAgent_FreshTurn_NotMistakenForResume — a fresh user message that happens to share a session with leftover state from a prior workflow does not get misinterpreted as a resume. All existing workflow and workflowagent tests still pass; the suite is race-free under go test -race. * feat: add HITL re-entry resume mode for workflow agents (#832) Builds on #831 (HITL handoff resume) by adding re-entry mode: when a paused node is configured with `NodeConfig.RerunOnResume = true`, `Workflow.Resume` re-activates the asker itself instead of forwarding the response to its successors. The asker observes the response via `ctx.ResumedInput(interruptID)` and decides what to emit. Direct analogue of adk-python's `@node(rerun_on_resume=True)`. Re-entry preserves the asker's original input, so the node can recompute its decision with the same context it had on the first turn — only the user's response arrives separately. Successors fire only when the re-entry activation produces an output, not on the bare resume call. Support of multiple asks while executing one node will be added in a follow-up PR. - [x] `go build ./...` clean - [x] `go test ./workflow/... ./agent/workflowagent/...` — all pass - [x] `go test -race` clean - [x] 4 new tests covering the canonical re-entry round-trip, original-input preservation across re-entry, the no-successor-before-output invariant, and a regression guard pinning that handoff is still the default mode - [x] 1 new test (`TestNodeContext_ResumedInput`) pinning the per-node context-level contract * feat: remove unused TriggeredBy() interface method (#841) agent.InvocationContext.TriggeredBy() was added in the workflow scheduler PR (#803) as 'engine-supplied metadata available to nodes' but no production caller ever materialised: the method is called only by its own round-trip test and by zero workflow nodes, samples, agents, tools, callbacks, or telemetry sites in the repo. The godoc on the interface method is aspirational; the implementation behind it is a dead branch. This change drops the public API surface (one interface method plus six implementations: agent.invocationContext, internal/context.InvocationContext, and the four MockInvocationContext types in workflowagent, workflow, replayplugin, and llminternal). The unrelated NodeState.TriggeredBy field remains: scheduler.go populates it for resume bookkeeping (workflow.Resume reads it back when re-scheduling a paused node), and it is part of the JSON-serialised RunState so dropping it would break forward compatibility for anyone already running the engine. The TestNewNodeContext_TriggeredByRoundTrip test is removed (it exercised the now-deleted method); TestNodeContext_ResumedInput keeps its coverage of the surviving wrapper functionality. * fix: persist resume inputs across re-entry cycles (#844) * workflow: persist resume inputs across re-entry cycles A re-entry-mode node (NodeConfig.RerunOnResume = true) that yields RequestInput more than once across resume cycles previously lost prior responses on each subsequent resume: ctx.ResumedInput exposed only the response to the most recent InterruptID, and asking the node about an earlier ID returned (nil, false) even though the user had already answered it. The fix accumulates response payloads on NodeState across resume cycles. Each Resume call merges the new {InterruptID: response} entry into ns.ResumedInputs; the scheduler hands the full map to the per-node context on every re-entry activation. The node sees every prior response, not only the most recent one. * feat: add retry config implementation (#853) * feat: add generic Output field to session events and persist via storage layer (#863) * feat: workflow - add JoinNode fan-in barrier (#856) Adds a fan-in primitive built on the orchestrator-aggregator model: the scheduler waits until every predecessor of a JoinNode has completed, assembles a map[string]any keyed by predecessor name, and triggers the JoinNode once with that aggregated input. The node itself is a pass-through that emits the input as its output. * feat: implement AgentNode (#840) * fix: align WorkflowInputFunctionCallName with adk-python (#875) The Go workflow runtime synthesises a FunctionCall part on every RequestInput event so the generic FunctionResponse-by-ID dispatch can route the user's follow-up reply back to the agent that issued the request. The synthesised call's Name was 'adk_request_workflow_input'. adk-python uses 'adk_request_input' for the equivalent constant (REQUEST_INPUT_FUNCTION_CALL_NAME in google/adk/workflow/utils/_workflow_hitl_utils.py). The divergence broke cross-runtime HITL workflows: a session recorded by Python (with function_call.name='adk_request_input' in the interrupt event) could not be replayed in Go, because Go-side conformance compare would see 'adk_request_workflow_input' and flag the mismatch. The reverse direction is broken too — a function_response addressed by name to 'adk_request_input' from a Python-authored spec.yaml could not be routed to a Go workflow agent's pending interrupt. This change renames the constant value to 'adk_request_input'. The exported symbol WorkflowInputFunctionCallName is unchanged, so no Go consumer…Configuration menu - View commit details
-
Copy full SHA for 893e4a4 - Browse repository at this point
Copy the full SHA 893e4a4View commit details -
Configuration menu - View commit details
-
Copy full SHA for 78f9c24 - Browse repository at this point
Copy the full SHA 78f9c24View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v1.4.0...v2.0.0