Skip to content

Hooks

HookEngine

HookEngine()

Manages global and per-room hook registration and execution.

has_off_lock_hooks

has_off_lock_hooks(room_id, event)

Whether a needs_lock=False check applies to event (RFC §9.5.1).

room_hook_names

room_hook_names(room_id)

The names of the hooks registered for room_id alone.

has_router_hook

has_router_hook(room_id)

Whether a conversation router's hook applies to room_id.

has_sync_hooks

has_sync_hooks(room_id, trigger, event)

Whether a SYNC hook of trigger applies to event in room_id.

register

register(hook)

Register a global hook.

add_room_hook

add_room_hook(room_id, hook)

Register a hook for a specific room.

remove_global_hook

remove_global_hook(name)

Remove a global hook by name.

remove_room_hook

remove_room_hook(room_id, name)

Remove a room hook by name.

has_hooks

has_hooks(trigger=None)

Whether a hook is registered — for trigger, or for any trigger at all.

O(1) either way: a set lookup, or the set's emptiness. The framework skips the work only a hook would consume when nothing is listening — the context built for BEFORE_DELIVER, the room history loaded for the inbound pipeline — so it runs a handful of times per message, never per hook. Global and room hooks alike are in the index; identity hooks live in the framework's own registry, and a caller that needs both asks both.

fail_closed_hook

fail_closed_hook(room_id, trigger)

The name of the first SYNC hook on trigger that fails closed, or None.

For a caller that cannot run the pipeline at all (the room's context would not build): with such a hook registered, the payload must be withheld as the hook's own failure would withhold it.

run_sync_hooks async

run_sync_hooks(room_id, trigger, event, context, *, skip_event_filter=False, needs_lock=None, fold=None, fire_observers=True)

Run sync hooks sequentially. Stops on block, passes modified events.

Parameters:

Name Type Description Default
room_id str

The room ID to run hooks for.

required
trigger HookTrigger

The hook trigger type.

required
event RoomEvent | Any

The event to pass to hooks. For voice hooks, this may be a VoiceSession or str instead of RoomEvent.

required
context RoomContext

The room context.

required
skip_event_filter bool

If True, skip channel-based event filtering. Use this for voice hooks where event is not a RoomEvent.

False
needs_lock bool | None

None runs every SYNC hook. False runs only the off-lock ones (RFC §9.5.1) and fires no ASYNC observer — the locked pass that follows fires them once, on the final event. True runs only the hooks that need the lock.

None
fold Callable[[Any, dict[str, Any]], Any] | None

For a trigger whose hooks may also rewrite the payload through metadata (ON_TOOL_CALL's result override), fold(event, metadata) returns the event as a hook's rewrite left it (a MODIFY's payload or the event, with the metadata written in; metadata is empty after a MODIFY that set none), so the next hook, the ASYNC observers and the caller all see the chain's latest state. None leaves metadata beside the event.

None
fire_observers bool

False leaves the ASYNC observers to the caller, for a firing whose observers must see something other than the event the chain left: a served tool call's result as the model reads it, or a report no SYNC hook can change.

True

run_async_hooks async

run_async_hooks(room_id, trigger, event, context, *, skip_event_filter=False, name_prefix=None, exclude_name_prefix=None)

Run async hooks concurrently. Errors are logged, never raised.

Finds hooks regardless of their declared execution mode so that hooks registered with the default SYNC execution still fire for triggers that are only invoked asynchronously (e.g. AFTER_BROADCAST, lifecycle hooks, voice hooks).

Parameters:

Name Type Description Default
room_id str

The room ID to run hooks for.

required
trigger HookTrigger

The hook trigger type.

required
event RoomEvent | Any

The event to pass to hooks. For voice hooks, this may be a VoiceSession or str instead of RoomEvent.

required
context RoomContext

The room context.

required
skip_event_filter bool

If True, skip channel-based event filtering. Use this for voice hooks where event is not a RoomEvent.

False
name_prefix str | None

Only run hooks whose name starts with this prefix.

None
exclude_name_prefix str | None

Skip hooks whose name starts with this prefix.

None

run_observers async

run_observers(room_id, trigger, event, context, *, skip_event_filter=False)

Run only the ASYNC-registered hooks for trigger, fire-and-forget.

The counterpart of :meth:run_async_hooks, which deliberately ignores the declared execution mode. Here the mode is the whole point: it is what separates a hook that observes a call from one that serves it.

A refused tool call must still be observable — an audit trail that cannot see a denial cannot tell a denied agent from an idle one — but it must not reach a hook that would serve it, or the denial would merely hide the side effect instead of preventing it. Only a SYNC hook can serve a call (it is the one that returns a result), so dispatching the ASYNC hooks alone makes the distinction structural rather than a rule each hook author has to remember.

HookRegistration dataclass

HookRegistration(trigger, execution, fn, priority=0, name='', timeout=30.0, channel_types=None, channel_ids=None, directions=None, event_types=None, fail_closed=False, needs_lock=True)

A registered hook function.

Attributes:

Name Type Description
trigger HookTrigger

When the hook fires (BEFORE_BROADCAST, AFTER_BROADCAST, etc.)

execution HookExecution

SYNC (can block/modify) or ASYNC (fire-and-forget)

fn SyncHookFn | AsyncHookFn

The hook function

priority int

Lower numbers run first (default: 0)

name str

Optional name for logging and removal

timeout float

Max execution time in seconds (default: 30.0)

channel_types set[ChannelType] | None

Only run for events from these channel types (None = all)

channel_ids set[str] | None

Only run for events from these channel IDs (None = all)

directions set[ChannelDirection] | None

Only run for events with these directions (None = all)

event_types set[EventType] | None

Only run for events of these types (None = all)

fail_closed bool

SYNC only — a timeout, an exception or an unusable result blocks instead of allowing (RFC §9.3). For a content check that must never let an unchecked payload through.

needs_lock bool

BEFORE_BROADCAST SYNC only — False runs the hook off the room lock, before it is taken, ordered by the room's admission ticket (RFC §9.5.1). Only for a hook that reads the event, not the room's state: its context is the pre-lock one.