Skip to main content
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

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

RegistrationCalled for
on_messageNormalized inbound message events
on_reactionNormalized reaction change events
on_commandNormalized command invocation events
on_card_actionNormalized card-action callback events
on_eventFallback when an event exists but no specific handler is registered
on_invokeDirect invocation without an Aion event envelope

Handler Injection

Handlers may declare only the parameters they need:
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:
ParameterValue
stateCurrent LangGraph state
runtimeLangGraph Runtime[AionRuntimeContext]
contextCurrent AionRuntimeContext
eventCurrent typed Aion event, or None
distributionCurrent distribution from the Distribution extension, or None
behaviorCurrent behavior from the Distribution extension, or None
environmentCurrent environment from the Distribution extension, or None
principal_identityPrincipal identity associated with the request, or None
service_identityExternal service identity associated with the request, or None
inboxRaw A2A inbox snapshot
threadThread helper for replies, posts, typing, and history
messageNormalized 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.
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.