> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aion.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Handlers

> Event-router node API for LangGraph messaging integrations.

Use `create_event_router` when a LangGraph agent wants Aion to dispatch normalized Aion events to
handler functions without hiding graph topology.

The helper returns a normal LangGraph node. Add it with `builder.add_node(...)` and connect it with
ordinary LangGraph edges.

## Overview

```python theme={null}
from langgraph.graph import END, START, StateGraph

from aion.core.runtime import AionRuntimeContext
from aion.langgraph.authoring import create_event_router


builder = StateGraph(State, context_schema=AionRuntimeContext)

builder.add_node(
    "aion_events",
    create_event_router(
        on_message=handle_message,
        on_reaction=handle_reaction,
        on_command=handle_command,
        on_card_action=handle_card_action,
        on_event=handle_event,
        on_invoke=handle_invoke,
    ),
)
builder.add_edge(START, "aion_events")
builder.add_edge("aion_events", END)

graph = builder.compile()
```

The event router owns only the internal event-to-handler dispatch. The graph author owns all incoming
and outgoing edges.

## Registrations

| Registration     | Called for                                                          |
| ---------------- | ------------------------------------------------------------------- |
| `on_message`     | Normalized inbound message events                                   |
| `on_reaction`    | Normalized reaction change events                                   |
| `on_command`     | Normalized command invocation events                                |
| `on_card_action` | Normalized card-action callback events                              |
| `on_event`       | Fallback when an event exists but no specific handler is registered |
| `on_invoke`      | Direct invocation without an Aion event envelope                    |

Dispatch order for each invocation:

1. Kind-specific handler (`on_message`, `on_reaction`, `on_command`, `on_card_action`) if registered for the event kind.
2. `on_event` — if no kind-specific handler matches but an Aion event is present.
3. `on_invoke` — if there is no Aion event, or neither a kind-specific handler nor `on_event` is registered.

## Handler Injection

Handlers may declare only the parameters they need:

```python theme={null}
async def handle_message(thread, message, distribution, state):
    await thread.reply(f"Got your message: {message.text}")
    return {"last_distribution_id": distribution.id}
```

Available injected parameters:

| Parameter            | Value                                                            |
| -------------------- | ---------------------------------------------------------------- |
| `state`              | Current LangGraph state                                          |
| `runtime`            | LangGraph `Runtime[AionRuntimeContext]`                          |
| `context`            | Current `AionRuntimeContext`                                     |
| `event`              | Current typed Aion event, or `None`                              |
| `distribution`       | Current distribution from the Distribution extension, or `None`  |
| `behavior`           | Current behavior from the Distribution extension, or `None`      |
| `environment`        | Current environment from the Distribution extension, or `None`   |
| `principal_identity` | Principal identity associated with the request, or `None`        |
| `service_identity`   | External service identity associated with the request, or `None` |
| `inbox`              | Raw A2A inbox snapshot                                           |
| `thread`             | `Thread` helper for replies, posts, typing, and history          |
| `message`            | Normalized inbound `Message`, or `None`                          |

Any other declared parameter is forwarded to LangGraph for native injection, such as `config`,
`store`, or `writer`.

## Explicit Entry Routing

Because the event router is a normal node, you can place your own node before it. This is useful for
agents that need to route daemon-style requests, reject unsupported runtime contexts, or validate
distribution metadata before event dispatch.

```python theme={null}
from langgraph.graph import END, START, StateGraph

from aion.core.runtime import AionRuntimeContext
from aion.langgraph.authoring import create_event_router


builder = StateGraph(State, context_schema=AionRuntimeContext)
builder.add_node("request_router", route_request_node)
builder.add_node("daemon", daemon_node)
builder.add_node("reject", reject_node)
builder.add_node("aion_events", create_event_router(on_message=handle_message))

builder.add_edge(START, "request_router")
builder.add_conditional_edges(
    "request_router",
    route_after_request,
    {
        "events": "aion_events",
        "daemon": "daemon",
        "reject": "reject",
    },
)
builder.add_edge("aion_events", END)
builder.add_edge("daemon", END)
builder.add_edge("reject", END)
```

## Dispatch Rules

The dispatch surface is based on normalized event kinds, not provider-specific trigger names.

That means:

* a Slack mention and a Telegram DM can both normalize to `on_message`
* a slash command can normalize to `on_command`
* a card button click can normalize to `on_card_action`

If an author needs provider-specific behavior, they should inspect `context.event`, `distribution`,
or `inbox`, not register provider-only handler names.

## Related Pages

* [AionRuntimeContext](/sdk/langgraph/api/runtime-context)
* [Message](/sdk/langgraph/api/message)
* [Thread](/sdk/langgraph/api/thread)
* [Distribution Messaging Extension](/a2a/extensions/aion/distribution/messaging/1.0.0)
