Event System Overview
Fragola is fundamentally event-driven. Instead of relying on a monolithic execution graph, the agent progresses through a well-defined lifecycle during each execution turn. You can hook into specific stages of this lifecycle to mutate data, pause execution, or handle side effects.
Event Pipeline
When you call agent.userMessage() or agent.step(), the following general sequence of events occurs:
- User Message Phase: If a message was provided,
onUserMessagetriggers. - Step Loop Starts:
onBeforeStepfires. The agent checks if it has reached itsmaxSteplimit. - Model Invocation:
onBeforeModelInvocationfires, allowing you to modify the API request.onModelInvocationstreams the chunked response as it arrives.onAiMessagefires when the model finishes its response.onAfterModelInvocationfires with the final message and usage stats.
- Tool Resolution (if requested by the model):
onBeforeToolCallfires for each requested tool.- The Tool Handler executes (if it's not marked as dynamic).
onToolCallfires, allowing you to mutate the tool's result before it is saved.onAfterToolCallfires for post-execution cleanup or logging.
- Step Loop Ends:
onAfterStepfires. If tool calls occurred, the loop goes back to Step 2. Otherwise, the agent returns to anidlestate.
Registering Event Handlers
You register event listeners directly on the agent instance using the on* methods.
Every event listener receives a payload containing the relevant data for that stage, as well as the AgentContext object.
// Register an inline method
agent.onUserMessage(({ message, context }) => {
// Mutate the message before it gets processed
message.content = `[From Web]: ${message.content}`;
return message;
});You can also use the generic on() method if you prefer strings:
agent.on("userMessage", ({ message }) => { ... });Removing Listeners
When you register a listener, it returns an object containing a remove() method. This is useful for temporary listeners or cleanup inside custom hooks.
const event = agent.onAiMessage(() => { console.log("AI Spoke!") });
// Later...
event.remove();Flow Control: stop() and stopSync()
Events allow you to gracefully exit the agent's execution loop if certain conditions are met.
context.stop(): Signals the agent to stop at the end of the current asynchronous phase. It finishes writing the current tool result or message, then halts the step loop and returns to idle.context.stopSync(): Immediately throws an internal exception, aborting the execution turn entirely without waiting.
agent.onBeforeModelInvocation(({ context, config }) => {
const budget = context.getStore("token-budget")?.get();
if (budget.remaining <= 0) {
context.stopSync(); // Immediately abort the generation
}
return config;
});