Show HN:Jido Assembly——纯 Elixir 构建、集成 Agent 的 Slack 克隆
英文标题:🔶 Show HN: Jido Assembly; Slack Clone in Pure Elixir with Integrated Agents
Jido Assembly 是一个多用户团队聊天应用,也是 Jido 生态能力的参考实现:它将 Jido Agent、持久化消息、Telegram/Discord 桥接以及 Hologram 的 Elixir 前端整合在同一套 BEAM 运行时中。应用中的 Agent 与人类用户共享频道、私信、线程和事件模型,Agent 回复通过与普通消息相同的持久化、Signal、搜索和反应机制处理。项目默认无需 LLM 或服务商凭据即可查看预置演示数据,配置 Anthropic、Telegram 或 Discord 凭据后可启用实时 Agent 和外部服务连接。该项目并非生产级 Slack 替代品,但为开发者展示了如何以 Elixir/OTP 的并发、监督和事件驱动模型构建 Agent-native 应用。
302.3 KB
52.1 KB
中文
Jido Assembly 展示了 Jido 生态系统能够构建什么。它是一个多用户聊天应用——一个 Slack 克隆——用于展示 Jido、AI Agent、Hologram 和 BEAM 的能力。
我们将其构建为 Agent-native 应用的架构案例研究。用户可以在频道、私信和线程中聊天;Jido Agent 以参与者身份加入这些房间。Jido Messaging 持久化聊天模型。Jido Chat 将主要运维房间连接至 Telegram 和 Discord。Hologram 则提供交互式 Elixir 用户界面。
在 Assembly 中,Agent-native 意味着 Agent 与人类使用相同的房间、消息、线程和事件。它们是应用参与者,而不是一个独立的聊天机器人功能。Jido Agent 是一个带有状态和行为、通过 Jido runtime 运行的模块。
Assembly 将这一模型置于一个可运行的应用中。用户可以发起一轮 Agent 交互,Agent 回复会与人类消息显示在同一房间中。配置桥接后,从 Assembly 编辑器发送的顶级消息也可以扇出至 Telegram 和 Discord。每个订阅客户端都能接收已提交的结果。

Assembly 包含什么
Assembly 包含团队聊天应用应有的功能:频道、私信、线程、表情反应、提及、搜索、在线状态,以及移动端房间切换。
这一范围让系统的每个部分都有真实工作可做。应用需要处理并发参与者、持久状态、房间级路由、实时更新、Agent 执行和外部服务商边界。它展示了 Jido 各软件包如何在一个小型 Agent 示例之外进行组合。
Assembly 是开发者展示项目,而不是生产级 Slack 替代方案。它不包含身份验证、授权、文件上传、企业管理功能或 Slack API 兼容性。
预置的 #ops-workflow 房间加入了 Jido 相关组件。其中包含三个 Agent、示例服务商流量和工作流数据。开发者检查器会展示界面背后的 Signal 和消息元数据。
默认工作区无需 LLM 密钥或服务商凭据即可运行。预置数据使界面可供查看和检查。添加 Anthropic 密钥即可运行实时 Agent 轮次;添加 Telegram 或 Discord 凭据即可连接实时服务。
跟随一条消息穿过 Assembly
一条本地消息会经过三个必需阶段;如果配置了服务商桥接,它还可以进入第四阶段。
1. 用户发送消息
Hologram Action 接收浏览器事件。它会更新本地组件状态,例如草稿和待发送状态,然后向服务器发送一个 Hologram Command。
这是发送路径的客户端部分,内容从 Jido.Assembly.Pages.Assembly.action/3 压缩而来:
component
|> put_state(:draft, "")
|> put_state(:send_pending, true)
|> put_command(:persist_message,
room_id: component.state.active_room_id,
body: draft,
sender_id: component.state.current_user.id
)
该 Command 会调用 Assembly 聊天上下文。该上下文通过 Jido Messaging 及其 SQLite 适配器写入消息。Jido Messaging 为应用提供规范化的房间、参与者、消息、线程和反应记录。
服务器端持久化消息,向当前浏览器返回一个 Action,并将同一 Action 广播到工作区。以下示例从 Jido.Assembly.Pages.Assembly.Commands.command/3 及其广播辅助函数压缩而来:
case Chat.send_message_command(
params.room_id,
params.body,
params.sender_id,
route_outbound: true
) do
{:ok, message, signals} ->
action_params = %{
room_id: message.room_id,
message: message,
connector_snapshot: Chat.connector_snapshot(message.room_id),
signal: SignalPresenter.summary(signals, "jido.messaging.room.message_added")
}
server
|> put_action(:message_saved, action_params)
|> put_broadcast(
{:workspace, Chat.workspace_id()},
:message_saved,
action_params
)
end
2. Jido Agent 在房间中响应
Jido 提供 Agent runtime。Jido AI 则通过 ReqLLM 提供由 LLM 支持的 Agent Strategy 和模型访问能力。Agent 执行按需触发:用户在运维 Agent 面板中输入提示词,并选择 Ask 来发起一轮交互。
Assembly 在 #ops-workflow 中定义了三个 Agent:
- Triage Agent 专注于影响范围和严重性。
- Bridge Agent 解释服务商及消息投递状态。
- Runbook Agent 提出检查项、审批和交接建议。
每个 Agent 都是标准的 Jido AI Agent 模块:
use Jido.AI.Agent,
name: "assembly_triage_agent",
model: :assembly_haiku,
max_iterations: 2,
streaming: false
Assembly 会为每次 Agent 轮次启动一个短生命周期 runtime。Agent 接收近期房间上下文,并返回受限长度的响应。在这一演示中,一轮最多运行三个 Agent;一个提示词最多可延续两轮;每条回复限制为 420 个字符。启用 Agent-to-Agent 聊天时,后运行的 Agent 可以读取此前 Agent 的回复。
Agent 响应不会使用独立的 AI 持久化路径。Assembly 会通过与人类消息相同的聊天 Command 发送它,因此它获得相同的消息、Signal、线程、反应和搜索行为。
3. 已提交事件更新每个客户端
Jido Messaging 在提交持久化变更后返回 Signal。Signal 是一种结构化事件,具有诸如 jido.messaging.room.message_added 的类型。Jido Signal 为这些事件提供兼容 CloudEvents 的封装和路由。
Assembly 将生成的 Hologram Action 发送给当前浏览器,并广播至工作区。其他已订阅客户端会通过 Server-Sent Events 接收更新。开发者检查器会展示已提交 Signal 的元数据,因此你可以检查每次用户界面更新所对应的触发事件。
同样的“提交并广播”模式适用于消息、反应和房间创建。在线状态则有所不同:Phoenix Presence 跟踪临时会话状态,Assembly 会将标准化的在线状态 Signal 广播给已连接客户端。
4. 配置后,Telegram 和 Discord 加入房间
Jido Chat 定义了服务商中立的目标和载荷。Telegram 与 Discord 适配器则针对各自服务实现这些契约。
Assembly 将这些服务商绑定到规范化的 #ops-workflow 房间。一条传入的 Telegram 或 Discord 消息可以成为普通房间消息;从 Assembly 编辑器发送的一条顶级消息可以通过所有已配置的适配器进行路由。
服务商会变化,但对话模型不会变化。用户、Agent、Telegram 参与者和 Discord 参与者都可以在同一房间中参与对话。
边界是明确的:传入的服务商消息不会自动启动 Agent 轮次。Agent 回复会存储在规范化房间中,但默认情况下 Assembly 不会将其扇出至服务商。
为什么选择 Hologram
我们选择 Hologram 作为 Assembly 的用户界面层。它使项目能够同时使用 Elixir 处理浏览器行为和服务器操作。
LiveView 将其主要视图状态保存在服务器端 socket assigns 中。浏览器事件通常会到达 LiveView 进程,该进程更新自身状态并将渲染差异发送到浏览器。仅客户端行为可以使用 JavaScript command 或 hook。该模型非常适合面向服务器的 Phoenix 应用。LiveView 文档 描述了这一进程和事件模型。
Hologram 将组件状态保留在浏览器中,并将所需的 Elixir 代码编译为 JavaScript。Actions 在客户端运行;当界面需要数据库、API 或其他受保护资源时,Commands 在服务器端运行。Hologram 使用 HTTP/2 处理 Action 到 Command 的请求,并使用 Server-Sent Events 处理广播 Action。其架构指南记录了这一划分。
| 关注点 | LiveView | Hologram |
|---|---|---|
| 主要状态 | 服务器 socket assigns | 浏览器组件状态 |
| 仅客户端行为 | JavaScript command 或 hook | 由 Elixir 编译而来的 Action |
| 服务器工作 | LiveView 事件处理器和应用上下文 | Command 和应用上下文 |
| 服务器到客户端更新 | 通过 LiveView 连接传输的渲染差异 | 通过 Server-Sent Events 传输的广播 Action |
Assembly 拥有许多本地界面事件:用户切换房间、打开线程、编辑草稿、选择演示参与者和操作面板。Hologram 使用 Elixir 代码在浏览器中运行这些 Action。持久化写入、Agent 调用、搜索和桥接操作仍由服务器 Command 处理。
这种划分与演练中展示的事件流相匹配。一个浏览器执行 Command;Jido Messaging 提交变更并生成 Signal;随后 Hologram 会向当前浏览器应用 Action,并将其广播至其他已订阅浏览器。
LiveView 同样可以支持这一应用。我们选择 Hologram,是因为其客户端 Action、服务器 Command 和广播模型能直接映射 Assembly 的本地及持久化事件,同时也让界面逻辑保持在 Elixir 中。
这存在一种权衡:Hologram 尚未提供类似 LiveViewTest 的 API,用于完整的进程内交互测试。Assembly 对 Action 和 Command 进行单元测试,再将浏览器测试留给 JavaScript 行为、SSE 投递和跨标签页更新。
演练背后的软件包
每个软件包在应用中都有一个清晰职责。
| 软件包 | 在 Assembly 中的职责 |
|---|---|
| Jido 和 Jido AI | 运行 Triage、Bridge 和 Runbook Agent。 |
| Jido Messaging | 负责房间、参与者、消息、线程、反应、持久化和桥接策略。 |
| Jido Signal | 表示并路由已提交的应用事件。 |
| Jido Chat | 将服务商中立的聊天操作连接至 Telegram 和 Discord。 |
| Hologram | 运行客户端 Action、调用服务器 Command,并应用实时用户界面更新。 |
这些职责彼此分离。Jido AI 生成 Agent 响应,但 Jido Messaging 负责房间和消息记录。Jido Chat 连接服务商,但不会创建第二套消息模型。Jido Signal 承载已提交事件,Hologram 将这些事件应用到界面中。
为什么 Elixir/OTP 和 BEAM 很重要
Assembly 同时运行许多相互独立的活动:用户发送消息;Jido 为每次 Agent 轮次启动独立的短生命周期 runtime;服务商适配器接收外部事件;Phoenix 跟踪在线状态并分发更新;Hologram 保持活跃客户端连接。
BEAM 为并发工作而构建。其轻量级进程会隔离每项活动,并通过消息而非共享可变状态进行通信。较慢的 Agent 响应或服务商请求可以占用各自的进程,而不会阻塞其他聊天活动。
OTP supervision 为长期运行的服务提供清晰的故障边界。发生故障的受监督进程可以在不重启整个应用的情况下重新启动。Phoenix PubSub 和 Presence 使用同一 runtime 模型分发房间更新并跟踪已连接参与者。
Elixir 提供模式匹配、不可变数据和显式数据转换。这些特性适合在各软件包之间流转的记录和事件。最终形成了一个统一的 runtime 模型,用于处理 Agent 执行、消息持久化、在线状态、服务商流量和实时界面更新。
开始构建
Assembly 的规模足够小,适合作为参考应用阅读。客户端 Action 可从 app/pages/assembly_page.ex 开始;然后查看服务器 Command 所在的 app/pages/assembly/commands.ex、消息功能所在的 lib/jido_assembly/chat.ex、Agent 轮次所在的 lib/jido_assembly/agents.ex,以及服务商路由所在的 lib/jido_assembly/bridges.ex。
项目启动后,打开 #ops-workflow。发送一条消息,运行一轮 Agent,添加一个反应,打开开发者检查器并跟踪生成的 Signal。
Jido Assembly 在一个地方展示了完整组合:运行在 BEAM 上的 Elixir 界面、持久化消息、按需 Agent、结构化事件和可选服务商桥接。
English / 原文
Jido Assembly is a showcase for what the Jido ecosystem can build. It's a multi-user chat application - a Slack clone - to showcase the power of Jido, AI Agents, Hologram and the BEAM.
We built it as an architecture case study for agent-native applications. People chat in channels, direct messages, and threads. Jido Agents join those rooms as participants. Jido Messaging persists the chat model. Jido Chat connects the main ops room to Telegram and Discord. Hologram provides the interactive Elixir user interface.
In Assembly, agent-native means that Agents use the same rooms, messages, threads, and events as people. They are application participants, not a separate chatbot feature. A Jido Agent is a module with state and behavior that runs through the Jido runtime.
Assembly puts that model in one working application. A person can start an Agent round. Agent replies appear in the same room as human messages. When a bridge is configured, top-level messages from the Assembly composer can also fan out to Telegram and Discord. Every subscribed client can receive the committed result.

What Assembly includes
Assembly includes the features expected from a team chat application: channels, direct messages, threads, reactions, mentions, search, presence, and mobile room switching.
That scope gives every part of the system real work to do. The application handles concurrent participants, durable state, room-level routing, real-time updates, Agent execution, and external provider boundaries. It shows how the Jido packages compose beyond a small Agent example.
Assembly is a developer showcase, not a production Slack replacement. It does not include authentication, authorization, file uploads, enterprise administration, or Slack API compatibility.
The seeded #ops-workflow room adds the Jido parts. It contains three Agents, sample provider traffic, and workflow data. The developer inspector displays the Signals and message metadata behind the interface.
The default workspace runs without an LLM key or provider credentials. Seeded data keeps the interface available for inspection. Add an Anthropic key to run live Agent rounds. Add Telegram or Discord credentials to connect live services.
Follow a message through Assembly
A local message passes through three required stages. It can enter a fourth stage when a provider bridge is configured.
1. A person sends a message
A Hologram action receives the browser event. It updates local component state, such as the draft and pending status, then sends a Hologram Command to the server.
This is the client side of the send path, condensed from Jido.Assembly.Pages.Assembly.action/3:
component
|> put_state(:draft, "")
|> put_state(:send_pending, true)
|> put_command(:persist_message,
room_id: component.state.active_room_id,
body: draft,
sender_id: component.state.current_user.id
)
The Command calls the Assembly chat context. That context writes the message through Jido Messaging and its SQLite adapter. Jido Messaging provides the canonical room, participant, message, thread, and reaction records for the application.
The server side persists the message, returns an action to the current browser, and broadcasts the same action to the workspace. This example is condensed from Jido.Assembly.Pages.Assembly.Commands.command/3 and its broadcast helper:
case Chat.send_message_command(
params.room_id,
params.body,
params.sender_id,
route_outbound: true
) do
{:ok, message, signals} ->
action_params = %{
room_id: message.room_id,
message: message,
connector_snapshot: Chat.connector_snapshot(message.room_id),
signal: SignalPresenter.summary(signals, "jido.messaging.room.message_added")
}
server
|> put_action(:message_saved, action_params)
|> put_broadcast(
{:workspace, Chat.workspace_id()},
:message_saved,
action_params
)
end
2. Jido Agents respond in the room
Jido supplies the Agent runtime. Jido AI adds LLM-backed Agent Strategies and model access through ReqLLM. Agent execution is on demand. A person types a prompt in the ops Agent panel and selects Ask to start a round.
Assembly defines three Agents in #ops-workflow:
- Triage Agent focuses on impact and severity.
- Bridge Agent explains provider and delivery state.
- Runbook Agent proposes checks, approvals, and handoffs.
Each Agent is a standard Jido AI Agent module:
use Jido.AI.Agent,
name: "assembly_triage_agent",
model: :assembly_haiku,
max_iterations: 2,
streaming: false
Assembly starts a short-lived runtime for each Agent turn. An Agent receives recent room context and returns a bounded response. In this demo, a round can run at most three Agents, a prompt can continue for two rounds, and each reply is limited to 420 characters. When Agent-to-Agent chat is active, a later Agent can read responses from the Agents that ran before it.
An Agent response does not use a separate AI persistence path. Assembly sends it through the same chat command as a human message. It receives the same message, Signal, thread, reaction, and search behavior.
3. The committed event updates each client
Jido Messaging returns Signals after it commits a durable change. A Signal is a structured event with a type such as jido.messaging.room.message_added. Jido Signal provides the CloudEvents-compatible envelopes and routing for these events.
Assembly sends the resulting Hologram action to the current browser and broadcasts it to the workspace. Other subscribed clients receive the update through Server-Sent Events. The developer inspector shows the committed Signal metadata, so you can inspect the event that caused each user interface update.
The same commit-and-broadcast pattern applies to messages, reactions, and room creation. Presence is different. Phoenix Presence tracks temporary session state, and Assembly broadcasts normalized presence Signals to connected clients.
4. Telegram and Discord join the room when configured
Jido Chat defines provider-neutral targets and payloads. The Telegram and Discord adapters implement those contracts for each service.
Assembly binds those providers to the canonical #ops-workflow room. An incoming Telegram or Discord message can become a normal room message. A top-level message sent from the Assembly composer can route through every configured adapter.
The provider changes, but the conversation model does not. People, Agents, Telegram participants, and Discord participants can take part in the same room.
The boundaries are explicit. Incoming provider messages do not start Agent rounds automatically. Agent replies are stored in the canonical room, but Assembly does not fan them out to providers by default.
Why we chose Hologram
We chose Hologram as the user interface layer for Assembly. It lets the project use Elixir for both browser behavior and server operations.
LiveView keeps its primary view state in server-side socket assigns. Browser events usually reach the LiveView process. That process updates its state and sends rendered diffs to the browser. Client-only behavior can use JavaScript commands or hooks. This model works well for server-oriented Phoenix applications. The LiveView documentation describes this process and event model.
Hologram keeps component state in the browser and compiles the required Elixir code to JavaScript. Actions run on the client. Commands run on the server when the interface needs a database, API, or other protected resource. Hologram uses HTTP/2 for Action-to-Command requests and Server-Sent Events for broadcast actions. Its architecture guide documents this split.
| Concern | LiveView | Hologram |
|---|---|---|
| Primary state | Server socket assigns | Browser component state |
| Client-only behavior | JavaScript commands or hooks | Actions compiled from Elixir |
| Server work | LiveView event handlers and application contexts | Commands and application contexts |
| Server-to-client updates | Rendered diffs over the LiveView connection | Broadcast actions over Server-Sent Events |
Assembly has many local interface events. People switch rooms, open threads, edit drafts, select demo participants, and operate panels. Hologram runs these actions in the browser with Elixir code. Durable writes, Agent calls, searches, and bridge operations remain server Commands.
That split fits the event flow shown in the walkthrough. One browser performs a Command. Jido Messaging commits the change and produces Signals. Hologram then applies an action to the current browser and broadcasts it to other subscribed browsers.
LiveView could support this application. We chose Hologram because its client action, server Command, and broadcast model maps directly to Assembly's local and durable events. It also keeps the interface logic in Elixir.
There is a tradeoff. Hologram does not yet provide a LiveViewTest-style API for full in-process interaction tests. Assembly unit-tests Actions and Commands, then reserves browser tests for JavaScript behavior, SSE delivery, and cross-tab updates.
The packages behind the walkthrough
Each package has one clear role in the application.
| Package | Responsibility in Assembly |
|---|---|
| Jido and Jido AI | Run the Triage, Bridge, and Runbook Agents. |
| Jido Messaging | Own rooms, participants, messages, threads, reactions, persistence, and bridge policy. |
| Jido Signal | Represent and route committed application events. |
| Jido Chat | Connect provider-neutral chat operations to Telegram and Discord. |
| Hologram | Run client Actions, call server Commands, and apply real-time user interface updates. |
The responsibilities stay separate. Jido AI produces an Agent response, but Jido Messaging owns the room and message records. Jido Chat connects providers, but it does not create a second message model. Jido Signal carries committed events. Hologram applies those events to the interface.
Why Elixir/OTP and the BEAM matter
Assembly runs many independent activities at the same time. People send messages. Jido starts a separate, short-lived runtime for each Agent turn. Provider adapters receive external events. Phoenix tracks presence and distributes updates. Hologram keeps active client connections open.
The BEAM is built for concurrent work. Its lightweight processes isolate each activity. They communicate with messages instead of shared mutable state. A slow Agent response or provider request can occupy its own process without stopping other chat activity.
OTP supervision gives long-running services clear failure boundaries. A failed supervised process can restart without restarting the application. Phoenix PubSub and Presence use the same runtime model to distribute room updates and track connected participants.
Elixir adds pattern matching, immutable data, and explicit data transformations. Those features fit the records and events that move between the packages. The result is one runtime model for Agent execution, message persistence, presence, provider traffic, and real-time interface updates.
Get building
Assembly is small enough to read as a reference application. Start with app/pages/assembly_page.ex for client Actions. Continue with app/pages/assembly/commands.ex for server Commands, lib/jido_assembly/chat.ex for messaging, lib/jido_assembly/agents.ex for Agent rounds, and lib/jido_assembly/bridges.ex for provider routing.
After the project starts, open #ops-workflow. Send a message. Run an Agent round. Add a reaction. Open the developer inspector and follow the resulting Signals.
Jido Assembly shows the complete composition in one place: an Elixir interface, durable messaging, on-demand Agents, structured events, and optional provider bridges running together on the BEAM.