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
This commit is contained in:
+122
-73
@@ -1,110 +1,149 @@
|
||||
# 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 dedicated per-agent bot users.
|
||||
|
||||
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 │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─── │
|
||||
│ │tanko-bot │ │mumuni-bot│ │koonimo │ │koby-bot │ │... │
|
||||
│ │ │ │ │ │-bot │ │ │ │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─── │
|
||||
└───────┼────────────┼────────────┼────────────┼──────────────┘
|
||||
│ │ │ │
|
||||
┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
|
||||
│ Hermes │ │ Hermes │ │ Hermes │ │ Hermes │
|
||||
│ Plugin │ │ Plugin │ │ Plugin │ │ Plugin │
|
||||
│ (Python)│ │ (Python)│ │ (Python)│ │ (Python)│
|
||||
│ │ │ │ │ │ │ │
|
||||
│ Tanko │ │ Mumuni │ │ Koonimo │ │ Koby │
|
||||
│ CT 112 │ │ CT 114 │ │ CT 113 │ │ CT 111 │
|
||||
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||||
│ 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 │
|
||||
└─────────┘ └───────┘ └───────┘ └───────┘ └───────────┘
|
||||
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ Agent Zero │ │ pi │
|
||||
│ Plugin │ │ Extension │
|
||||
│ (Python) │ │ (TypeScript) │
|
||||
│ │ │ │
|
||||
│ kagentz │ │ abiba │
|
||||
│ CT 105 │ │ CT 100 │
|
||||
└──────────────┘ └──────────────┘
|
||||
Note: Koby (Hermes) also on a Hermes CT, not shown for brevity
|
||||
```
|
||||
|
||||
## Message Flow (Normal)
|
||||
## Message Flow: DM (Primary)
|
||||
|
||||
```
|
||||
User creates topic "project-x" in #agent-hub
|
||||
└── User types: @tanko-bot analyze this data
|
||||
└── Zulip fires event to tanko-bot's event queue
|
||||
└── Hermes Zulip Plugin on CT 112
|
||||
├── Detects: tanko-bot in mentioned_users
|
||||
├── Strips @mention from message
|
||||
├── Constructs MessageEvent
|
||||
└── Routes to Tanko agent via BasePlatformAdapter
|
||||
└── Tanko agent processes, returns response
|
||||
└── Plugin posts to #agent-hub > project-x
|
||||
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)
|
||||
## Message Flow: @all-bots Broadcast (Secondary)
|
||||
|
||||
```
|
||||
User types: @all-bots status report
|
||||
└── Zulip fires event to ALL 6 bots (same narrow)
|
||||
└── Each plugin independently:
|
||||
├── Detects: All Bots in mentioned_users
|
||||
├── Forwards to its agent
|
||||
└── Posts response to same topic
|
||||
└── 6 independent responses appear in the topic
|
||||
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**: Agents communicate via Zulip. There is no direct A2A (Agent-to-Agent) messaging layer. Instead, each agent listens to the `#agent-hub` and responds to specific mentions or `@all-bots` pings.
|
||||
- **Swarm Development**: The "Swarm" refers to our collaborative development methodology where multiple agents/developers work on different components of the plugin simultaneously.
|
||||
- **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
|
||||
- Path: `hermes-zulip-plugin/src/hermes_zulip/`
|
||||
- Implements: `BasePlatformAdapter` (Hermes Gateway)
|
||||
- Config: `config.yaml` per-agent
|
||||
- Entry point: `plugin.yaml` (Hermes manifest)
|
||||
| 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
|
||||
- Path: `agent-zero-plugin/src/`
|
||||
- Implements: Agent Zero plugin API
|
||||
- Config: `config.yaml` per-agent
|
||||
| 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
|
||||
- Path: `pi-zulip-extension/src/`
|
||||
- Implements: pi extension (TypeScript module in `~/.pi/agent/extensions/`)
|
||||
- Config: `config.yaml` per-agent
|
||||
- Reference: pi extension docs at `/usr/lib/node_modules/@earendil-works/pi-coding-agent/docs/extensions.md`
|
||||
|
||||
## Reliability
|
||||
- **Reconnection**: Zulip SDK handles backoff/reconnect natively
|
||||
- **Retry**: 3 attempts with 5s backoff on agent timeout
|
||||
- **Error Response**: User-visible message on failure
|
||||
- **Monitoring**: Health endpoint (`/health`) on each plugin (port 9200)
|
||||
- **Logging**: Per-plugin log files
|
||||
### 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
|
||||
# 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"
|
||||
private_topic: "tanko-private"
|
||||
|
||||
zulip:
|
||||
server_url: "https://chat.sysloggh.net"
|
||||
email: "tanko-bot@chat.sysloggh.net"
|
||||
api_key: "${ZULIP_API_KEY}" # From env var
|
||||
all_bots_user_id: 1234
|
||||
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
|
||||
@@ -116,9 +155,19 @@ monitoring:
|
||||
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
|
||||
2. Zulip bot users created for all 6 agents + All Bots
|
||||
3. `#agent-hub` stream created with all bots subscribed
|
||||
|
||||
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`
|
||||
|
||||
Reference in New Issue
Block a user