From fe434553a8b588703d037a2cece0a0d7c407b325 Mon Sep 17 00:00:00 2001 From: kagentz-bot Date: Fri, 19 Jun 2026 18:37:27 -0400 Subject: [PATCH] feat: initial monorepo with PRD, 12 ADRs, and plugin skeletons --- docs/ARCHITECTURE.md | 121 +++++++++ docs/CONTEXT.md | 59 +++++ docs/PRD.md | 235 ++++++++++++++++++ docs/adr/ADR-001-topic-topology.md | 15 ++ docs/adr/ADR-002-mention-routing.md | 14 ++ docs/adr/ADR-003-agent-naming.md | 15 ++ docs/adr/ADR-004-per-agent-bots.md | 15 ++ docs/adr/ADR-005-mention-detection.md | 15 ++ docs/adr/ADR-006-all-bots-model.md | 15 ++ docs/adr/ADR-007-platform-native-contracts.md | 15 ++ docs/adr/ADR-008-native-message-format.md | 15 ++ docs/adr/ADR-009-error-handling.md | 19 ++ docs/adr/ADR-010-monorepo-structure.md | 16 ++ docs/adr/ADR-011-owner-config.md | 15 ++ docs/adr/ADR-012-all-bots-scope.md | 15 ++ 15 files changed, 599 insertions(+) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/CONTEXT.md create mode 100644 docs/PRD.md create mode 100644 docs/adr/ADR-001-topic-topology.md create mode 100644 docs/adr/ADR-002-mention-routing.md create mode 100644 docs/adr/ADR-003-agent-naming.md create mode 100644 docs/adr/ADR-004-per-agent-bots.md create mode 100644 docs/adr/ADR-005-mention-detection.md create mode 100644 docs/adr/ADR-006-all-bots-model.md create mode 100644 docs/adr/ADR-007-platform-native-contracts.md create mode 100644 docs/adr/ADR-008-native-message-format.md create mode 100644 docs/adr/ADR-009-error-handling.md create mode 100644 docs/adr/ADR-010-monorepo-structure.md create mode 100644 docs/adr/ADR-011-owner-config.md create mode 100644 docs/adr/ADR-012-all-bots-scope.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..2eb94e0 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,121 @@ +# Zulip Multi-Platform Agent Communication — Architecture + +## Overview + +A production-ready system enabling 6 AI agents across 3 platforms (Hermes Python, Agent Zero, PI/openclaw TypeScript) to communicate through Zulip via dedicated per-agent bot users. + +## 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 │ + └─────────┘ └─────────┘ └─────────┘ └─────────┘ + + ┌──────────────┐ ┌──────────────┐ + │ Agent Zero │ │ PI/openclaw │ + │ Plugin │ │ Extension │ + │ (Python) │ │ (TypeScript) │ + │ │ │ │ + │ kagentz │ │ abiba │ + │ CT 105 │ │ CT 100 │ + └──────────────┘ └──────────────┘ +``` + +## Message Flow (Normal) + +``` +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 +``` + +## Message Flow (@all-bots) + +``` +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 +``` + +## 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) + +### Agent Zero — A0 Plugin System +- Path: `agent-zero-plugin/src/` +- Implements: Agent Zero plugin API +- Config: `config.yaml` per-agent + +### PI/openclaw (TypeScript) — openclaw Extension API +- Path: `openclaw-zulip-extension/src/` +- Implements: openclaw Extension +- Config: `config.yaml` per-agent + +## 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 +- **Logging**: Per-plugin log files + +## Config Template + +```yaml +# config.yaml — Per-agent Zulip plugin configuration +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 + +agent: + max_retries: 3 + retry_delay_seconds: 5 + health_port: 8080 +``` + +## 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 +4. SSH key access to all 6 agent CTs +5. Gitea repo initialized diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md new file mode 100644 index 0000000..6048007 --- /dev/null +++ b/docs/CONTEXT.md @@ -0,0 +1,59 @@ +# Zulip Platform Plugins — Domain Vocabulary + +This document captures the shared language and architectural invariants for the multi-platform Zulip agent communication system. + +## Domain Terms + +| Term | Definition | +|------|------------| +| **Agent** | An AI agent operating on a platform (Hermes/Agent Zero/PI/openclaw) that can receive and respond to Zulip messages | +| **Adapter / Plugin** | A platform-native component that bridges Zulip events to the agent framework | +| **@mention** | A Zulip mention (`@**Tanko Bot**`) that triggers the named agent to respond | +| **@all-bots** | A dedicated Zulip bot user that, when @mentioned, triggers ALL agents across all platforms | +| **Topic** | A Zulip conversation thread within a stream | +| **Private topic** | An agent-named topic (e.g., `tanko-private`) where only the bot and its owner participate | +| **Owner** | The human user associated with an agent bot, stored in per-agent config | + +## Naming Conventions + +- Agent Zulip bot display names: lowercase, dash-separated, `-bot` suffix — e.g., `tanko-bot`, `mumuni-bot` +- Agent Zulip bot emails: `@chat.sysloggh.net` — e.g., `tanko-bot@chat.sysloggh.net` +- Private topics: `-private` — e.g., `tanko-private` +- Owner privacy: agent should never expose owner name to other agents or users + +## Agent Registry + +| Agent | Platform | Zulip Bot | CT | Private Topic | +|-------|----------|-----------|-----|---------------| +| tanko | Hermes (Python) | tanko-bot | amdpve CT 112 | tanko-private | +| mumuni | Hermes (Python) | mumuni-bot | minipve CT 114 | mumuni-private | +| koonimo | Hermes (Python) | koonimo-bot | amdpve CT 113 | koonimo-private | +| koby | Hermes (Python) | koby-bot | amdpve CT 111 | koby-private | +| kagentz | Agent Zero | kagentz-bot | amdpve CT 105 | kagentz-private | +| abiba | PI/openclaw (TypeScript) | abiba-bot | amdpve CT 100 | abiba-private | + +## @all-bots Resolution + +@all-bots refers to all 6 agents across all frameworks (Hermes, Agent Zero, PI/openclaw). When @all-bots is mentioned in any topic: + +1. Each per-agent bot receives the event (since they all share `narrow: stream agent-hub`) +2. Each bot checks: "Is `All Bots` in `mentioned_users`?" +3. If yes, bot forwards the message to its agent +4. @all-bots is a **dedicated Zulip bot user** with display name `All Bots` and email `all-bots@chat.sysloggh.net` + +## Response Rules + +- **Own topic**: Agent always responds (to its private topic) +- **Collaboration topic** (any topic that isn't a private topic): Agent responds only when @mentioned +- **@all-bots**: All agents respond +- **Private topics**: Only the agent owner + bot are participants; bot enforces this + +## Monorepo Structure + +All 3 platform plugins live in a single Gitea repository at `git@192.168.68.17:homelab/zulip-platform-plugins.git` + +## Guardian Directives + +- All destructive actions require user confirmation before execution +- Plugin updates are version-controlled through Gitea +- Config files are per-agent and pushed alongside code diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..d47b054 --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,235 @@ +# 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: "" + 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 diff --git a/docs/adr/ADR-001-topic-topology.md b/docs/adr/ADR-001-topic-topology.md new file mode 100644 index 0000000..db91301 --- /dev/null +++ b/docs/adr/ADR-001-topic-topology.md @@ -0,0 +1,15 @@ +# ADR 1: Topic & Stream Topology + +**Date**: 2026-04-28 + +### Decision +Each agent gets a private topic under `#agent-hub` named `-private` for owner-only communication; all other topics are collaboration topics where agents respond only when @mentioned. + +### Context +We need a clear separation between agent-private communication (user-agent DMs) and collaboration (multi-agent discussions). Zulip topics are the right granularity — no separate streams needed. The `-private` suffix convention is simple and self-documenting. + +### Consequences +- All 6 agents co-exist in `#agent-hub` stream +- Users can create any new topic and @mention agents +- Private topics are invitation-only (bot + owner) +- No need to manage multiple streams \ No newline at end of file diff --git a/docs/adr/ADR-002-mention-routing.md b/docs/adr/ADR-002-mention-routing.md new file mode 100644 index 0000000..93aa1bb --- /dev/null +++ b/docs/adr/ADR-002-mention-routing.md @@ -0,0 +1,14 @@ +# ADR 2: @mention-Based Routing + +**Date**: 2026-04-28 + +### Decision +Agents are triggered exclusively by Zulip @mentions. No topic-based assignment. Users @mention an agent in any topic, and the agent's bot responds. + +### Context +Topic-based routing is too rigid — it requires users to know which topic maps to which agent. @mention routing is natural in Zulip (users already know how to @mention). It also enables multi-agent conversations in a single topic. + +### Consequences +- Each bot must parse @mentions from every message in `#agent-hub` +- Routing complexity moves from topic name matching to @mention resolution +- Users can freely create topics and involve agents ad-hoc \ No newline at end of file diff --git a/docs/adr/ADR-003-agent-naming.md b/docs/adr/ADR-003-agent-naming.md new file mode 100644 index 0000000..67e8b57 --- /dev/null +++ b/docs/adr/ADR-003-agent-naming.md @@ -0,0 +1,15 @@ +# ADR 3: Agent Naming Convention + +**Date**: 2026-04-28 + +### Decision +Each agent bot has a display name in `{name}-bot` format with no spaces, dashes only: `tanko-bot`, `mumuni-bot`, `koonimo-bot`, `koby-bot`, `kagentz-bot`, `abiba-bot`. + +### Context +Zulip @mentions work best with clean, predictable names. No spaces avoids ambiguity in @mention parsing. The `-bot` suffix makes it clear these are automated agents, not human users. + +### Consequences +- @mention format: `@**tanko-bot**` +- Email format: `tanko-bot@chat.sysloggh.net` +- Users must type @tanko-bot (not @tanko) +- Consistent naming across all 6 agents \ No newline at end of file diff --git a/docs/adr/ADR-004-per-agent-bots.md b/docs/adr/ADR-004-per-agent-bots.md new file mode 100644 index 0000000..80102f2 --- /dev/null +++ b/docs/adr/ADR-004-per-agent-bots.md @@ -0,0 +1,15 @@ +# ADR 4: One Zulip Bot Per Agent + +**Date**: 2026-04-28 + +### Decision +Each agent has its own dedicated Zulip bot user, co-located on the agent's CT. Each bot runs independently as a platform-native plugin. + +### Context +A single bot creates a single point of failure and requires message routing to the correct agent. Per-agent bots provide full isolation — if tanko's bot fails, mumuni's bot continues working. Co-location means zero A2A overhead for the local agent. + +### Consequences +- 6 Zulip bot users to create and manage +- 6 independent event streams (one per CT) +- @all-bots requires each bot to know about `All Bots` user +- Adding a new agent = create Zulip bot + deploy plugin to CT \ No newline at end of file diff --git a/docs/adr/ADR-005-mention-detection.md b/docs/adr/ADR-005-mention-detection.md new file mode 100644 index 0000000..7ba421f --- /dev/null +++ b/docs/adr/ADR-005-mention-detection.md @@ -0,0 +1,15 @@ +# ADR 5: @mention Detection via Zulip SDK + +**Date**: 2026-04-28 + +### Decision +Use the Zulip SDK's `mentioned_users` field in message events for @mention detection, with regex fallback for cleaning @mention artifacts from forwarded message text. + +### Context +The Zulip event API provides `mentioned_users` — an array of user IDs that were @mentioned in the message. This is the most reliable detection mechanism (handles all @mention formats) and avoids fragile text parsing. Regex is only needed to strip the `@**Display Name**` markup from the forwarded message body. + +### Consequences +- Zero false positives for @mention detection +- Handles @all-bots, @everyone, and individual @mentions uniformly +- Stripped message text is clean for agent consumption +- SDK handles reconnection and backoff for the event stream \ No newline at end of file diff --git a/docs/adr/ADR-006-all-bots-model.md b/docs/adr/ADR-006-all-bots-model.md new file mode 100644 index 0000000..31ecf82 --- /dev/null +++ b/docs/adr/ADR-006-all-bots-model.md @@ -0,0 +1,15 @@ +# ADR 6: @all-bots via Dedicated Zulip Bot User + +**Date**: 2026-04-28 + +### Decision +@all-bots is implemented as a dedicated Zulip bot user with display name `All Bots` and email `all-bots@chat.sysloggh.net`. All 6 per-agent bots subscribe to the same event narrow and detect when `All Bots` is in `mentioned_users`. + +### Context +@all-bots needs to trigger all agents regardless of platform. A dedicated bot user is explicit and intentional — it prevents @everyone or stream-wide pings from accidentally triggering all agents. Each bot independently detects the @mention and forwards to its agent. + +### Consequences +- Must create a 7th Zulip bot user for `All Bots` +- Each agent bot's config must include `All Bots` user ID for detection +- @all-bots is opt-in: only topics that explicitly @all-bots trigger broadcast +- Cross-platform broadcast works without a central dispatcher \ No newline at end of file diff --git a/docs/adr/ADR-007-platform-native-contracts.md b/docs/adr/ADR-007-platform-native-contracts.md new file mode 100644 index 0000000..9f680b7 --- /dev/null +++ b/docs/adr/ADR-007-platform-native-contracts.md @@ -0,0 +1,15 @@ +# ADR 7: Platform-Native Plugin Contracts + +**Date**: 2026-04-28 + +### Decision +Each platform uses its native plugin interface: Hermes `BasePlatformAdapter`, openclaw extension API, Agent Zero plugin system. + +### Context +Each platform has matured, battle-tested interfaces for receiving and sending messages. A unified A2A contract would add unnecessary abstraction and latency for local agent communication. Platform-native contracts are reliable, handle connection-state/retry automatically, and follow each platform's established patterns. + +### Consequences +- Three separate plugin implementations +- Each plugin leverages platform-specific reliability features +- Plugin developers need familiarity with each platform +- Cross-platform @all-bots still works because each bot processes the same Zulip event independently \ No newline at end of file diff --git a/docs/adr/ADR-008-native-message-format.md b/docs/adr/ADR-008-native-message-format.md new file mode 100644 index 0000000..a1c2956 --- /dev/null +++ b/docs/adr/ADR-008-native-message-format.md @@ -0,0 +1,15 @@ +# ADR 8: Platform-Native Message Formats + +**Date**: 2026-04-28 + +### Decision +Each plugin sends and receives messages using its platform's native format (Hermes `MessageEvent`, openclaw message objects, Agent Zero message types) rather than a cross-platform envelope. + +### Context +Platform-native message formats carry richer context (threading, state, metadata) that the platform's event loop depends on. Wrapping them in a cross-platform envelope would strip useful metadata and increase parsing overhead on every message. + +### Consequences +- Plugin implementations must understand their platform's message types +- Enriched metadata (sender, topic, stream, message_id) is available to the agent naturally +- No serialization/deserialization overhead for local messages +- Each plugin's message handling code is cleaner and more idiomatic \ No newline at end of file diff --git a/docs/adr/ADR-009-error-handling.md b/docs/adr/ADR-009-error-handling.md new file mode 100644 index 0000000..124ca68 --- /dev/null +++ b/docs/adr/ADR-009-error-handling.md @@ -0,0 +1,19 @@ +# ADR 9: Graceful Degradation Error Handling + +**Date**: 2026-04-28 + +### Decision +When an agent is unreachable or times out, the plugin sends a user-visible "processing" or error message to the Zulip topic rather than silently dropping the request. + +### Context +Silent failures are confusing for users — they type @tanko-bot and get no response. A visible status message manages expectations and buys time for retries. The plugin implements: +- Retry: 3 attempts with 5s backoff +- On timeout: `:warning: Tanko is processing your request...` +- On failure: Agent-specific error message +- No persistent queueing in v1 + +### Consequences +- Users always get feedback, even if the agent is slow/down +- Plugin needs a retry timer +- Error messages are configurable per agent +- v2 can add queue-based retry for better reliability \ No newline at end of file diff --git a/docs/adr/ADR-010-monorepo-structure.md b/docs/adr/ADR-010-monorepo-structure.md new file mode 100644 index 0000000..84bce0b --- /dev/null +++ b/docs/adr/ADR-010-monorepo-structure.md @@ -0,0 +1,16 @@ +# ADR 10: Monorepo with Deploy Script + +**Date**: 2026-04-28 + +### Decision +All three platform plugins live in a single Git monorepo (`zulip-platform-plugins`). Updates are deployed via a `deploy.sh` script that SSHes into each CT, pulls the latest code, and restarts the plugin service. + +### Context +A monorepo keeps all 3 plugins in one version-controlled place, making it easy to manage, document, and tag releases together. Separate repos would require managing 3 independent version histories. The `deploy.sh` script is a lightweight deployment pipeline that works today without CI/CD infrastructure. + +### Consequences +- Single `git push` updates all 3 plugins +- Version tags apply to all plugins simultaneously +- deploy.sh must know SSH credentials and paths for all 6 CTs +- Each CT runs `git pull` (no build artifacts) +- Future: can add Gitea Actions webhook for auto-deploy \ No newline at end of file diff --git a/docs/adr/ADR-011-owner-config.md b/docs/adr/ADR-011-owner-config.md new file mode 100644 index 0000000..bab0077 --- /dev/null +++ b/docs/adr/ADR-011-owner-config.md @@ -0,0 +1,15 @@ +# ADR 11: Owner in Per-Agent Config File + +**Date**: 2026-04-28 + +### Decision +Each agent's bot owner is specified in a per-agent `config.yaml` file that lives alongside the plugin deployment on the CT. + +### Context +The bot needs to know its human owner to enforce private topic access control (only the owner can send DMs to the private topic). The config file is explicit, version-controllable, and each agent can have its own independent config. No external service or Zulip API call is needed to determine the owner. + +### Consequences +- config.yaml format includes: agent_name, display_name, owner_email, private_topic, zulip_api_key, zulip_email +- Config is git-versioned alongside the plugin code +- Owner change = update config.yaml + redeploy +- Config is not shared between agents (each is independent) \ No newline at end of file diff --git a/docs/adr/ADR-012-all-bots-scope.md b/docs/adr/ADR-012-all-bots-scope.md new file mode 100644 index 0000000..cafde8b --- /dev/null +++ b/docs/adr/ADR-012-all-bots-scope.md @@ -0,0 +1,15 @@ +# ADR 12: @all-bots Spans All Frameworks + +**Date**: 2026-04-28 + +### Decision +@all-bots triggers ALL agents across all platforms (Hermes, Agent Zero, PI/openclaw), not just agents on the same platform. + +### Context +"All bots" means all bots in the system. If @all-bots only triggered Hermes agents, users in a cross-platform topic would need to @mention 3 separate users. Every bot independently detects `All Bots` in its event stream and forwards the message to its agent, regardless of platform. + +### Consequences +- @all-bots in a topic triggers 6 independent responses +- No central dispatcher needed (each bot detects independently) +- Users get truly cross-platform broadcast +- Agent responses may arrive at different times (platform variance) \ No newline at end of file