Events
Events are the fundamental building blocks of conversation history in ADK-Rust. Each interaction with an agent—whether it's a user message, an agent response, or a tool execution—is recorded as an event. Events form an immutable log that captures the complete execution trace of an agent session.
Overview
The event system serves several critical purposes:
- Conversation History: Events form the chronological record of all interactions in a session
- State Management: Events carry state changes through the
state_deltafield - Artifact Tracking: Events record artifact operations through the
artifact_deltafield - Agent Coordination: Events enable agent transfers and escalations
- Debugging & Observability: Events provide a complete audit trail of agent behavior
Event Structure
An Event represents a single interaction in a conversation. ADK-Rust uses a unified Event type that embeds LlmResponse, matching the design pattern used in ADK-Go:
pub struct Event {
pub id: String, // Unique event identifier (UUID)
pub timestamp: DateTime<Utc>, // When the event occurred
pub invocation_id: String, // Links related events in a single invocation
pub branch: String, // For future branching support
pub author: String, // Who created this event (user, agent name, tool name)
pub llm_response: LlmResponse, // Contains content and LLM metadata
pub actions: EventActions, // Side effects and metadata
pub long_running_tool_ids: Vec<String>, // IDs of long-running tools
}
The LlmResponse struct contains:
pub struct LlmResponse {
pub content: Option<Content>, // The message content (text, parts, etc.)
pub usage_metadata: Option<UsageMetadata>,
pub finish_reason: Option<FinishReason>,
pub partial: bool, // True for streaming partial responses
pub turn_complete: bool, // True when the turn is complete
pub interrupted: bool, // True if generation was interrupted
pub error_code: Option<String>,
pub error_message: Option<String>,
}
Content is accessed consistently via event.llm_response.content:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
println!("{}", text);
}
}
}
Key Fields
-
id: A unique UUID identifying this specific event. Used for event retrieval and ordering.
-
timestamp: The UTC timestamp when the event was created. Events are ordered chronologically in the session.
-
invocation_id: Groups events that belong to the same agent invocation. When an agent processes a message, all events generated (agent response, tool calls, sub-agent calls) share the same invocation_id.
-
branch: Reserved for future branching functionality. Currently unused but allows for conversation branching in future versions.
-
author: Identifies who created the event:
- User messages: typically "user" or a user identifier
- Agent responses: the agent's name
- Tool executions: the tool's name
- System events: "system"
-
llm_response: Contains the message content and LLM metadata. Access content via
event.llm_response.content. TheContenttype can contain text, multimodal parts (images, audio), or structured data. Some events (like pure state updates) may havecontent: None.
EventActions
The EventActions struct contains metadata and side effects associated with an event:
pub struct EventActions {
pub state_delta: HashMap<String, Value>, // State changes to apply
pub artifact_delta: HashMap<String, i64>, // Artifact version changes
pub skip_summarization: bool, // Skip this event in summaries
pub transfer_to_agent: Option<String>, // Transfer control to another agent
pub escalate: bool, // Escalate to human or supervisor
}
state_delta
The state_delta field contains key-value pairs representing changes to the session state. When an event is appended to a session, these changes are merged into the session's state.
State keys can use prefixes to control scope:
app:key- Application-scoped state (shared across all users)user:key- User-scoped state (shared across all sessions for a user)temp:key- Temporary state (cleared between invocations)- No prefix - Session-scoped state (default)
Example:
let mut actions = EventActions::default();
actions.state_delta.insert("user_name".to_string(), json!("Alice"));
actions.state_delta.insert("temp:current_step".to_string(), json!(3));
artifact_delta
The artifact_delta field tracks changes to artifacts. Keys are artifact names, and values are version numbers. This allows the system to track which artifacts were created or modified during an event.
Example:
actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);
skip_summarization
When true, this event will be excluded from conversation summaries. Useful for internal events, debugging information, or verbose tool outputs that shouldn't be part of the main conversation flow.
transfer_to_agent
When set to an agent name, control is transferred to that agent. This enables multi-agent workflows where one agent can hand off to another. The target agent must be configured as a sub-agent.
Example:
actions.transfer_to_agent = Some("specialist_agent".to_string());
escalate
When true, signals that the conversation should be escalated to a human operator or supervisor agent. The specific escalation behavior depends on your application's implementation.
Conversation History Formation
Events form the conversation history by accumulating in chronological order within a session. When an agent processes a request:
- User Message Event: A new event is created with the user's input
- Agent Processing: The agent receives the conversation history (all previous events)
- Agent Response Event: The agent's response is recorded as a new event
- Tool Execution Events: Each tool call may generate additional events
- State Updates: State deltas from all events are merged into the session state
The conversation history is constructed by:
- Retrieving all events from the session in chronological order
- Converting each event's content into the appropriate format for the LLM
- Including state information from accumulated state deltas
- Filtering out events marked with
skip_summarizationwhen appropriate
Event Flow Example
Session Start
↓
[Event 1] User: "What's the weather in Tokyo?"
↓
[Event 2] Agent: "Let me check that for you."
↓
[Event 3] Tool (weather_api): {"temp": 22, "condition": "sunny"}
↓
[Event 4] Agent: "It's 22°C and sunny in Tokyo."
↓
Session State Updated
Each event builds on the previous ones, creating a complete audit trail of the conversation.
Working with Events
Accessing Events from a Session
use adk_rust::session::{SessionService, GetRequest};
// Retrieve a session with its events
let session = session_service.get(GetRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: session_id.clone(),
num_recent_events: None, // Get all events
after: None,
}).await?;
// Access the events
let events = session.events();
println!("Total events: {}", events.len());
// Iterate through events (note: session events use llm_response.content)
for i in 0..events.len() {
if let Some(event) = events.at(i) {
println!("Event {}: {} by {} at {}",
event.id,
event.llm_response.content.as_ref().map(|_| "has content").unwrap_or("no content"),
event.author,
event.timestamp
);
}
}
Inspecting Event Details
// Get a specific event from session (uses llm_response.content)
if let Some(event) = events.at(0) {
// Check the author
println!("Author: {}", event.author);
// Check content (session events use llm_response.content)
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
println!("Text: {}", text);
}
}
}
// Check for state changes
if !event.actions.state_delta.is_empty() {
println!("State changes:");
for (key, value) in &event.actions.state_delta {
println!(" {} = {}", key, value);
}
}
// Check for agent transfers
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfers to: {}", target);
}
// Check for artifacts
if !event.actions.artifact_delta.is_empty() {
println!("Artifacts modified:");
for (name, version) in &event.actions.artifact_delta {
println!(" {} (v{})", name, version);
}
}
}
Limiting Event History
For long conversations, you may want to retrieve only recent events:
// Get only the last 10 events
let session = session_service.get(GetRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: session_id.clone(),
num_recent_events: Some(10),
after: None,
}).await?;
How Events Flow: Generation and Processing
Understanding how events are created and processed helps clarify how the framework manages actions and history.
Generation Sources
Events are created at different points in the agent execution lifecycle:
- User Input: The Runner wraps user messages into an Event with
author = "user" - Agent Responses: Agents yield Event objects (setting
author = agent.name()) to communicate responses - LLM Output: The model integration layer translates LLM output (text, function calls) into Event objects
- Tool Results: After tool execution, the framework generates an Event containing the tool response
Processing Flow
When an event is generated, it follows this processing path:
- Generation: An event is created and yielded by its source (agent, tool, or user input handler)
- Runner Receives: The Runner executing the agent receives the event
- SessionService Processing: The Runner sends the event to the SessionService, which:
- Applies Deltas: Merges
state_deltainto session state and updates artifact records - Finalizes Metadata: Assigns unique
idif not present, setstimestamp - Persists to History: Appends the event to
session.events
- Applies Deltas: Merges
- Stream Output: The Runner yields the processed event to the calling application
This flow ensures that state changes and history are consistently recorded alongside the communication content.
// Conceptual flow
User Input → Runner → Agent → LLM → Event Generated
↓
SessionService
- Apply state_delta
- Record in history
↓
Event Stream → Application
Identifying Event Types
When processing events from the Runner, you'll want to identify what type of event you're dealing with:
By Author
The author field tells you who created the event:
match event.author.as_str() {
"user" => println!("User input"),
agent_name => println!("Response from agent: {}", agent_name),
}
By Content Type
Check the llm_response.content field to determine the payload type:
if let Some(content) = &event.llm_response.content {
// Check for text content
let has_text = content.parts.iter().any(|part| {
matches!(part, Part::Text { .. })
});
// Check for function calls (tool requests)
let has_function_call = content.parts.iter().any(|part| {
matches!(part, Part::FunctionCall { .. })
});
// Check for function responses (tool results)
let has_function_response = content.parts.iter().any(|part| {
matches!(part, Part::FunctionResponse { .. })
});
if has_text {
println!("Text message");
} else if has_function_call {
println!("Tool call request");
} else if has_function_response {
println!("Tool result");
}
}
By Actions
Check the actions field for control signals and side effects:
// State changes
if !event.actions.state_delta.is_empty() {
println!("Event contains state changes");
}
// Agent transfer
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfer to agent: {}", target);
}
// Escalation signal
if event.actions.escalate {
println!("Escalation requested");
}
// Skip summarization
if event.actions.skip_summarization {
println!("Skip this event in summaries");
}
Working with Event Streams
When running an agent, you receive a stream of events. Here's how to process them effectively:
Processing Events from Runner
use futures::StreamExt;
let mut stream = runner.run(
"user_123".to_string(),
"session_id".to_string(),
user_input,
).await?;
while let Some(event_result) = stream.next().await {
match event_result {
Ok(event) => {
// Process the event
println!("Event from: {}", event.author);
// Extract text content
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{}", text);
}
}
}
// Check for state changes
if !event.actions.state_delta.is_empty() {
println!("\nState updated: {:?}", event.actions.state_delta);
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Extracting Function Calls
When the LLM requests a tool, the event contains function call information:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::FunctionCall { name, args } = part {
println!("Tool requested: {}", name);
println!("Arguments: {}", args);
// Your application might dispatch tool execution here
// based on the tool name and arguments
}
}
}
Extracting Function Responses
After a tool executes, the result is returned in a function response:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::FunctionResponse { function_response, .. } = part {
println!("Tool result from: {}", function_response.name);
println!("Response: {}", function_response.response);
// Process the tool result
// The LLM will use this to continue the conversation
}
}
}
Common Event Patterns
Here are typical event sequences you'll encounter:
Simple Text Exchange
[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")
Tool Usage Flow
[Event 1] author="user", content=Text("What's the weather?")
[Event 2] author="assistant", content=FunctionCall(name="get_weather", args={...})
[Event 3] author="assistant", content=FunctionResponse(name="get_weather", response={...})
[Event 4] author="assistant", content=Text("It's sunny and 72°F")
State Update
[Event 1] author="assistant", content=Text("I've saved your preference")
actions.state_delta={"user_theme": "dark"}
Agent Transfer
[Event 1] author="router", content=Text("Transferring to specialist")
actions.transfer_to_agent=Some("specialist_agent")
[Event 2] author="specialist_agent", content=Text("I can help with that")
Event Metadata and Identifiers
Event ID
Each event has a unique id (UUID) for precise identification:
println!("Event ID: {}", event.id);
Invocation ID
The invocation_id groups all events from a single user request through to the final response:
// All events in one interaction share the same invocation_id
println!("Invocation: {}", event.invocation_id);
// Use this for logging and tracing
log::info!("Processing event {} in invocation {}", event.id, event.invocation_id);
Timestamp
Events are timestamped for chronological ordering:
println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));
Best Practices
-
Event Immutability: Events should never be modified after creation. They form an immutable audit log.
-
State Management: Use
state_deltafor all state changes rather than modifying state directly. This ensures changes are tracked in the event log. -
Meaningful Authors: Set clear, descriptive author names to make event logs easier to understand.
-
Selective Summarization: Use
skip_summarizationfor verbose or internal events that would clutter the conversation history. -
Invocation Grouping: Keep the same
invocation_idfor all events generated during a single agent invocation to maintain logical grouping. -
Artifact Tracking: Always update
artifact_deltawhen creating or modifying artifacts to maintain consistency. -
Stream Processing: Always handle errors when processing event streams. Events can fail due to LLM errors, tool failures, or network issues.
-
Content Checking: Always check if
llm_response.contentisSomebefore accessing parts. Some events (like pure state updates) may not have content. -
Pattern Matching: Use Rust's pattern matching to elegantly handle different event types and content parts.
-
Logging: Use
invocation_idto correlate all events within a single user interaction for debugging and observability.
Related Documentation
- Sessions - Session management and lifecycle
- State Management - Working with session state
- Artifacts - Managing binary data
- Multi-Agent Systems - Agent transfers and coordination
- Callbacks - Intercepting and modifying events
Previous: ← Artifacts | Next: Telemetry →