Available hooks
| Hook | Timing | Use Cases |
|---|---|---|
on_start | Before agent starts | Input validation, logging |
on_end | After agent completes | Cleanup, metrics |
on_agent_step_start | Before each LLM call | Rate limiting |
on_agent_step_end | After each LLM call | Logging responses |
on_tool_start | Before tool execution | Modify inputs |
on_tool_end | After tool execution | Modify outputs |
Define hooks
from polos import hook, WorkflowContext, HookContext, HookResult
@hook
def log_start(ctx: WorkflowContext, hook_ctx: HookContext) -> HookResult:
"""Log when agent execution starts."""
print(f"Agent started - workflow: {hook_ctx.workflow_id}")
return HookResult.continue_with()
@hook
def log_end(ctx: WorkflowContext, hook_ctx: HookContext) -> HookResult:
"""Log when agent execution ends."""
print(f"Agent completed - workflow: {hook_ctx.workflow_id}")
return HookResult.continue_with()
@hook
def validate_input(ctx: WorkflowContext, hook_ctx: HookContext) -> HookResult:
"""Validate input before agent execution starts."""
payload = hook_ctx.current_payload or {}
prompt = payload.get("prompt", "")
if not prompt or not prompt.strip():
return HookResult.fail("Empty prompt not allowed")
if len(prompt) > 10000:
return HookResult.fail("Prompt too long (max 10000 characters)")
return HookResult.continue_with()
import { defineHook, HookResult } from '@polos/sdk';
import type { HookResultType } from '@polos/sdk';
export const logStart = defineHook(
async (ctx, _hookCtx): Promise<HookResultType> => {
console.log(`Agent started - workflow: ${ctx.workflowId}`);
return HookResult.continue();
},
{ name: 'log_start' },
);
export const logEnd = defineHook(
async (ctx, _hookCtx): Promise<HookResultType> => {
console.log(`Agent completed - workflow: ${ctx.workflowId}`);
return HookResult.continue();
},
{ name: 'log_end' },
);
export const validateInput = defineHook(
async (_ctx, hookCtx): Promise<HookResultType> => {
const payload = hookCtx.currentPayload as Record<string, unknown> | undefined;
const input = String(payload?.['input'] ?? '');
if (!input || !input.trim()) {
return HookResult.fail('Empty input not allowed');
}
if (input.length > 10000) {
return HookResult.fail('Input too long (max 10000 characters)');
}
return HookResult.continue();
},
{ name: 'validate_input' },
);
Attach hooks to agent
from polos import Agent, max_steps, MaxStepsConfig
logged_agent = Agent(
id="logged_agent",
provider="openai",
model="gpt-4o-mini",
system_prompt="You are a helpful assistant.",
tools=[search, calculate],
on_start=[validate_input, log_start],
on_end=[log_end],
on_agent_step_start=[log_step_start],
on_agent_step_end=[log_step_end],
on_tool_start=[log_tool_start],
on_tool_end=[log_tool_end],
stop_conditions=[
max_steps(MaxStepsConfig(limit=5)),
],
)
import { defineAgent, maxSteps } from '@polos/sdk';
import { openai } from '@ai-sdk/openai';
import { search, calculate } from './tools.js';
import {
logStart, logEnd, logStepStart, logStepEnd,
logToolStart, logToolEnd, validateInput,
} from './hooks.js';
const loggedAgent = defineAgent({
id: 'logged_agent',
model: openai('gpt-4o-mini'),
systemPrompt: 'You are a helpful assistant.',
tools: [search, calculate],
onStart: [validateInput, logStart],
onEnd: [logEnd],
onAgentStepStart: [logStepStart],
onAgentStepEnd: [logStepEnd],
onToolStart: [logToolStart],
onToolEnd: [logToolEnd],
stopConditions: [maxSteps({ count: 5 })],
});
Run it
git clone https://github.com/polos-dev/polos.git
cd polos/python-examples/07-lifecycle-hooks
cp .env.example .env # Add your POLOS_PROJECT_ID and API key
uv sync
python main.py
git clone https://github.com/polos-dev/polos.git
cd polos/typescript-examples/07-lifecycle-hooks
cp .env.example .env # Add your POLOS_PROJECT_ID and API key
npm install
npx tsx main.ts