Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: google/adk-go
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.4.0
Choose a base ref
...
head repository: google/adk-go
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.0.0
Choose a head ref
  • 16 commits
  • 578 files changed
  • 14 contributors

Commits on Jun 1, 2026

  1. Configuration menu
    Copy the full SHA
    f771a33 View commit details
    Browse the repository at this point in the history

Commits on Jun 2, 2026

  1. Context unification: switch all to not deprecated (#935)

    * Replaced tool.Context with agent.ToolContext
    
    * fixed runnableTool
    kdroste-google authored Jun 2, 2026
    Configuration menu
    Copy the full SHA
    81a63d8 View commit details
    Browse the repository at this point in the history

Commits on Jun 10, 2026

  1. 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.
    karolpiotrowicz authored Jun 10, 2026
    Configuration menu
    Copy the full SHA
    a9a99a7 View commit details
    Browse the repository at this point in the history
  2. 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.
    karolpiotrowicz authored Jun 10, 2026
    Configuration menu
    Copy the full SHA
    38b11f7 View commit details
    Browse the repository at this point in the history

Commits on Jun 15, 2026

  1. 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).
    DABH authored Jun 15, 2026
    Configuration menu
    Copy the full SHA
    c6f168f View commit details
    Browse the repository at this point in the history

Commits on Jun 16, 2026

  1. 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.
    wolo-lab authored Jun 16, 2026
    Configuration menu
    Copy the full SHA
    9f6080f View commit details
    Browse the repository at this point in the history
  2. 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>
    dietb and kdroste-google authored Jun 16, 2026
    Configuration menu
    Copy the full SHA
    90091e2 View commit details
    Browse the repository at this point in the history
  3. 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.
    karolpiotrowicz authored Jun 16, 2026
    Configuration menu
    Copy the full SHA
    bfab1bd View commit details
    Browse the repository at this point in the history

Commits on Jun 18, 2026

  1. 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.
    jankrynauw authored Jun 18, 2026
    Configuration menu
    Copy the full SHA
    fea8596 View commit details
    Browse the repository at this point in the history
  2. Get rid of context.Background (#1062)

    * Got rid of context.Background
    
    * Linter fixes
    kdroste-google authored Jun 18, 2026
    Configuration menu
    Copy the full SHA
    b304aaf View commit details
    Browse the repository at this point in the history

Commits on Jun 19, 2026

  1. 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>
    3 people authored Jun 19, 2026
    Configuration menu
    Copy the full SHA
    ca2e3d5 View commit details
    Browse the repository at this point in the history
  2. Main linter fixes (#1068)

    * session test suite fix
    
    * Linter fixes
    kdroste-google authored Jun 19, 2026
    Configuration menu
    Copy the full SHA
    44e3d67 View commit details
    Browse the repository at this point in the history

Commits on Jun 24, 2026

  1. Configuration menu
    Copy the full SHA
    5350266 View commit details
    Browse the repository at this point in the history

Commits on Jun 30, 2026

  1. 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.
    nuthalapativarun authored Jun 30, 2026
    Configuration menu
    Copy the full SHA
    caf798a View commit details
    Browse the repository at this point in the history
  2. V2 release (#1109)

    * 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…
    7 people authored Jun 30, 2026
    Configuration menu
    Copy the full SHA
    893e4a4 View commit details
    Browse the repository at this point in the history
  3. add docs (#1111)

    wolo-lab authored Jun 30, 2026
    Configuration menu
    Copy the full SHA
    78f9c24 View commit details
    Browse the repository at this point in the history
Loading