Files
zulip-platform-plugins/docs/PRD.md
T

236 lines
12 KiB
Markdown

# Product Requirements Document: Zulip Platform Plugins
**Status:** Draft v1.0
**Date:** 2026-06-18
**Authors:** Agent Zero (via grill-with-design on Tabiri infrastructure)
---
## Problem Statement
The Sysloggh agent mesh consists of **6 autonomous agents** across **3 distinct frameworks** (Hermes Python, Agent Zero, PI/openclaw TypeScript) deployed on **5 Proxmox CTs**. These agents operate in isolation — there is no unified inter-agent communication channel. Each agent processes tasks independently, and cross-agent coordination requires manual intervention.
The existing Zulip infrastructure (Zulip 12.0-1 on CT 117, Docker-based) provides a mature async messaging platform, but no agent is wired into it. A partial implementation (`zulip-plugin/adapter.py`) exists with event streaming and dedup logic, but was never deployed. The gateway adapter is compiled but not running on any agent CT, and the required Python packages are not installed.
**The gap:** Agents cannot collaboratively route tasks, share context, or coordinate via Zulip threads.
---
## Solution
A multi-platform **Zulip agent communication layer** consisting of one plugin per agent framework, each running co-located on its agent's CT, connecting its agent to the shared Zulip messaging infrastructure at `chat.sysloggh.net`.
### Architecture at a Glance
```
┌──────────────────────────────────────────────────────────────────┐
│ Zulip Server (CT 117) │
│ chat.sysloggh.net:443 │
│ Stream: #agent-hub │
│ Bot Users: tanko-bot, mumuni-bot, koonimo-bot, koby-bot, │
│ kagentz-bot, abiba-bot, all-bots │
└────┬──────┬──────┬──────┬──────┬──────┬──────┬──────────────────┘
│ │ │ │ │ │ │
┌────┴┐ ┌───┴──┐ ┌┴────┐ ┌┴────┐ ┌┴────┐ ┌┴────┐ ┌──────────┐
│Tnako│ │Mumuni│ │Kooni│ │Koby │ │Kage │ │Abiba│ │@all-bots │
│Herm │ │Herm │ │Herm │ │Herm │ │A0 │ │PI │ │ (special)│
│CT112│ │CT... │ │CT.. │ │CT.. │ │CT.. │ │CT.. │ │ │
└─────┘ └──────┘ └─────┘ └─────┘ └─────┘ └─────┘ └──────────┘
│ │ │ │ │ │
┌────┴┐ ┌───┴──┐ ┌┴────┐ ┌┴────┐ ┌┴────┐ ┌┴────┐
│Config│ │Config│ │Config│ │Config│ │Config│ │Config│
│tanko │ │mumuni│ │kooni │ │koby │ │kage │ │abiba │
│.yaml │ │.yaml │ │.yaml │ │.yaml │ │.yaml │ │.yaml │
└─────┘ └──────┘ └─────┘ └─────┘ └─────┘ └─────┘
```
### Core Principles
- **Co-located:** Each plugin runs on the same CT as its agent, uses platform-native interfaces
- **@mention-routed:** Messages reach the correct agent via Zulip @mention detection
- **Owner-gated:** Private topics are restricted per-agent owner via config
- **Version-controlled:** All plugin code lives in a Gitea monorepo, pushed to CTs via deploy script
- **Graceful degradation:** Users always receive feedback, even when an agent is unavailable
---
## User Stories
### Agent Communication
1. As a **human user**, I want to **@mention an agent in any Zulip topic**, so that the agent receives my message and responds in-thread.
2. As a **human user**, I want to **@mention @all-bots**, so that all 6 agents across all frameworks receive the message.
3. As an **agent**, I want to **receive messages addressed to me**, so that I can process and respond to user requests.
### Private Channels
4. As a **human user**, I want a **private topic per agent** (e.g., `#agent-hub > tanko-private`), so that I can have confidential conversations with a single agent.
5. As a **system administrator**, I want **per-agent owner configuration**, so that only designated users can access each agent's private topic.
### Deployment & Operations
6. As a **system administrator**, I want to **version-control all plugin code in a single Gitea repository**, so that updates are tracked, auditable, and pushable across all CTs.
7. As a **system administrator**, I want a **deploy script** that pulls the latest plugin code and restarts services on all CTs, so that updates are consistent and repeatable.
### Reliability
8. As a **human user**, I want to see **a friendly error message** if an agent is unavailable, rather than a silent timeout.
9. As a **system administrator**, I want **health check endpoints** on each plugin, so that I can monitor plugin status from the harness or monitoring infra.
---
## Implementation Decisions
### Platform Plugin Contracts
Each of the 3 frameworks uses its **native plugin system** — not a unified bridge or A2A overlay for intra-agent communication. Cross-framework @all-bots messages flow through Zulip: all 7 bot users are subscribed to the same `#agent-hub` stream, so every bot receives every message. Each bot checks if it's @mentioned, then either processes or ignores.
### Framework-Specific Implementation
| Framework | Plugin Type | Interface | Reference Architecture |
|-----------|-------------|-----------|----------------------|
| **Hermes Python** (4 agents) | Hermes Gateway `BasePlatformAdapter` | `on_event(message) → Response` | Existing `zulip-plugin/` in homelab |
| **PI/openclaw TypeScript** (1 agent) | openclaw Extension | Extension API (similar to existing Discord/GChat/Mattermost/Matrix) | `/a0/usr/projects/homelab/openclaw/extensions/` |
| **Agent Zero** (1 agent) | A0 Plugin | Agent Zero plugin system | Custom A0 plugin pattern |
### @mention Detection
- Use Zulip SDK's `mentioned_users` field on message events (ADR-005)
- Each bot checks if its own `user_id` is in the `mentioned_users` array
- For @all-bots: bot checks if dedicated `All Bots` user is in `mentioned_users` (ADR-006)
- @all-bots spans all 7 bots across all frameworks (ADR-012)
### Config Schema (per-agent `config.yaml`)
```yaml
# config.yaml (one per bot, stored alongside plugin)
agent:
name: "Tanko"
bot_username: "tanko-bot"
bot_email: "tanko-bot@chat.sysloggh.net"
bot_api_key: "<api_key>"
owner_email: "jerome@sysloggh.net"
private_topic: "tanko-private"
zulip:
server_url: "https://chat.sysloggh.net"
stream: "agent-hub"
error_handling:
timeout_seconds: 30
retry_count: 3
graceful_message: "{{agent_name}} is processing your request — please wait..."
monitoring:
health_endpoint_enabled: true
health_port: 9200
```
### Private Topic ACL
- Bot subscribes to its private topic (`tanko-private`) at startup
- Owner is matched via `config.yaml > owner_email` (ADR-011)
- Messages in private topic: only respond if sender email matches owner
- Messages in public topics: respond to @mentions from any sender
### Error Handling Strategy
- Agent timeout: plugin sends graceful degradation message to Zulip thread (ADR-009)
- Zulip server down: SDK auto-reconnects with exponential backoff
- Invalid message format: plugin logs error, does not crash
- Health endpoint (`/health` on port 9200): returns status, uptime, last_message_time
### Naming Convention
- Bot users: `{name}-bot` (lowercase, dash separator) — `tanko-bot`, `mumuni-bot`, etc. (ADR-003)
- Private topics: `{name}-private``tanko-private`, `mumuni-private`, etc. (ADR-001)
- Display names: capitalize properly — "Tanko Bot", "Mumuni Bot", "All Bots"
---
## Testing Decisions
### Unit Tests
- @mention detection logic: correct extraction of bot from `mentioned_users`
- Private topic ACL: owner matches, non-owner denied
- Config parsing: all fields present, graceful defaults
- Error response: timeout triggers correct grace message
### Integration Tests
- Each plugin connects to Zulip and receives/sends test messages
- @all-bots message reaches all 7 bots
- Private topic message only reaches owner agent
- Health endpoint returns 200 with valid JSON
### Deployment Tests
- `deploy.sh` successfully pulls latest and restarts services on all CTs
- Plugin survives container restart (Zulip reconnection)
- Config YAML validation before deployment
---
## Out of Scope (v1.0)
- **Multi-realm Zulip:** All agents operate within a single Zulip realm
- **Message attachments:** v1 sends and receives text only (no file uploads)
- **Rich formatting:** v1 uses plain markdown responses from agents
- **A2A as primary transport:** Cross-platform communication routes through Zulip, not direct A2A
- **CI/CD pipeline:** Deployment is script-based (`deploy.sh`), not CI/CD
- **Rate limiting:** Zulip's built-in rate limiting is sufficient for initial use
- **Dashboard/monitoring UI:** Health endpoint data consumed by existing harness infra
- **Message history search:** Zulip's native search handles this
---
## Further Notes
### Design Decisions Summary
| # | Decision | Rationale |
|---|----------|-----------|
| ADR-001 | Topic-per-agent with `-private` suffix | Clear naming, easy ACL enforcement |
| ADR-002 | @mention-based routing (not topic-based) | User confirmed, flexible for any topic |
| ADR-003 | Bot naming: `{name}-bot` | Consistent, no space issues in Zulip API |
| ADR-004 | One bot per agent, co-located on CT | Isolated failures, platform-native |
| ADR-005 | SDK `mentioned_users` field | Zulip API native, always accurate |
| ADR-006 | Dedicated `All Bots` user | Explicit @all-bots, not noisy @everyone |
| ADR-007 | Platform-native plugin contracts | Reliability, platform handles state/retries |
| ADR-008 | Native message formats per platform | No translation layer needed |
| ADR-009 | Graceful degradation + user-visible errors | No silent failures |
| ADR-010 | Gitea monorepo + deploy.sh | Single source of truth, versioned |
| ADR-011 | Owner in per-agent config.yaml | Explicit, version-controllable |
| ADR-012 | @all-bots spans all frameworks | Single @all-bots reaches every agent |
### Prerequisites
1. Zulip server operational at `https://chat.sysloggh.net`
2. Zulip bot users created (7 total: tanko-bot, mumuni-bot, koonimo-bot, koby-bot, kagentz-bot, abiba-bot, all-bots)
3. `#agent-hub` stream created and all bots subscribed
4. Gitea repo `zulip-platform-plugins` created and monorepo pushed
5. Hermes gateway framework verified on Tanko/Mumuni/Koonimo/Koby CTs
6. openclaw extension API verified on Abiba CT
7. Agent Zero plugin system verified on Kagentz CT
### Deployment Workflow
```
1. Developer pushes plugin changes to Gitea repo
2. Run: ./scripts/deploy.sh
3. deploy.sh SSHes into each CT:
a. git pull
b. Install/update dependencies
c. Restart plugin service (systemd or supervisor)
4. Verify health endpoint on each CT
```
### Risk Mitigation
- **Template bug in Zulip 12.0-1:** Portico-header renders 500 on configs without `REGISTRATION_OPEN` or with Authentik SSO — resolved via template patch `{% if settings.REGISTRATION_OPEN %}` guard
- **Docker container restarts:** Plugin auto-reconnects via Zulip SDK's built-in reconnect
- **API key rotation:** Keys stored in plugin config, updated via Gitea push