Skip to content

Function responses may not be persisted before tool confirmation pauses execution #759

@gustafvh

Description

@gustafvh

Describe the bug

In internal/llminternal/base_flow.go, runOneStep currently yields a generated adk_request_confirmation event before yielding the merged function-response event returned from tool execution:

toolConfirmationEvent := generateRequestConfirmationEvent(ctx, modelResponseEvent, ev)
if toolConfirmationEvent != nil {
    if !yield(toolConfirmationEvent, nil) {
        return
    }
}

if !yield(ev, nil) {
    return
}

This ordering can lose function-response history for consumers that intentionally stop iterating when they receive a tool confirmation request so they can pause execution and ask a user to approve or reject the pending action.

In that flow, the confirmation event is yielded and persisted, but the function-response event (ev) may never be yielded. The session history is then incomplete when execution resumes.

To Reproduce

  1. Create an agent with at least one regular tool and one tool that requests confirmation.
  2. Run the agent through a consumer that pauses as soon as it receives an adk_request_confirmation event.
  3. Let the confirmed tool path generate both:
    • a normal function-response event for already executed tools
    • an adk_request_confirmation event for the pending tool approval
  4. Stop consuming the iterator after the confirmation event, as a UI or human-in-the-loop integration commonly would.
  5. Inspect the session history before resuming.

Expected behavior

All completed tool function responses should be yielded and persisted before the confirmation request is yielded. A consumer that pauses on the confirmation boundary should still have complete function-response history for work that already happened.

Observed behavior

The confirmation request is yielded first. If the consumer pauses immediately, the merged function-response event is not yielded and may not be persisted to the session.

Proposed fix

Yield the merged function-response event before yielding the generated adk_request_confirmation event:

toolConfirmationEvent := generateRequestConfirmationEvent(ctx, modelResponseEvent, ev)

if !yield(ev, nil) {
    return
}

if toolConfirmationEvent != nil {
    if !yield(toolConfirmationEvent, nil) {
        return
    }
}

This preserves completed tool results before entering the human-confirmation pause.

Testing

Add a runner-level regression test where the consumer stops after receiving adk_request_confirmation. The test should assert that the function-response event is yielded before the confirmation request so it can be persisted even when the consumer pauses at the confirmation boundary.

Alignment with adk-python

This is specific to ADK Go's runner iterator/yield ordering. The cross-language invariant is that completed tool results should be persisted before execution pauses for human confirmation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions