feat(adk): add AfterAgent hook to TypedChatModelAgentMiddleware#1002
Merged
shentongmartin merged 3 commits intoalpha/09from May 6, 2026
Merged
feat(adk): add AfterAgent hook to TypedChatModelAgentMiddleware#1002shentongmartin merged 3 commits intoalpha/09from
shentongmartin merged 3 commits intoalpha/09from
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## alpha/09 #1002 +/- ##
===========================================
Coverage ? 82.92%
===========================================
Files ? 162
Lines ? 22092
Branches ? 0
===========================================
Hits ? 18319
Misses ? 2538
Partials ? 1235 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
shentongmartin
added a commit
that referenced
this pull request
Apr 29, 2026
Cherry-pick fixes from PR #1002: 1. cancel_edge_test: replace time.Sleep(500ms) with channel-based synchronization. The tool now pauses after emitting a few chunks and signals a channel; the test waits on that channel before cancelling, guaranteeing the tool is mid-stream. 2. turn_loop: add drained flag to preemptSignal. After drainAll, any subsequent requestPreempt calls close the ack immediately instead of appending to pendingAckList, preventing orphaned ack channels that cause Push callers to hang. 3. turn_loop_test: stop the loop concurrently with wg.Wait in ConcurrentPreemptsDuringTurn, since the run loop may be blocked on buffer.Receive after processing all preempts. Change-Id: I489fe86315f86ed15b4a29131146139a1eb82967
shentongmartin
added a commit
that referenced
this pull request
Apr 29, 2026
Cherry-pick fixes from PR #1002: 1. cancel_edge_test: replace time.Sleep(500ms) with channel-based synchronization. The tool now pauses after emitting a few chunks and signals a channel; the test waits on that channel before cancelling, guaranteeing the tool is mid-stream. 2. turn_loop: add drained flag to preemptSignal. After drainAll, any subsequent requestPreempt calls close the ack immediately instead of appending to pendingAckList, preventing orphaned ack channels that cause Push callers to hang. 3. turn_loop_test: stop the loop concurrently with wg.Wait in ConcurrentPreemptsDuringTurn, since the run loop may be blocked on buffer.Receive after processing all preempts. Change-Id: I489fe86315f86ed15b4a29131146139a1eb82967
Add AfterAgent lifecycle hook that fires when the agent reaches a successful terminal state (final answer or return-directly tool result). Implemented as a graph node before compose.END so compose.ProcessState can read the full final state including tool results. Change-Id: I5ec5ec7e8b61587b5e7d5200421635f06a88428e
Add AgenticFinalAnswer subtest to TestAfterAgent to cover the agentic model path (buildAgenticReActRunFunc + newAgenticReact afterAgentNode). Fix TestWithCancel_CancelImmediate_StreamableToolAborted: replace time.Sleep with channel-based synchronization to eliminate timing race. Change-Id: Ibcf96c14ac47949cf705dbc3a5cbfe95bca59a8c
…shutdown When drainAll runs during TurnLoop cleanup, a concurrent Push caller that has already called holdRunLoop but not yet requestPreempt can add an ack channel to pendingAckList after drainAll clears it. This orphaned ack is never closed, causing the Push caller to hang. Add a `drained` flag to preemptSignal. drainAll sets it, and requestPreempt checks it — if drained, the ack is closed immediately instead of being appended to pendingAckList. Also fix the test to stop the loop concurrently with wg.Wait, since the run loop may be blocked on buffer.Receive after processing all preempts, and Stop is needed to unblock it and trigger drainAll. Change-Id: I8d58b0e1478d2ae6f89e06d420b9252e43aa9cd6
66908f4 to
ba13cca
Compare
N3kox
approved these changes
Apr 30, 2026
shentongmartin
added a commit
that referenced
this pull request
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add an
AfterAgentlifecycle hook toTypedChatModelAgentMiddlewarethat fires when the agent reaches a successful terminal state — either a final answer (model response with no tool calls) or a return-directly tool result.Motivation
Users need a way to perform post-run logic (e.g., persisting conversation history, emitting metrics, cleaning up resources) after the agent completes successfully. Existing middleware hooks only cover pre-run and per-iteration stages; there was no hook for the terminal state.
Design Decisions
Graph Node Approach
AfterAgent is implemented as a graph node (
afterAgentNode_) inserted beforecompose.END, rather than being called externally after graph execution. This ensures:compose.ProcessStateis available, giving access to the complete final state including tool results from return-directly paths.Success-Only Semantics
AfterAgent fires only on successful terminal paths. It is NOT called on errors (e.g.,
ErrExceedMaxIterations, context cancellation, model errors). This avoids the design conflict of combining fail-fast error propagation with an error-input parameter.Fail-Fast Error Propagation
Consistent with all other middleware hooks (
BeforeAgent,BeforeModelRewriteState,AfterModelRewriteState): if any handler'sAfterAgentreturns an error, subsequent handlers are skipped and the error propagates to the event stream.Changes
adk/handler.goAfterAgent(ctx, *TypedChatModelAgentState[M]) (context.Context, error)toTypedChatModelAgentMiddlewareinterfaceTypedBaseChatModelAgentMiddlewareadk/chatmodel.goapplyAfterAgentmethod that reads state viacompose.ProcessStateand calls handlers in order with fail-fastafterAgentFuncclosures in all three run-func builders: no-tools chain, message ReAct graph, agentic ReAct graphadk/react.goafterAgentFuncfield totypedReactConfigafterAgentNode_in bothnewReactandnewAgenticReact: conditionally inserted beforecompose.END, with both terminal paths (final answer branch, return-directly converter) routing through itadk/handler_test.goTestAfterAgentwith 8 subtests covering: FinalAnswer, ReturnDirectly, NotCalledOnModelError, NotCalledOnMaxIterations, ErrorStopsRun, ContextPropagation, NoToolsPath, FailFastTestCustomHandlerto verify AfterAgent fires during normal agent runsInterface
Test Coverage
All 8 test cases pass with
-race: