Hooks
Extend and customize agent behavior with reusable, composable hooks.
Overview
Hooks are a way to encapsulate complex logic in a reusable way. Much like hooks in React or other modern frameworks, Fragola hooks let you encapsulate agent logic and reuse it across agents or multiple projects.
Use hooks to augment your agent's capabilities and customize Fragola SDK to scale with your project needs.
The following presets are implemented using the Fragola API and hook feature:
- mcpClient — connects to a remote or local MCP server and exposes its tools.
- orchestration — enables multiple specialized agents to communicate and work together.
- guardrail — validates user messages against unwanted content and rejects them with a reason.
- fileSystemSave — provides simple conversation persistence on Node.js-like runtimes.
Hooks are similar to events but are designed for reusable, self-contained behaviors. Prefer hooks when you want to package functionality for reuse across agents.
Creating a Hook
A hook is a function that receives an Agent and may register event handlers, add stores, or modify the agent's configuration.
import { Hook } from "@fragola-ai/agentic-sdk-core/hook";
// Simple hook that logs every user message
const loggingHook = Hook((agent) => {
agent.onUserMessage((message, context) => {
console.log("User said:", message.content);
return message;
});
});
// Async hook that fetches configuration before registering tools
const configHook = Hook(async (agent) => {
const config = await fetchConfig();
agent.context.updateTools((tools) => [
...tools,
createToolFromConfig(config)
]);
});The Hook() helper is optional—it simply returns the function you pass in. It exists for consistency and readability.
Use a hook
Attach hooks to an agent using the .use() method. Hooks are initialized in sequence, so each hook can rely on state set by previous hooks.
import { Fragola } from "@fragola-ai/agentic-sdk-core";
import { fileSystemSave, guardrail } from "@fragola-ai/agentic-sdk-core/hook/presets";
const fragola = new Fragola({
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-4o-mini"
});
const agent = fragola.agent({
name: "assistant",
instructions: "You are a helpful assistant.",
description: "General purpose assistant"
})
.use(fileSystemSave("./conversations"))
.use(guardrail([myGuardrail]));The .use() method returns the agent, so you can chain multiple hooks.
Hook Execution Order
Hooks initialize in the order they are attached via .use(). Initialization is serialized—each hook's initialization completes before the next begins. This ensures:
- Tools registered by earlier hooks are available to later hooks.
- Stores added by earlier hooks can be accessed by later hooks.
- Configuration changes are applied in a predictable order.
// Hook A initializes first, Hook B second
agent.use(hookA).use(hookB);
// When agent.userMessage() is called, hooks are already initialized
await agent.userMessage({ content: "Hello" });Avoid long-running async operations in hooks. They block agent initialization and delay the first user message.
Building Custom Hooks
Create custom hooks to encapsulate your own reusable behaviors.
Examples
// 1) Analytics hook that tracks message counts
const analyticsHook = Hook((agent) => {
let userMessages = 0;
let aiMessages = 0;
agent.onUserMessage((message, context) => {
userMessages++;
return message;
});
agent.onAiMessage((message, isPartial, context) => {
if (!isPartial) aiMessages++;
return message;
});
agent.onAfterStateUpdate((context) => {
if (context.state.status === "idle") {
console.log(`Session stats: ${userMessages} user, ${aiMessages} AI`);
}
});
});
// 2) Rate limiting hook
const rateLimitHook = (maxPerMinute: number) => Hook((agent) => {
const timestamps: number[] = [];
agent.onUserMessage((message, context) => {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove old timestamps
while (timestamps.length && timestamps[0] < oneMinuteAgo) {
timestamps.shift();
}
if (timestamps.length >= maxPerMinute) {
throw new Error("Rate limit exceeded");
}
timestamps.push(now);
return message;
});
});
// 3) Context injection hook
const contextInjectionHook = (getContext: () => Promise<string>) => Hook(async (agent) => {
const contextData = await getContext();
const originalInstructions = agent.context.getInstructions() ?? "";
agent.context.setInstructions(
`${originalInstructions}\n\nContext: ${contextData}`
);
});
// Usage
const agent = fragola.agent({...})
.use(analyticsHook)
.use(rateLimitHook(10))
.use(contextInjectionHook(async () => {
return await fetchUserPreferences();
}));Hooks vs Events
| Aspect | Hooks | Events |
|---|---|---|
| Purpose | Reusable, self-contained behaviors | One-off handlers for specific use cases |
| Scope | Package multiple event handlers together | Single event handler |
| Initialization | Async, serialized on attach | Sync, immediate |
| Reusability | Designed for sharing across agents | Typically agent-specific |
| Configuration | Can accept parameters (factory pattern) | Inline configuration |
When to use hooks:
- Building reusable middleware (logging, persistence, rate limiting).
- Integrating with external systems (MCP, databases).
- Packaging complex behaviors for distribution.
When to use events:
- One-off customizations for a specific agent.
- Quick prototyping and experimentation.
- Simple transformations that don't need reuse.