Quick Start
How It Works
BEFORE_AGENT and AFTER_AGENT are fired by agent.chat() itself — the gateway does not re-fire them to avoid double-dispatch to plugins.Events Fired by the Bot Runtime
| Event | When | Input Type | Key Fields |
|---|---|---|---|
GATEWAY_START | BotOS.start() | GatewayStartInput | platforms, bot_count |
GATEWAY_STOP | BotOS.stop() | GatewayStopInput | platforms, bot_count, reason |
SESSION_START | First message per user (once per session lifetime) | SessionStartInput | session_id, agent_name, source, session_name |
SESSION_END | /new reset, policy auto-reset, stale reap, reset_all | SessionEndInput | session_id, agent_name, reason |
SCHEDULE_TRIGGER | Scheduled job runs in BotOS._execute_schedule_job | ScheduleTriggerInput | job_name, job_id, message |
MESSAGE_RECEIVED | Incoming message from platform | MessageReceivedInput | platform, content, sender_id |
MESSAGE_SENDING | Before bot sends a reply | MessageSendingInput | platform, content, channel_id |
MESSAGE_SENT | After bot successfully sends a reply | MessageSentInput | platform, content, message_id |
SESSION_END reason values:
clear— user sent/newpolicy— policy auto-reset triggeredstale— session reaped due to inactivityclear_all—reset_allcalled (clears all sessions)
Common Patterns
Audit Log
Log every gateway and session event to a file with timestamps.Per-User Usage Counter
Increment a counter when a session starts, persist it when the session ends.Scheduled-Job Observability
Push a metric every time a scheduled job fires.Configuration / HookResult
ReturningHookResult.deny("reason") from a gateway or session lifecycle hook is best-effort for these events: BotOS emits them but does not gate startup or shutdown on the result.
Treat lifecycle hooks as observability points, not policy gates. For policy enforcement (e.g. blocking tool calls or LLM requests), use BEFORE_TOOL or BEFORE_LLM hooks and link to your guardrail logic.
Best Practices
Keep lifecycle hooks lightweight
Keep lifecycle hooks lightweight
Gateway and session hooks may run inside an async event loop. Avoid blocking I/O or heavy computation — use fire-and-forget coroutines or thread pools for slow operations.
Don't raise from a hook
Don't raise from a hook
Emission is wrapped in
try/except inside the bot runtime, but unhandled exceptions from your hook function are logged at debug level and swallowed. Return HookResult.allow() even on internal errors to avoid silent failures.Use agent_name to disambiguate multiple agents
Use agent_name to disambiguate multiple agents
When one
BotOS runs multiple bots (each with a different agent), the agent_name field on every event identifies which agent the event belongs to. Key your per-agent metrics on event_data.agent_name.Key per-user state on session_id, not platform user IDs
Key per-user state on session_id, not platform user IDs
Platform user IDs differ between Telegram, Discord, Slack, and WhatsApp. Use
session_id from SessionStartInput / SessionEndInput as the stable key for per-user state across platforms.Related
Hook Events Reference
Complete event reference with all input types and fields
BotOS
Multi-platform bot orchestrator that emits these lifecycle hooks
Hooks
Hook system concepts: registries, decisions, and matchers
Session Management
How per-user sessions are managed across platforms

