Lifecycle Hooks
Lifecycle hooks let you run custom logic at key points during an agent's execution. The most commonly used hook is beforeExecute, which fires before each execution step and lets you modify the message parts the agent sees.
beforeExecute
The beforeExecute hook fires before each agent execution step. It receives the current message and can modify it — adding parts, injecting context, or transforming existing data.
This is the primary way to inject dynamic application state into an agent's context at runtime.
How It Works
- Your agent starts a run
- Before each execution step, the server fires
beforeExecute - If you have an inline hook registered, it sends the request to your client
- Your handler returns a
HookMutationwithdynamic_values - These values are merged into the agent's context as template variables
import type { HookHandler } from '@distri/core';
const beforeExecute: HookHandler = async (req) => {
// req.message contains the current message parts
// req.context has agent_id, thread_id, task_id, run_id
const appState = await getApplicationState();
return {
dynamic_values: {
current_user: appState.userName,
active_items: appState.items.length,
last_updated: appState.updatedAt,
},
};
};
The dynamic_values are injected into the agent's prompt and become available as Handlebars template variables:
# CURRENT CONTEXT
User: {{current_user}}
Active items: {{active_items}}
Last updated: {{last_updated}}
Configuring Hooks in Agent Definitions
Reference hooks in your agent's TOML frontmatter:
---
name = "my_agent"
description = "Agent with lifecycle hooks"
hooks = ["my_hook"]
---
The hook name maps to a handler on the client side that responds to inline_hook_requested events during the agent's streaming execution.
Parts Metadata (save: false)
When tool handlers return DistriPart[], you can attach __metadata: { save: false } to any part. This tells Distri to include the part in the current agent context but not persist it to conversation history.
This is critical for keeping context lean — screenshots, DOM snapshots, and other large ephemeral data should use save: false to avoid bloating the thread.
Pattern
import type { DistriPart } from '@distri/core';
// Return parts with __metadata to prevent saving
handler: async (input): Promise<DistriPart[]> => {
const screenshot = await captureScreenshot();
const pageState = await extractPageState();
return [
{
part_type: 'text',
data: `=== BROWSER STATE ===\nURL: ${pageState.url}`,
__metadata: { save: false },
},
{
part_type: 'image',
data: screenshot,
__metadata: { save: false },
},
{
part_type: 'text',
data: `=== PAGE STATE ===\n${pageState.stateText}`,
__metadata: { save: false },
},
];
}
Building Observation Parts
A common pattern is a helper that builds multiple observation parts and marks them all as transient:
function buildObservationParts(observation: {
url: string;
screenshot?: string;
stateText?: string;
}): DistriPart[] {
const parts: DistriPart[] = [];
// URL and browser state
parts.push({
part_type: 'text',
data: `=== BROWSER STATE ===\nURL: ${observation.url}`,
__metadata: { save: false },
});
// Screenshot (large, never save)
if (observation.screenshot) {
parts.push({
part_type: 'image',
data: {
bytes: observation.screenshot,
mime_type: 'image/png',
name: 'viewport.png',
},
__metadata: { save: false },
});
}
// Page state text
if (observation.stateText) {
parts.push({
part_type: 'text',
data: `=== PAGE STATE ===\n${observation.stateText}`,
__metadata: { save: false },
});
}
return parts;
}
When to Use save: false
| Use Case | Why |
|---|---|
| Browser screenshots | Large binary data, only relevant for current step |
| DOM snapshots | Changes every step, stale data wastes context |
| Intermediate observations | Agent needs them now but not in history |
| Temporary tool output | Verbose data that should not clutter the thread |
Without save: false, every tool response is persisted to the thread. For multi-step agents (e.g., browser automation with 30+ steps), this quickly fills the context window. Mark ephemeral parts as save: false to keep only the data the agent needs right now.
Hook Kinds Reference
Distri provides four hook points. beforeExecute is the most commonly used:
| Hook | When It Fires | Use Case |
|---|---|---|
beforeExecute | Before each execution step | Inject fresh context, modify parts |
onPlanStart | Agent begins planning | Log start, inject planning context |
onPlanEnd | Planning completes | Review plan before execution |
onStepEnd | After each step completes | Log progress, update UI |
References
- Agent Definition — Configure hooks in TOML frontmatter
- In-Product Tools — Tools that return parts with metadata
- Using Sessions — Persistent state across agent turns