Events:¶
Events are the fundamental units of information flow within the Agent Development Kit (ADK). They represent every significant occurrence during an agent's interaction lifecycle, from initial user input to the final response and all the steps in between. Understanding events is crucial because they are the primary way components communicate, state is managed, and control flow is directed.
What Events Are and Why They Matter¶
An Event
in ADK is an immutable record representing a specific point in the agent's execution. It captures user messages, agent replies, requests to use tools (function calls), tool results, state changes, control signals, and errors. Technically, it's an instance of the Event
class from the events directory, which builds upon the basic LlmResponse
structure by adding essential ADK-specific metadata and an actions
payload.
// Conceptual Structure of an Event
// import { LlmResponse } from '../models/LlmResponse';
// import { EventActions } from './EventActions';
// import { FunctionCall, FunctionResponse } from '../models/types';
// export class Event extends LlmResponse {
// // --- LlmResponse fields ---
// content?: any;
// partial?: boolean;
// turnComplete?: boolean;
// errorCode?: string;
// errorMessage?: string;
// interrupted?: boolean;
// customMetadata?: Record<string, any>;
// // ... other response fields ...
// // --- ADK specific additions ---
// author: string; // 'user' or agent name
// invocationId: string; // ID for the whole interaction run
// id: string; // Unique ID for this specific event
// timestamp: number; // Creation time (seconds since epoch)
// actions: EventActions; // Important for side-effects & control
// longRunningToolIds?: Set<string>; // IDs of long-running tool calls
// branch?: string; // Hierarchy path
// // ...
// }
Events are central to ADK's operation for several key reasons:
- Communication: They serve as the standard message format between the user interface, the
Runner
, agents, the LLM, and tools. Everything flows as anEvent
. - Signaling State & Artifact Changes: Events carry instructions for state modifications via
event.actions.stateDelta
and track artifact updates viaevent.actions.artifactDelta
. TheBaseSessionService
(and its implementations) use these signals to ensure persistence. - Control Flow: Specific fields like
event.actions.transferToAgent
orevent.actions.escalate
act as signals that direct the framework, determining which agent runs next or if a loop should terminate. - History & Observability: The sequence of events recorded in
session.events
provides a complete, chronological history of an interaction, invaluable for debugging, auditing, and understanding agent behavior step-by-step.
In essence, the entire process, from a user's query to the agent's final answer, is orchestrated through the generation, interpretation, and processing of Event
objects.
Understanding and Using Events¶
As a developer, you'll primarily interact with the stream of events yielded by the Runner
. Here's how to understand and extract information from them:
Identifying Event Origin and Type¶
Quickly determine what an event represents by checking:
- Who sent it? (
event.author
)'user'
: Indicates input directly from the end-user.'AgentName'
: Indicates output or action from a specific agent (e.g.,'WeatherAgent'
,'SummarizerAgent'
).
- What's the main payload? (
event.content
andevent.content.parts
)- Text: If
event.content.parts[0].text
exists, it's likely a conversational message. - Tool Call Request: Check
event.getFunctionCalls()
. If not empty, the LLM is asking to execute one or more tools. Each item in the list has.name
and.args
. - Tool Result: Check
event.getFunctionResponses()
. If not empty, this event carries the result(s) from tool execution(s). Each item has.name
and.response
(the object returned by the tool). Note: For history structuring, therole
inside thecontent
is often'user'
, but the eventauthor
is typically the agent that requested the tool call.
- Text: If
- Is it streaming output? (
event.partial
)true
: This is an incomplete chunk of text from the LLM; more will follow.false
orundefined
: This part of the content is complete (though the overall turn might not be finished ifturnComplete
is also false).
// TypeScript: Basic event identification
// (async () => {
// for await (const event of runner.runAsync(...)) {
// console.log(`Event from: ${event.author}`);
//
// if (event.content && event.content.parts) {
// if (event.getFunctionCalls().length > 0) {
// console.log(" Type: Tool Call Request");
// } else if (event.getFunctionResponses().length > 0) {
// console.log(" Type: Tool Result");
// } else if (event.content.parts[0].text) {
// if (event.partial) {
// console.log(" Type: Streaming Text Chunk");
// } else {
// console.log(" Type: Complete Text Message");
// }
// } else {
// console.log(" Type: Other Content (e.g., code result)");
// }
// } else if (event.actions &&
// (Object.keys(event.actions.stateDelta).length > 0 ||
// Object.keys(event.actions.artifactDelta).length > 0)) {
// console.log(" Type: State/Artifact Update");
// } else {
// console.log(" Type: Control Signal or Other");
// }
// }
// })();
Extracting Key Information¶
Once you know the event type, access the relevant data:
- Text Content:
text = event.content.parts[0].text
(Always checkevent.content
andevent.content.parts
first). - Function Call Details:
- Function Response Details:
- Identifiers:
event.id
: Unique ID for this specific event instance (an 8-character random string generated byEvent.newId()
).event.invocationId
: ID for the entire user-request-to-final-response cycle this event belongs to. Useful for logging and tracing.
Detecting Actions and Side Effects¶
The event.actions
object signals changes that occurred or should occur. Always check if event.actions
exists before accessing its fields.
- State Changes:
delta = event.actions.stateDelta
gives you an object of{key: value}
pairs that were modified in the session state during the step that produced this event. - Artifact Saves:
artifactChanges = event.actions.artifactDelta
gives you an object of{filename: version}
indicating which artifacts were saved and their new version number. - Control Flow Signals: Check boolean flags or string values:
event.actions.transferToAgent
(string): Control should pass to the named agent.event.actions.escalate
(boolean): A loop should terminate.event.actions.skipSummarization
(boolean): A tool result should not be summarized by the LLM.event.actions.requestedAuthConfigs
(Map): Authentication configurations requested by tools. if (event.actions) { if (event.actions.transferToAgent) { console.log(` Signal: Transfer to ${event.actions.transferToAgent}`); } if (event.actions.escalate) { console.log(" Signal: Escalate (terminate loop)"); } if (event.actions.skipSummarization) { console.log(" Signal: Skip summarization for tool result"); } if (event.actions.requestedAuthConfigs.size > 0) { console.log(" Signal: Authentication requested for tools"); } }
Determining if an Event is a "Final" Response¶
Use the built-in helper method event.isFinalResponse()
to identify events suitable for display as the agent's complete output for a turn.
- Purpose: Filters out intermediate steps (like tool calls, partial streaming text, internal state updates) from the final user-facing message(s).
- When
true
? The implementation in code checks:- If
this.actions.skipSummarization
is true (regardless of event content). - If
this.longRunningToolIds
is set (indicating a tool is running in the background). - OR, all of the following are met:
- No function calls (
getFunctionCalls()
is empty). - No function responses (
getFunctionResponses()
is empty). - Not a partial stream chunk (
partial
is nottrue
). - Doesn't end with a code execution result that might need further processing/display.
- No function calls (
- If
-
Usage: Filter the event stream in your application logic.
// TypeScript: Handling final responses in application // let fullResponseText = ""; // (async () => { // for await (const event of runner.runAsync(...)) { // // Accumulate streaming text if needed... // if (event.partial && event.content && event.content.parts && // event.content.parts[0].text) { // fullResponseText += event.content.parts[0].text; // } // // // Check if it's a final, displayable event // if (event.isFinalResponse()) { // console.log("\n--- Final Output Detected ---"); // if (event.content && event.content.parts && event.content.parts[0].text) { // // If it's the final part of a stream, use accumulated text // const finalText = fullResponseText + (event.content.parts[0].text || ""); // console.log(`Display to user: ${finalText.trim()}`); // fullResponseText = ""; // Reset accumulator // } else if (event.actions.skipSummarization) { // // Handle displaying the raw tool result if needed // const responseData = event.getFunctionResponses()[0].response; // console.log("Display raw tool result:", responseData); // } else if (event.longRunningToolIds) { // console.log("Display message: Tool is running in background..."); // } else { // // Handle other types of final responses if applicable // console.log("Display: Final non-textual response or signal."); // } // } // } // })();
By carefully examining these aspects of an event, you can build robust applications that react appropriately to the rich information flowing through the ADK system.
How Events Flow: Generation and Processing¶
Events are created at different points and processed systematically by the framework. Understanding this flow helps clarify how actions and history are managed.
-
Generation Sources:
- User Input: The
Runner
typically wraps initial user messages or mid-conversation inputs into anEvent
withauthor='user'
. - Agent Logic: Agents (
BaseAgent
,LlmAgent
) explicitly create and yieldnew Event({author: this.name, ...})
objects to communicate responses or signal actions. - LLM Responses: The ADK model integration layer translates raw LLM output (text, function calls, errors) into
Event
objects, authored by the calling agent. - Tool Results: After a tool executes, the framework generates an
Event
containing thefunctionResponse
. Theauthor
is typically the agent that requested the tool, while therole
inside thecontent
is set to'user'
for the LLM history.
- User Input: The
-
Processing Flow:
- Yield: An event is generated and yielded by its source.
- Runner Receives: The main
Runner
executing the agent receives the event. - SessionService Processing (
appendEvent
): TheRunner
sends the event to the configuredSessionService
(an implementation ofBaseSessionService
). This is a critical step:- Processes State Delta: The
BaseSessionService._updateSessionStateFromEvent
method processesevent.actions.stateDelta
and applies it tosession.state
, filtering out keys withtemp:
prefix. - Artifact Processing: Note that while
event.actions.artifactDelta
tracks which artifacts were saved, the actual artifact handling is implementation-specific and not directly handled in the baseappendEvent
method. - Finalizes Metadata: The event ID and timestamp are usually set during construction of the Event object.
- Persists to History: Appends the processed event to the
session.events
list.
- Processes State Delta: The
- External Yield: The
Runner
yields the processed event outwards to the calling application (e.g., the code that invokedrunner.runAsync
).
This flow ensures that state changes and history are consistently recorded alongside the communication content of each event.
Common Event Examples (Illustrative Patterns)¶
Here are concise examples of typical events you might see in the stream:
- User Input:
- Agent Final Text Response: (
isFinalResponse() === true
) - Agent Streaming Text Response: (
isFinalResponse() === false
) - Tool Call Request (by LLM): (
isFinalResponse() === false
) - Tool Result Provided (to LLM): (
isFinalResponse()
depends onskipSummarization
){ "author": "TravelAgent", // Author is agent that requested the call "invocationId": "e-xyz...", "content": { "role": "user", // Role for LLM history "parts": [{"functionResponse": {"name": "findAirports", "response": {"result": ["LHR", "LGW", "STN"]}}}] }, "actions": { "skipSummarization": true // This would make isFinalResponse() return true } }
- State/Artifact Update Only: (
isFinalResponse() === false
) - Agent Transfer Signal: (
isFinalResponse() === false
) - Loop Escalation Signal: (
isFinalResponse() === false
) - Authentication Request: (
isFinalResponse() === false
)
Additional Context and Event Details¶
Beyond the core concepts, here are a few specific details about context and events that are important for certain use cases:
-
ToolContext.functionCallId
(Linking Tool Actions):- When an LLM requests a tool (
FunctionCall
), that request has an ID. TheToolContext
provided to your tool function includes thisfunctionCallId
. - Importance: This ID is crucial for linking actions like authentication (
requestCredential
,getAuthResponse
) back to the specific tool request that initiated them, especially if multiple tools are called in one turn. The framework uses this ID internally for associating auth requests with the right tool call.
- When an LLM requests a tool (
-
How State/Artifact Changes are Recorded:
- When you modify state (
context.state['key'] = value
) or save an artifact (context.saveArtifact(...)
) usingCallbackContext
orToolContext
, these changes aren't immediately written to persistent storage. - Instead, they populate the
stateDelta
andartifactDelta
fields within theEventActions
object. - This
EventActions
object is attached to the next event generated after the change (e.g., the agent's response or a tool result event). - The
BaseSessionService.appendEvent
method reads the state deltas from the incoming event and applies non-temporary ones to the session's state. The handling of artifact deltas depends on the specific SessionService implementation.
- When you modify state (
-
State Scope Prefixes (
app:
,user:
,temp:
):- When managing state via
context.state
, you can optionally use prefixes:app:mySetting
: Suggests state relevant to the entire application (requires a persistentSessionService
).user:userPreference
: Suggests state relevant to the specific user across sessions (requires a persistentSessionService
).temp:intermediateResult
or no prefix: Typically session-specific or temporary state for the current invocation.
- The
BaseSessionService._updateSessionStateFromEvent
method specifically excludes keys with thetemp:
prefix from being persisted to the session state.
- When managing state via
-
Conversation History vs Events:
- The
Session
class maintains bothevents: Event[]
and a privateconversationHistory: Content[]
property. - The
events
array contains the complete record of all events in chronological order, while theconversationHistory
specifically stores semantic content for LLM context. - Use
session.addConversationHistory(content)
to add to the conversation history andsession.getConversationHistory()
to retrieve it.
- The
-
Error Events:
- An
Event
can represent an error. Check theevent.errorCode
andevent.errorMessage
fields (inherited fromLlmResponse
). - Errors might originate from the LLM (e.g., safety filters, resource limits) or potentially be packaged by the framework if a tool fails critically. Check tool
FunctionResponse
content for typical tool-specific errors.
- An
These details provide a more complete picture for advanced use cases involving tool authentication, state persistence scope, and error handling within the event stream.
Best Practices for Working with Events¶
To use events effectively in your ADK applications:
- Clear Authorship: When building custom agents (
BaseAgent
), ensurenew Event({author: this.name, ...})
to correctly attribute agent actions in the history. The framework generally handles authorship correctly for LLM/tool events. - Semantic Content & Actions: Use
event.content
for the core message/data (text, function call/response). Useevent.actions
specifically for signaling side effects (state/artifact deltas) or control flow (transferToAgent
,escalate
,skipSummarization
,requestedAuthConfigs
). - Idempotency Awareness: Understand that the
BaseSessionService
is responsible for applying the state changes signaled inevent.actions.stateDelta
. Implementation-specific sessionService classes may handle artifact deltas differently. - Use
isFinalResponse()
: Rely on this helper method in your application/UI layer to identify complete, user-facing text responses. Avoid manually replicating its logic. - Leverage History: The
session.events
list is your primary debugging tool. Examine the sequence of authors, content, and actions to trace execution and diagnose issues. - Use Metadata: Use
invocationId
to correlate all events within a single user interaction. Useevent.id
to reference specific, unique occurrences.
Treating events as structured messages with clear purposes for their content and actions is key to building, debugging, and managing complex agent behaviors in ADK.