Messaging and notification system for the Jido ecosystem. Provides a unified interface for building conversational AI agents across multiple channels (Telegram, Discord, Slack, etc.).
This package is experimental and pre-1.0. APIs and behavior will change.
jido_messaging is built around Jido.Chat and the Elixir implementation
aligned to the Vercel Chat SDK (chat-sdk.dev/docs).
- Channel-agnostic: Write once, deploy to any messaging platform
- OTP-native: Built on GenServers, Supervisors, and ETS for reliability
- LLM-ready: Message format designed for LLM integration with role-based messages
- Extensible: Pluggable adapters for storage and channels
Add jido_messaging to your list of dependencies in mix.exs:
def deps do
[
{:jido_messaging, github: "agentjido/jido_messaging", branch: "main"}
]
enddefmodule MyApp.Messaging do
use Jido.Messaging,
adapter: Jido.Messaging.Adapters.ETS
end# In application.ex
def start(_type, _args) do
children = [
MyApp.Messaging
]
Supervisor.start_link(children, strategy: :one_for_one)
end# Create a room
{:ok, room} = MyApp.Messaging.create_room(%{type: :direct, name: "Support Chat"})
# Save a message
{:ok, message} = MyApp.Messaging.save_message(%{
room_id: room.id,
sender_id: "user_123",
role: :user,
content: [%Jido.Messaging.Content.Text{text: "Hello!"}]
})
# List messages
{:ok, messages} = MyApp.Messaging.list_messages(room.id)jido_messaging no longer ships in-package Telegram/Discord handlers.
Use adapter packages directly:
jido_chat_telegram(Jido.Chat.Telegram.Adapter)jido_chat_discord(Jido.Chat.Discord.Adapter)
def deps do
[
{:jido_chat, github: "agentjido/jido_chat", branch: "main"},
{:jido_chat_telegram, github: "agentjido/jido_chat_telegram", branch: "main"},
{:jido_chat_discord, github: "agentjido/jido_chat_discord", branch: "main"},
{:jido_messaging, github: "agentjido/jido_messaging", branch: "main"}
]
end# Telegram
config :jido_chat_telegram,
telegram_bot_token: System.get_env("TELEGRAM_BOT_TOKEN")
# Discord (Nostrum transport)
config :nostrum,
token: System.get_env("DISCORD_BOT_TOKEN")
# Discord webhook verification (optional, recommended)
config :jido_chat_discord,
discord_public_key: System.get_env("DISCORD_PUBLIC_KEY")jido_messaging is now the shared ingress runtime:
- Host app receives webhook/gateway payload.
- Call
MyApp.Messaging.route_webhook_request/4orroute_payload/3. - Runtime resolves bridge config, verifies/parses via adapter callbacks, and routes through
Jido.Chat.process_event/4. - Message events are ingested; non-message events return typed envelopes.
Canonical APIs:
MyApp.Messaging.route_webhook_request(bridge_id, request_meta, payload, opts \\ [])MyApp.Messaging.route_payload(bridge_id, payload, opts \\ [])Jido.Messaging.WebhookPlug(generic host-mounted Plug endpoint)MyApp.Messaging.create_bridge_room(spec)(room + bindings + policy bootstrap)
For adapter-owned listeners (Telegram polling / Discord gateway), pass a sink MFA that targets:
Jido.Messaging.IngressSink.emit(instance_module, bridge_id, payload, opts)
defmodule MyApp.Webhooks.Router do
use Plug.Router
plug :match
plug :dispatch
post "/webhooks/:bridge_id" do
conn =
Jido.Messaging.WebhookPlug.call(
conn,
Jido.Messaging.WebhookPlug.init(
instance_module: MyApp.Messaging,
bridge_id_resolver: fn conn -> conn.params["bridge_id"] end
)
)
conn
end
end# Telegram polling ingress (listener worker owned by bridge runtime)
MyApp.Messaging.put_bridge_config(%{
id: "tg_primary",
adapter_module: Jido.Chat.Telegram.Adapter,
opts: %{
ingress: %{
mode: "polling",
token: System.fetch_env!("TELEGRAM_BOT_TOKEN"),
timeout_s: 30,
poll_interval_ms: 500
}
}
})
# Discord gateway ingress (Nostrum ConsumerGroup source by default)
MyApp.Messaging.put_bridge_config(%{
id: "dc_primary",
adapter_module: Jido.Chat.Discord.Adapter,
opts: %{
ingress: %{
mode: "gateway",
poll_interval_ms: 250
}
}
})The demo task supports declarative topology bootstrap from YAML:
mix jido.messaging.demo --topology config/demo.topology.example.yamlLive Telegram + Discord bridge demo (env-driven topology):
scripts/demo_bridge_live.shSupported top-level keys:
mode:echo | bridge | agentbridge: demo runtime bridge opts (telegram_chat_id,discord_channel_id, adapter modules)bridge_rooms: one-shot room bootstrap specs (create_bridge_room/2)bridge_configs: control-planeBridgeConfigentriesrooms: room bootstrap entriesroom_bindings: bridge-scoped room bindingsrouting_policies: outbound routing policy bootstrap
Use config/demo.topology.example.yaml as the starter template.
For live bridge ingress with Telegram polling + Discord gateway, use
config/demo.topology.live.yaml with .env values.
MyApp.Messaging (Supervisor)
├── Runtime (GenServer) - Manages adapter state
└── (Future) RoomSupervisor, InstanceSupervisor
Message Flow:
1. Host webhook endpoint or adapter listener emits into runtime ingress.
2. `InboundRouter` resolves `BridgeConfig` and adapter module by `bridge_id`.
3. Adapter verifies/parses event; runtime routes through `Jido.Chat.process_event/4`.
4. Message events are ingested (room/participant/message + dedupe/session).
5. Outbound delivery runs through `OutboundRouter`/`OutboundGateway`.
jido_messaging uses lane-based test execution:
mix testormix test.core: core/unit/component tests (default)mix test.integration: integration-only tests (@moduletag :integration)mix test.story: story/spec contract tests (@moduletag :story)mix test.all: full suite except:flaky
%Jido.Messaging.Message{
id: "msg_abc123",
room_id: "room_xyz",
sender_id: "user_123",
thread_id: "thread_123",
external_thread_id: "platform_thread_123",
delivery_external_room_id: "platform_delivery_target_123",
role: :user | :assistant | :system | :tool,
content: [%Content.Text{text: "Hello"}],
status: :sending | :sent | :delivered | :read | :failed,
metadata: %{}
}%Jido.Chat.Room{
id: "room_xyz",
type: :direct | :group | :channel | :thread,
name: "Support Chat",
external_bindings: %{telegram: %{"bot_id" => "chat_123"}}
}%Jido.Chat.Participant{
id: "part_abc",
type: :human | :agent | :system,
identity: %{username: "john", display_name: "John"},
external_ids: %{telegram: "123456789"}
}Full documentation is available at HexDocs.
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.