# Zulip Multi-Platform Agent Communication — Architecture **Version:** 2.0 (DM-First) **Date:** 2026-06-21 ## Overview A production-ready system enabling 6 AI agents across 3 platforms (Hermes Python, Agent Zero, PI TypeScript) to communicate through Zulip via **Direct Messages (DMs)**. Each agent's bot user receives DMs directly from human users, and all bots subscribe to `#agent-hub` only for `@all-bots` broadcasts. ## Core Design Decision: DM-First | Old (v1) — @mention in Stream | New (v2) — DM-First | |-------------------------------|---------------------| | User @mentions bot in #agent-hub | User sends DM directly to bot | | Bot detects via `mentioned_users` (unreliable in Zulip 12.0) | Bot detects `message.type === 'private'` (trivial) | | Bot must capture origin topic for reply routing | No topic routing needed — Zulip DM thread is 1:1 | | All 6 bots receive every #agent-hub message (noise) | Only the recipient bot receives the DM | | Stream subscription required for all agents | DM requires no stream subscription | ## Architecture Diagram ``` ┌─────────────────────────────────────────────────────────────┐ │ Zulip Server (CT 117) │ │ chat.sysloggh.net:443 │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Each Agent Connects via TWO Channels: │ │ │ │ │ │ │ │ DM (PRIMARY) #agent-hub (BROADCAST) │ │ │ │ ┌────────────────────┐ ┌─────────────────────┐ │ │ │ │ │ Chat with owner │ │ @all-bots detection │ │ │ │ │ │ No @mention parse │ │ Content-based regex │ │ │ │ │ │ No topic capture │ │ (no dedicated user) │ │ │ │ │ │ Trivial detection │ │ 6 agents respond │ │ │ │ │ └────────────────────┘ └─────────────────────┘ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ Bot Users (6): tanko-bot, mumuni-bot, koonimo-bot, │ │ koby-bot, kagentz-bot, abiba-bot │ │ (No dedicated @all-bots bot — see ADR-006) │ └──────┬──────────┬──────────┬──────────┬──────────┬───────────┘ │ │ │ │ │ ┌────▼────┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───────┐ │ Hermes │ │Hermes │ │Hermes │ │ PI │ │ Agent Zero│ │ Plugin │ │Plugin │ │Plugin │ │Extens.│ │ Plugin │ │ (Python)│ │Python │ │Python │ │(TS) │ │ (Python) │ │ │ │ │ │ │ │ │ │ │ │ Tanko │ │Mumuni │ │Koonimo│ │ Abiba │ │ kagentz │ │ CT 112 │ │CT 114 │ │CT 113 │ │CT 100 │ │ CT 105 │ └─────────┘ └───────┘ └───────┘ └───────┘ └───────────┘ Note: Koby (Hermes) also on a Hermes CT, not shown for brevity ``` ## Message Flow: DM (Primary) ``` User opens DM to @tanko-bot in Zulip User types: "deploy the API update" └── Zulip pushes event to tanko-bot's event queue ONLY └── Hermes Zulip Plugin on CT 112 ├── Checks: message.type === 'private' ✅ ├── No @mention parsing needed ├── No topic/stream routing needed ├── Constructs MessageEvent with sender_id + content └── Routes to Tanko agent for processing └── Tanko processes, returns response └── Plugin sends DM reply via: POST /api/v1/messages { "type": "private", "to": [sender_id], "content": "Starting deployment..." } ``` ## Message Flow: @all-bots Broadcast (Secondary) ``` User posts in #agent-hub > any-topic: "@**all-bots** status report please" └── Zulip pushes event to ALL 6 bot event queues └── Each plugin on each CT independently: ├── Checks: message.type === 'stream' ✅ ├── Checks: regex /@\*\*(?:all-bots|All Bots)\*\*/i.test(content) ✅ ├── Ignores: all non-@all-bots stream messages └── Forwards @all-bots content to its agent └── Agent processes, returns response └── Plugin posts to same #agent-hub > topic └── 6 independent responses appear in the topic ``` ## Plugin Communication Logic - **Isolation**: Each agent (Tanko, Mumuni, etc.) runs as an independent process on its own Compute Target (CT). - **State**: Agents are stateless; all "memory" is handled via the RA-H OS Knowledge Graph and the Hermes persistent memory tool. - **Communication**: Primary channel is Zulip DMs. Secondary broadcast channel is `#agent-hub` for `@all-bots` notifications only. - **No @mention routing**: DM-based routing eliminates the `mentioned_users` field dependency (unreliable in Zulip 12.0). - **Single stream subscription**: Each bot subscribes to `#agent-hub` exclusively for `@all-bots` detection. All other messages in `#agent-hub` are ignored. ## Platform Plugin Contracts ### Hermes (Python) — BasePlatformAdapter | Field | Value | |-------|-------| | Path | `hermes-zulip-plugin/src/hermes_zulip/` | | Interface | `BasePlatformAdapter` (Hermes Gateway) | | Config | `config.yaml` per-agent | | Entry point | `plugin.yaml` (Hermes manifest) | | DM detection | `message.type === 'private'` | | @all-bots detection | Regex `/@\*\*(?:all-bots|All Bots)\*\*/i` on stream events | ### Agent Zero — A0 Plugin System | Field | Value | |-------|-------| | Path | `agent-zero-plugin/src/` | | Interface | Agent Zero plugin API | | Config | `config.yaml` per-agent | | DM detection | `message.type === 'private'` | | @all-bots detection | Regex `/@\*\*(?:all-bots|All Bots)\*\*/i` on stream events | ### PI (TypeScript) — PI Extension API | Field | Value | |-------|-------| | Path | `pi-zulip-extension/src/` | | Interface | PI extension (TypeScript module in `~/.pi/agent/extensions/`) | | Config | `config.yaml` per-agent | | DM detection | `event.type === 'private'` or `message.type === 'private'` | | @all-bots detection | Regex `/@\*\*(?:all-bots|All Bots)\*\*/i` on stream events | ## Config Template ```yaml # config.yaml — Per-agent Zulip plugin configuration (DM-First) agent: name: "tanko" display_name: "Tanko" zulip_bot_name: "tanko-bot" owner_email: "jerome@sysloggh.net" zulip: server_url: "https://chat.sysloggh.net" email: "tanko-bot@chat.sysloggh.net" api_key: "${ZULIP_API_KEY}" # From env var or config stream: "agent-hub" # Only used for @all-bots broadcasts stream_only_for_broadcast: true error_handling: timeout_seconds: 30 retry_count: 3 retry_delay_seconds: 5 monitoring: health_endpoint_enabled: true health_port: 9200 ``` ## Reliability - **Reconnection**: Zulip SDK handles backoff/reconnect natively - **Retry**: 3 attempts with 5s backoff on agent timeout - **Error Response**: User-visible message in DM on failure - **Monitoring**: Health endpoint (`/health`) on each plugin (port 9200) - **Logging**: Per-plugin log files (or journald for Hermes systemd services) ## Prerequisites 1. Zulip server running on CT 117 (`chat.sysloggh.net`) 2. Zulip bot users created for all 6 agents (no dedicated "All Bots" bot) 3. `#agent-hub` stream created with all 6 bots subscribed 4. SSH key access to all 6 agent CTs 5. Gitea repo initialized at `git@git.sysloggh.net:SyslogSolution/zulip-platform-plugins.git` 6. Each plugin configured with per-agent `config.yaml`