This page covers in-process lifecycle hooks (SETUP, SESSION_START, BEFORE_AGENT, etc.) that fire inside a running agent. For HTTP inbound triggers that start agent runs from external services via
POST /hooks/<path>, see Gateway Inbound Hooks.How It Works
Lifecycle Events
Quick Start
Core Events
Tool Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
BEFORE_TOOL | Before tool execution | BeforeToolInput | Security checks, logging |
AFTER_TOOL | After tool execution | AfterToolInput | Result validation, logging |
Tool Definition Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
BEFORE_TOOL_DEFINITIONS | After advertised tool list is assembled, before sent to LLM | BeforeToolDefinitionsInput | Redact tools per request, append usage notes, constrain schema per model |
BEFORE_TOOL_DEFINITIONS lets you shape the tool list the LLM actually sees — without changing the agent’s permanent tool registration. Mutate tool_definitions in place; the runtime only adopts in-place mutations.
BeforeToolDefinitionsInput Fields
| Field | Type | Default | Description |
|---|---|---|---|
tool_definitions | List[Dict[str, Any]] | [] | The fully-assembled OpenAI-style tool definition list about to be sent to the LLM. Mutate in place to filter or rewrite. |
model | str | "" | Model id the call will be sent to (e.g. "gpt-4o"). Use for per-model filtering. |
session_id, cwd, event_name, timestamp, agent_name | — | — | Standard HookInput fields. |
model field:
Agent Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
BEFORE_AGENT | Before agent runs | BeforeAgentInput | Setup, initialization |
AFTER_AGENT | After agent completes | AfterAgentInput | Cleanup, reporting |
LLM Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
BEFORE_LLM | Before LLM API call | BeforeLLMInput | Request modification |
AFTER_LLM | After LLM response | AfterLLMInput | Response validation |
Session Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
SESSION_START | When session starts | SessionStartInput | Session initialization |
SESSION_END | When session ends | SessionEndInput | Session cleanup |
Bot runtime semantics: In the bot runtime (
BotOS), SESSION_START fires exactly once per user session lifetime — on the first message, not on every message. SESSION_END fires when the user sends /new, when a policy auto-reset triggers, when stale sessions are reaped, or on reset_all. The reason field on SessionEndInput is one of clear, policy, stale, or clear_all.Error Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
ON_ERROR | When error occurs | OnErrorInput | Error handling |
ON_RETRY | Before retry attempt | OnRetryInput | Retry logic |
Async vs Sync
ON_RETRY fires on both sync and async retry paths. If you register the handler as a regular def, it is dispatched in a thread executor on the async path; if you register an async def, it is awaited directly. Either form is safe on both paths.
Released in PraisonAI #2386 — earlier versions skipped this event on the async path.
OnRetryInput Fields
TheOnRetryInput event includes both new tool-specific fields and legacy fields for backward compatibility:
New fields (recommended):
tool_name: Name of the failing toolattempt: Current attempt number (1-based)max_attempts: Maximum attempts configureddelay_ms: Delay before this retry in millisecondserror_type: Classified error type (timeout,rate_limit,connection_error,unknown)error: Original exception object
Agent(retry=...) — see Agent Retry):
delay_seconds: Seconds the agent will sleep before the next attemptattempt: Current attempt number (0-based)operation:"llm_request"(sync) or"async_llm_request"(async)error_message: String representation of the failingLLMErrormax_retries: ConfiguredRetryBackoffConfig.max_retriesretry_count: Same asattempt + 1(1-based legacy alias)
retry_count: Same asattemptmax_retries: Same asmax_attemptserror_message: String representation oferror
Agent-Level Error Callbacks
In addition to hook events, agents support a directon_error callback for LLM failures:
Hook Events vs Agent Callbacks
| Approach | Scope | Return Value | When Called |
|---|---|---|---|
| HookEvent.ON_ERROR | Global, all agents | HookResult | Any hook system error |
| agent.on_error | Single agent | None | LLM chat completion errors |
Extended Events
User Interaction Events
| Event | Trigger | Use Case |
|---|---|---|
USER_PROMPT_SUBMIT | User submits prompt | Input validation, logging |
NOTIFICATION | Notification sent | Alert routing |
Subagent Events
| Event | Trigger | Use Case |
|---|---|---|
SUBAGENT_STOP | Subagent completes | Result handling |
System Events
| Event | Trigger | Use Case |
|---|---|---|
SETUP | Initialization/maintenance | Config loading |
BEFORE_COMPACTION | Before context compaction | Pre-compaction hooks |
AFTER_COMPACTION | After context compaction | Post-compaction validation |
Message Events
| Event | Trigger | Input Type | Use Case |
|---|---|---|---|
MESSAGE_RECEIVED | Inbound message from a bot channel, before agent dispatch | MessageReceivedInput | Inbound gate — drop (deny), redact/rewrite content, authorise sender, rate-limit |
MESSAGE_SENDING | Before message sent | MessageSendingInput | Outbound gate — cancel or rewrite outbound text |
MESSAGE_SENT | After message sent | MessageSentInput | Confirmation |
MESSAGE_RECEIVED is a first-class control point: hooks can drop an inbound message so the agent never runs, or rewrite the message content before the agent (or memory) sees it. This is symmetric with MESSAGE_SENDING on the outbound side.
The hook now returns a decision that every platform adapter (Telegram, Slack, Discord, WhatsApp, Email, AgentMail) honours:
HookResult.deny(reason=...)→ the message is dropped; agent dispatch is skipped entirely.HookResult(modified_input={"content": "..."})→ the inbound message content is rewritten before dispatch.NoneorHookResult.allow()→ message passes through unchanged (default).- Hook errors are non-fatal — a raising hook logs the error and lets the message through.
MESSAGE_RECEIVED and MESSAGE_SENDING are the two message-lifecycle events that do gate — deny drops the message entirely, modified_input["content"] rewrites it. The gate is safe from both sync and async adapters (Telegram, Slack, Discord, WhatsApp, Email, AgentMail) — no async def required in your hook. See PraisonAI #2589.Drop / block an inbound message
Redact PII before the agent sees it
Authorise sender against an allowlist
How the gate applies decisions
| Decision | Effect |
|---|---|
HookResult.deny("reason") | Message is dropped; the agent is never invoked; the adapter returns immediately |
HookResult(decision="allow", modified_input={"content": "..."}) | Content is rewritten in place; agent, memory, and command parser all see the new text |
HookResult.allow() | Message passes through unchanged |
When multiple
MESSAGE_RECEIVED hooks run, the last matching modification wins. Hook errors are non-fatal — the message passes through unchanged.Gateway Events
| Event | Trigger | Input Type | Key Fields | Use Case |
|---|---|---|---|---|
GATEWAY_START | BotOS.start() | GatewayStartInput | platforms, bot_count | Initialization |
GATEWAY_STOP | BotOS.stop() | GatewayStopInput | platforms, bot_count, reason | Cleanup |
Schedule & background events
Schedule hooks fire when scheduled jobs are managed and triggered byBotOS. JOB_COMPLETED fires when a background subagent job launched via spawn_subagent(background=True) reaches a terminal state — after the internal on_complete callback runs, best-effort (a raising handler cannot crash the worker).
| Event | Trigger | Input Type | Key Fields | Use Case |
|---|---|---|---|---|
SCHEDULE_ADD | Schedule job added | — | — | Audit schedule changes |
SCHEDULE_REMOVE | Schedule job removed | — | — | Audit schedule changes |
SCHEDULE_TRIGGER | Scheduled job runs | ScheduleTriggerInput | job_name, job_id, message | Observability, metrics |
JOB_COMPLETED | Background job reaches terminal state (COMPLETED or FAILED) | JobCompletedInput | job_info (job_id, status, result/error, duration, origin, deliver, platform, chat_id, thread_id) | Observability, custom delivery routing, chat-back delivery (see Background Subagents) |
Background Job Events
JOB_COMPLETED fires when a background job reaches a terminal state (COMPLETED or FAILED). It fires after the internal on_complete callback runs — a raising handler cannot crash the worker.
JobCompletedInput Fields
| Field | Type | Description |
|---|---|---|
job_info | JobInfo | Terminal job state. Contains job_id, status, result, error, duration, origin. |
- Observability and metrics on background job durations and failure rates
- Custom delivery routing when the built-in
deliver=token is insufficient - External side effects on completion (webhooks, database writes)
Storage Events
| Event | Trigger | Use Case |
|---|---|---|
TOOL_RESULT_PERSIST | Before result storage | Result modification |
Kanban Events
| Event | Input Type | Use Case |
|---|---|---|
KANBAN_TASK_CREATED | KanbanHookInput | Task added to board |
KANBAN_TASK_CLAIMED | KanbanHookInput | Task assigned |
KANBAN_TASK_MOVED | KanbanHookInput | Task status changed (includes auto-promotion: dispatcher fires to_status='ready' when all parents are terminal — see Dependency Auto-Promotion). |
KANBAN_TASK_DONE | KanbanHookInput | Task completed |
KANBAN_TASK_BLOCKED | KanbanHookInput | Task blocked |
KANBAN_TASK_FAILED | KanbanHookInput | Task failed |
Dependency Auto-Promotion Events
When the dispatcher auto-promotes a child task,KANBAN_TASK_MOVED fires with this payload:
Auto-promotion events do not include
from_status. Check for its absence if you need to distinguish auto-promotions from manual moves.Complete Event Reference
| Event | Category | Description |
|---|---|---|
BEFORE_TOOL | Tool | Before tool execution |
AFTER_TOOL | Tool | After tool execution |
BEFORE_TOOL_DEFINITIONS | Tool | Before tool definitions are sent to LLM |
BEFORE_AGENT | Agent | Before agent runs |
AFTER_AGENT | Agent | After agent completes |
BEFORE_LLM | LLM | Before LLM API call |
AFTER_LLM | LLM | After LLM response |
SESSION_START | Session | Session starts |
SESSION_END | Session | Session ends |
ON_ERROR | Error | Error occurs |
ON_RETRY | Error | Before retry |
USER_PROMPT_SUBMIT | User | User submits prompt |
NOTIFICATION | User | Notification sent |
SUBAGENT_STOP | Subagent | Subagent completes |
SETUP | System | Initialization |
BEFORE_COMPACTION | Context | Before compaction |
AFTER_COMPACTION | Context | After compaction |
MESSAGE_RECEIVED | Message | Inbound gate — drop (deny) or redact/rewrite before agent dispatch (see Inbound Message Gate) |
MESSAGE_SENDING | Message | Outbound gate — cancel or rewrite before sending |
MESSAGE_SENT | Message | After message sent |
GATEWAY_START | Gateway | Gateway starts |
GATEWAY_STOP | Gateway | Gateway stops |
SCHEDULE_ADD | Schedule | Schedule job added |
SCHEDULE_REMOVE | Schedule | Schedule job removed |
SCHEDULE_TRIGGER | Schedule | Scheduled job runs |
JOB_COMPLETED | Background | Background job reached terminal state (COMPLETED or FAILED) |
TOOL_RESULT_PERSIST | Storage | Before result storage |
KANBAN_TASK_CREATED | Kanban | Task added to board |
KANBAN_TASK_CLAIMED | Kanban | Task assigned |
KANBAN_TASK_MOVED | Kanban | Task status changed (includes auto-promotion: dispatcher fires this with to_status='ready' for each dependent task whose parents are all terminal) |
KANBAN_TASK_DONE | Kanban | Task completed |
KANBAN_TASK_BLOCKED | Kanban | Task blocked |
KANBAN_TASK_FAILED | Kanban | Task failed |
Bot Runtime Lifecycle
Gateway and session hooks are emitted byBotOS and BotSessionManager — no extra wiring needed when your agent is passed to a bot.
- All emission is best-effort and a no-op when no hooks are registered (zero overhead)
BEFORE_AGENT/AFTER_AGENTare fired byagent.chat()itself — they are not re-fired at the gateway boundary to avoid double-dispatch- In async contexts (e.g. inside
BotSessionManager.chat), emission is fire-and-forget; in sync contexts it is blocking
MESSAGE_RECEIVED and MESSAGE_SENDING are policy gates, not passive observers. deny drops the message; modified_input["content"] rewrites it. Gateway/session lifecycle events (GATEWAY_START, GATEWAY_STOP, SESSION_START, SESSION_END) remain best-effort observability points and do not gate startup or shutdown.Best Practices
Keep hooks lightweight
Keep hooks lightweight
Hooks run synchronously. Avoid heavy operations that could slow down agent execution.
Use matchers for filtering
Use matchers for filtering
Use pattern matchers to only run hooks for specific tools or operations.
Return early
Return early
Return
HookResult.allow() quickly for non-matching cases to minimize overhead.Handle errors gracefully
Handle errors gracefully
Wrap hook logic in try/except to prevent breaking agent execution.
Related
Inbound Message Gate
Drop or redact incoming messages before the agent sees them
Hooks
Hook system overview
Kanban Tasks
Kanban hook events and lifecycle
Plugins
Plugin system with hooks

