Agent SDK
Event Handlers
Customize agent behavior with event handlers. Intercept messages, add pre-processing, and respond to the tool execution lifecycle.
onMessage Handler
The onMessage handler runs before the LLM processes a user message. It lets you:
- Add custom routing logic
- Validate or transform input
- Short-circuit with a direct response
- Log or track messages
const agent = createAgent({
name: "Support Agent",
systemPrompt: "...",
tools: [tools.webSearch],
onMessage: async (message, context) => {
// message: string — the user's message text
// context: { messages, metadata } — conversation context
// Return null → proceed with normal AI processing
// Return string → skip AI, use this as the response
// Example: Route specific keywords directly
if (message.toLowerCase().includes("pricing")) {
return "Our pricing starts at free! Visit /pricing for details.";
}
// Example: Log all messages
console.log(`User said: ${message}`);
// Proceed with AI processing
return null;
},
});Parameters
| Parameter | Type | Description |
|---|---|---|
| message | string | The user's message text |
| context | object | Contains messages (conversation history) and metadata (arbitrary key-value pairs) |
Return Value
null— Continue with standard AI processingstring— Skip the LLM entirely and return this text as the response
Tool Execution Lifecycle
When the LLM decides to call a tool, the following sequence occurs:
- LLM requests tool call — The model returns a function call with name and arguments
- Arguments validated — The tool's Zod schema validates the arguments
- Tool executes — The tool's
execute()function runs - Result sent back — The tool's return value is sent to the LLM
- Loop continues — Steps 1-4 repeat if the LLM needs more tool calls
- Final response — The LLM generates a text response incorporating all tool results
Each tool call is tracked in the AgentOutput.toolCalls array:
const result = await handler(input);
for (const call of result.toolCalls ?? []) {
console.log(`Tool: ${call.name}`);
console.log(`Args: ${JSON.stringify(call.args)}`);
console.log(`Duration: ${call.durationMs}ms`);
console.log(`Result: ${JSON.stringify(call.result)}`);
}Context Messages
Pass conversation history via context.messages to give the agent memory of the conversation:
const result = await handler({
taskId: "task-001",
userId: "user-001",
userEmail: "user@example.com",
data: { query: "Now show me the top 5" },
integrations: {},
context: {
messages: [
{ role: "user", content: "Analyze Q3 revenue by region" },
{ role: "assistant", content: "Here's the Q3 revenue breakdown..." },
{ role: "user", content: "Now show me the top 5" },
],
},
});The platform automatically includes the last 20 messages as context in chat conversations. In the Code Editor, you can manually control what context to pass.
Integration Flags
The integrations field in AgentInput tells the agent which third-party services the user has connected:
integrations: {
googleDrive: true, // Can access Google Drive files
googleCalendar: true, // Can read/write calendar events
gmail: true, // Can send/read emails
notion: false, // Not connected
slack: false, // Not connected
}Use these flags to conditionally enable tool functionality:
onMessage: async (message, context) => {
if (!input.integrations.googleCalendar) {
return "Please connect your Google Calendar first in Settings.";
}
return null;
},Error Handling
If a tool execution fails, the error is caught and sent back to the LLM, which can:
- Try a different tool
- Retry with different arguments
- Report the error to the user
The AgentOutput.success field indicates whether the overall execution succeeded. Tool-level errors are recorded in individual ToolCall results.
Common Patterns
Input Validation
onMessage: async (message) => {
if (message.length > 10000) {
return "Message too long. Please keep messages under 10,000 characters.";
}
if (message.trim().length === 0) {
return "Please enter a message.";
}
return null;
},Feature Gating
onMessage: async (message, context) => {
const isPremiumQuery = message.includes("detailed analysis");
if (isPremiumQuery && !context.metadata?.isPremium) {
return "Detailed analysis is a premium feature. Upgrade to access.";
}
return null;
},Logging & Analytics
onMessage: async (message) => {
// Track usage without modifying behavior
await trackEvent("agent_message", { length: message.length });
return null; // Always proceed with AI processing
},Next Steps
- AgentBuilder Class — Full builder API reference
- Tools Reference — Every tool and its parameters
- Code Editor — Build agents with these patterns in the editor