Files
zulip-platform-plugins/docs/ARCHITECTURE.md
T
kagentz-bot 705c29a4e0
CI / validate (pull_request) Failing after 1s
feat: DM-First v2 architecture - Tanko live on CT 112
Architecture change: DM-First (v2) replaces @mention-in-stream (v1) as primary
agent interaction channel. See ADR-001-dm-topology and ADR-002-dm-routing.

Key changes:
- New DM-first Zulip adapter (adapter_dm.py) deployed on CT 112
  - Registers empty narrow to receive ALL events
  - Demuxes DMs (type: 'private') vs stream events
  - Replies to DMs back in private thread, stream replies to #agent-hub
  - Uses blocking long-poll (dont_block=False) to eliminate rate limiting
- Rewritten ARCHITECTURE.md, CONTEXT.md, PRD.md for v2 DM-First
- New ADRs: ADR-001 (DM Topology), ADR-002 (DM Routing)
- Archived old ADRs: 001, 002, 005 (topic-topology, mention-routing, mention-detection)
- Updated ADRs: 003 (agent-naming), 004 (per-agent-bots), 006 (all-bots-model)
- Updated PI extension (index.ts) with DM-aware patterns
- Tanko agent confirmed responding in both DM and @all-bots in #agent-hub
2026-06-21 07:50:30 -04:00

8.7 KiB

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

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

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

Config Template

# 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