# AionUi Multi-Agent Communication and Channels: Architecture Deep Dive

> Explore AionUi multi-agent communication and channels. Learn how its layered subsystem connects AI agents to messaging platforms via plugins and secure workflows. Deep dive into the architecture.

- Repository: [OfficeAI/AionUi](https://github.com/iofficeai/aionui)
- Tags: architecture
- Published: 2026-02-15

---

**AionUi implements a layered channel subsystem that connects AI agents to instant messaging platforms through platform-agnostic plugins, unified message formats, and secure pairing workflows.**

AionUi is an open-source AI desktop application that bridges large language models with external communication platforms. The **AionUi multi-agent communication and channels** architecture enables seamless interaction between AI workers (Gemini, Claude, Codex) and instant messaging clients like Telegram, Lark, and DingTalk through a sophisticated gateway pattern that isolates platform-specific details while maintaining unified session management.

## Architecture Overview

The channel subsystem follows a layered architecture that separates concerns across six distinct layers. Each layer communicates through well-defined interfaces, allowing the system to support new messaging platforms without modifying core AI logic.

The **Gateway** layer handles plugin registration and message routing through `PluginManager` and `ActionExecutor`. The **Core** layer orchestrates lifecycle management via singletons like `ChannelManager`, `SessionManager`, and `PairingService`. The **Agent** layer manages AI communication through `ChannelMessageService` and `ChannelEventBus`. Platform-specific implementations reside in the **Plugins** and **Adapters** layers, while the **Actions** layer defines concrete commands triggered by UI buttons or slash-commands.

## Core Components

### ChannelManager Singleton

`ChannelManager` serves as the entry point for the entire subsystem. Located at [`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts), this singleton creates and holds four critical sub-components:

```typescript
private pluginManager: PluginManager | null = null;
private sessionManager: SessionManager | null = null;
private pairingService: PairingService | null = null;
private actionExecutor: ActionExecutor | null = null;

```

During `initialize()`, the manager instantiates `PairingService`, `SessionManager`, and `PluginManager`, then creates an `ActionExecutor` and wires the message handler and tool-confirmation handler. The method loads all enabled plugins from the database and starts them. When the application shuts down, `shutdown()` stops every plugin, clears the pairing interval, and disposes of the AI service.

### PluginManager and BasePlugin Lifecycle

`PluginManager` maintains a registry of platform plugins in [`src/channels/gateway/PluginManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/PluginManager.ts). Each plugin extends `BasePlugin` from [`src/channels/plugins/BasePlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/BasePlugin.ts), which defines a state-machine lifecycle: `created → initializing → ready → starting → running → stopping → stopped`.

The manager forwards incoming unified messages to `ActionExecutor` and routes tool-confirmation callbacks to `ChannelMessageService`. This abstraction allows the core system to treat Telegram, Lark, and DingTalk as interchangeable communication endpoints.

### ActionExecutor Routing Logic

Located in [`src/channels/gateway/ActionExecutor.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/ActionExecutor.ts), this component receives `IUnifiedIncomingMessage` objects from plugins. It performs several critical functions:

1. **Authorization Check**: Verifies pairing status via `PairingService`
2. **Session Resolution**: Creates or retrieves sessions through `SessionManager` using the composite key `userId:chatId`
3. **Placeholder Messaging**: Sends "Thinking..." indicators to the user
4. **AI Delegation**: Routes the request to `ChannelMessageService` for agent processing
5. **Throttling**: Implements a 500ms throttle timer to prevent flooding platforms with streaming updates

### SessionManager Isolation

`SessionManager` in [`src/channels/core/SessionManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/SessionManager.ts) ensures conversation isolation across different chats. It generates composite keys using `buildKey(userId, chatId)`, guaranteeing that a user can maintain independent conversations in different group chats. Sessions are cached in-memory and persisted to the `assistant_sessions` SQLite table.

### PairingService Security

The `PairingService` in [`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts) implements a secure approval workflow. When a new user messages the bot, the service generates a **6-digit pairing code**, stores it in `assistant_pairing_codes`, and displays it on the desktop UI via the IPC bridge. The user must approve the request in Settings → Channels. Approved users are recorded in `assistant_users` and can subsequently send messages without repeating the pairing process.

### ChannelMessageService and Event Broadcasting

`ChannelMessageService` in [`src/channels/agent/ChannelMessageService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelMessageService.ts) encapsulates calls to AI workers (Gemini, ACP, Codex). It emits events on the global `ChannelEventBus` from [`src/channels/agent/ChannelEventBus.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/agent/ChannelEventBus.ts), ensuring that both the desktop UI (via IPC) and the IM plugins receive identical AI output streams. This **dual broadcast** mechanism keeps the external channel and internal UI perfectly synchronized.

## Platform Adapters and Plugins

Each messaging platform implements a plugin and adapter pair:

- **Telegram**: [`TelegramPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/TelegramPlugin.ts) handles long-polling, message splitting, and inline keyboards. [`TelegramAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/TelegramAdapter.ts) converts Telegram updates to `IUnifiedIncomingMessage` format.
- **Lark**: [`LarkPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/LarkPlugin.ts) manages WebSocket connections, interactive cards, and event de-duplication. [`LarkAdapter.ts`](https://github.com/iOfficeAI/AionUi/blob/main/LarkAdapter.ts) handles native SDK object conversion.
- **DingTalk**: [`DingTalkPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/DingTalkPlugin.ts) implements the Stream API, AI Card handling, and token management.

All plugins expose uniform `sendMessage` and `editMessage` APIs operating on the unified format, allowing the core system to remain platform-agnostic.

## Code Examples

### Initializing the Channel Subsystem

```typescript
import { getChannelManager } from '@/channels';

// Called from the main process on app start
async function startChannels() {
  const manager = getChannelManager();
  await manager.initialize();          // loads plugins, pairing, sessions
  console.log('Channel subsystem ready');
}

```

The `initialize()` method in [`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts) instantiates all core services and starts enabled plugins.

### Sending a Unified Message

```typescript
import { getChannelManager } from '@/channels';
import { IUnifiedOutgoingMessage } from '@/channels/types';

async function sendHello(chatId: string) {
  const manager = getChannelManager();
  const pluginMgr = manager.getPluginManager();
  if (!pluginMgr) return;

  const msg: IUnifiedOutgoingMessage = {
    type: 'text',
    text: 'Hello from AionUi!',
  };

  // TelegramPlugin implements sendMessage(chatId, msg)
  await pluginMgr.sendMessage('telegram', chatId, msg);
}

```

The plugin internally uses its adapter to convert the unified message into native Telegram API calls.

### Handling Tool Confirmations

When an AI tool requires user approval, the confirmation flow works as follows:

```typescript
// Inside a plugin's confirm handler (registered by ChannelManager)
async function onConfirm(
  userId: string, 
  platform: string, 
  callId: string, 
  value: string
) {
  const manager = getChannelManager();
  // Route to the confirm handler defined in initialize()
  await manager?.pluginManager?.confirmHandler?.(userId, platform, callId, value);
}

```

This handler:
1. Looks up the user in `assistant_users` via `db.getChannelUserByPlatform`
2. Retrieves the active session to obtain `conversationId`
3. Calls `ChannelMessageService.confirm(conversationId, callId, value)` to resume the AI task

### Synchronizing Channel Settings

When users change AI models or agents in the UI:

```typescript
await getChannelManager().syncChannelSettings('telegram', {
  backend: 'gemini',
  customAgentId: undefined,
  name: 'Gemini 1.5',
}, { id: 'gemini-1.5', useModel: 'gemini-pro' });

```

The `syncChannelSettings` method updates stored conversation models and clears all active sessions, ensuring the next message uses the new configuration.

## Summary

- **AionUi multi-agent communication and channels** rely on a layered architecture separating platform adapters from core AI logic
- `ChannelManager` acts as the singleton orchestrator, managing `PluginManager`, `SessionManager`, `PairingService`, and `ActionExecutor`
- **Unified message formats** (`IUnifiedIncomingMessage`, `IUnifiedOutgoingMessage`) enable platform-agnostic communication across Telegram, Lark, and DingTalk
- **Session isolation** uses composite keys (`userId:chatId`) to maintain independent conversations per chat
- **Secure pairing** requires 6-digit code approval before users can interact with AI agents
- **Dual broadcast** via `ChannelEventBus` synchronizes AI responses between the desktop UI and external messaging platforms

## Frequently Asked Questions

### How does AionUi handle message routing between different IM platforms?

AionUi routes messages through the `ActionExecutor` class in [`src/channels/gateway/ActionExecutor.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/gateway/ActionExecutor.ts). When a plugin receives a message from Telegram, Lark, or DingTalk, it converts the native format to `IUnifiedIncomingMessage` using its adapter. The `ActionExecutor` then checks authorization, resolves the session via `SessionManager`, and delegates to `ChannelMessageService` for AI processing. Responses follow the reverse path, ensuring platform-specific details never leak into core AI logic.

### What security measures protect the multi-agent communication channels?

The architecture implements a **pairing workflow** managed by `PairingService` in [`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts). When a new user contacts the bot, the system generates a cryptographically random 6-digit code stored in `assistant_pairing_codes`. The desktop UI displays this code via IPC, and the user must explicitly approve the request in Settings → Channels. Only after approval is the user record created in `assistant_users`, granting persistent access. This prevents unauthorized access to AI agents through compromised messaging accounts.

### How does AionUi maintain conversation context across multiple chats?

`SessionManager` in [`src/channels/core/SessionManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/SessionManager.ts) implements **composite key isolation** using the `buildKey(userId, chatId)` method. This creates a unique identifier combining the user ID and chat ID, ensuring that conversations in different group chats remain independent. Sessions are cached in-memory for performance and persisted to the `assistant_sessions` SQLite table. When `ActionExecutor` processes a message, it retrieves or creates the specific session for that chat context, maintaining isolated conversation histories even when a single user participates in multiple channels simultaneously.

### What happens when a user changes the AI model in the channel settings?

When users modify the default model or agent through the UI, the system calls `ChannelManager.syncChannelSettings()` in [`src/channels/core/ChannelManager.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/core/ChannelManager.ts). This method updates the backend configuration for the specified channel (e.g., switching from Claude to Gemini) and **clears all active sessions** for that platform. Clearing sessions ensures that the next incoming message creates a fresh conversation context using the new model parameters, preventing context contamination between different AI backends. The change takes effect immediately for new messages while preserving historical conversation data in the database.