Skip to main content
Hook events are triggered at specific points in the agent lifecycle, allowing you to intercept, modify, or block operations.
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.
The user sends a prompt; hooks fire at each lifecycle event as the agent runs.

How It Works

Lifecycle Events

Quick Start

1

Import Hook Components

2

Register a Hook

3

Use with Agent


Core Events

Tool Events

Tool Definition Events

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.
Mutate in place using event_data.tool_definitions[:] = [...]. Reassigning the local name (event_data.tool_definitions = [...]) is silently ignored by the runtime.

BeforeToolDefinitionsInput Fields

Per-model filtering using the model field:

Agent Events

LLM Events

Session Events

Plugin authors: subclass Plugin and override session_start / session_end — see Plugins → How the Bridge Works.
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

Plugin authors: subclass Plugin and override on_error — see Plugins → How the Bridge Works.

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

The OnRetryInput event includes both new tool-specific fields and legacy fields for backward compatibility: New fields (recommended):
  • tool_name: Name of the failing tool
  • attempt: Current attempt number (1-based)
  • max_attempts: Maximum attempts configured
  • delay_ms: Delay before this retry in milliseconds
  • error_type: Classified error type (timeout, rate_limit, connection_error, unknown)
  • error: Original exception object
LLM retry fields (populated when fired by Agent(retry=...) — see Agent Retry):
  • delay_seconds: Seconds the agent will sleep before the next attempt
  • attempt: Current attempt number (0-based)
  • operation: "llm_request" (sync) or "async_llm_request" (async)
  • error_message: String representation of the failing LLMError
  • max_retries: Configured RetryBackoffConfig.max_retries
  • retry_count: Same as attempt + 1 (1-based legacy alias)
Legacy fields (for backward compatibility):
  • retry_count: Same as attempt
  • max_retries: Same as max_attempts
  • error_message: String representation of error

Agent-Level Error Callbacks

In addition to hook events, agents support a direct on_error callback for LLM failures:

Hook Events vs Agent Callbacks


Extended Events

User Interaction Events

Subagent Events

System Events

Message Events

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.
  • None or HookResult.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 gatedeny 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

When multiple MESSAGE_RECEIVED hooks run, the last matching modification wins. Hook errors are non-fatal — the message passes through unchanged.
Since PraisonAI PR #2589, every messaging adapter (Telegram, Slack, Discord, WhatsApp, email, agentmail) honours these decisions. Hook exceptions are non-fatal — the message passes through. See Inbound Message Gate for patterns and failure semantics.

Gateway Events

Schedule & background events

Schedule hooks fire when scheduled jobs are managed and triggered by BotOS. 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).

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

Typical uses:
  • 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

Kanban Events

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


Bot Runtime Lifecycle

Gateway and session hooks are emitted by BotOS 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_AGENT are fired by agent.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

Hooks run synchronously. Avoid heavy operations that could slow down agent execution.
Use pattern matchers to only run hooks for specific tools or operations.
Return HookResult.allow() quickly for non-matching cases to minimize overhead.
Wrap hook logic in try/except to prevent breaking agent execution.

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