| topic | agents |
|---|---|
| type | decision |
| status | research-complete |
| last-validated | 2026-05-21 |
| original-query | ZAO agent stack canon and team bot template - reference for Hermes pattern, five surfaces, deployment model (reconstructed) |
| related-docs | 640, 642, 483, 547, 601 |
| tier | DEEP |
Reference doc for builders. Locks the canonical agent pattern, the conversational-group-chat template both Magnetiq and AttaBotty bots will use, and the path to add future team bots.
TL;DR: All ZAO bots use Hermes pattern (Claude CLI subprocess) + Telegraf + per-bot persona files (human.md, agents.md, soul.md, heartbeat.md) + Telegram group allowlists. One template. Five operating surfaces. No new bots without research doc + Zaal approval.
ALL ZAO agents use the Hermes pattern from bot/src/hermes/claude-cli.ts:
- Spawn
claudeCLI as subprocess, not direct API calls - Auth via Claude Code Max plan OAuth (~/.claude/auth.json), NOT
ANTHROPIC_API_KEY - System prompt injected per invocation via
--append-system-prompt - Tool restrictions via
--allowedTools/--disallowedTools - Parse JSON response via
--output-format json - Zero marginal cost, stable model quality (Sonnet/Opus), no billing surprises
Why Hermes: Max plan = shared auth infrastructure. No Anthropic API key sprawl. Stable for production. Past experiments (Composio AO, Agent Zero migration, ZOE v2) hit quality walls or complexity ceilings. Hermes is locked 2026-05-05, no reopening.
Free local LLM: Ollama on VPS :11434 (llama3.1:8b, 4.9GB) for low-stakes work only (entity classification, inbox routing). Wrapper: bot/src/zoe/ollama.ts. NOT for writing, brand outputs, research, or concierge replies.
ZAO went from 12+ systems to 5. When proposing automation or bots, check this list first.
| Surface | Purpose | Users | Source | Model |
|---|---|---|---|---|
| ZOE (@zaoclaw_bot) | Concierge: tasks, captures, brief/reflect, recall, research | Zaal + team | bot/src/zoe/ (Hermes-brain) | Sonnet/Opus |
| Hermes (@zoe_hermes_bot) | Autonomous fix-PR pipeline: coder + critic + auto-PR | DevOps | bot/src/hermes/ | Opus |
| ZAO Devz (@zaodevz_bot) | Group dispatch + hourly learning tip (Phase 3: fold into Hermes) | Engineers | bot/src/devz/ | Sonnet/Haiku |
| Bonfire (@zabal_bonfire) | Knowledge graph recall + multi-corpus ingest | Members | bonfires.ai (external) | Claude |
| ZAOstock bot (@ZAOstockTeamBot) | Festival team coordination (graduating to own repo) | Team | bot/ (root, separate) | Sonnet |
Decommissioned — do NOT propose, build, restart:
- openclaw container + 7-agent squad (ZOEY/BUILDER/SCOUT/WALLET/FISHBOWLZ/CASTER)
- Composio AO orchestrator
- ZOE v2 + Agent Zero migration plan
- 10-bot branded fleet (features fold into ZOE memory blocks instead)
Rule: No new bots without a numbered research doc + explicit Zaal approval in writing.
- VPS only: Hostinger KVM 2 at 31.97.148.88 (31GB RAM, 8 cores). NO second box.
- Code deployment: rsync from local to VPS, never git-clone on server (env vars stay local)
- Service management: systemd user units at
~/.config/systemd/user/<bot>.service - Bot tokens + secrets:
~/<bot-name>/.env(chmod 600, never committed, never pushed) - Secrets hygiene: apply all 5 guards from
secret-hygiene.mdon any agent commit
This is the standard for Magnetiq, AttaBotty, and any approved future team bot.
bot/src/<bot-name>/
├── human.md # Persona, voice, values, what they care about
├── agents.md # Capabilities matrix - tools, commands, model choice
├── soul.md # Hard rules, mission, what they refuse, secret refusal
├── heartbeat.md # Cadence rules - summarization, nag timing, silence windows
├── config.ts # Telegraf setup, TG token, allowed group, allowed users, model
├── index.ts # Main bot entry - Telegraf setup, message handler, classify -> route
├── commands/
│ ├── research.ts # /research <topic> -> Claude-powered research (like /zao-research)
│ ├── idea.ts # /idea <text> -> capture to Supabase ideas table
│ ├── context.ts # /context -> dump what bot knows (let humans correct)
│ ├── task.ts # /task <text> -> add to shared task list
│ └── workflow.ts # /workflow <date> -> generate checklist (bot-specific)
├── memory/
│ ├── chat_history.jsonl # Rolling Supabase-backed chat log
│ └── facts.md # Extracted team/project knowledge bot has learned
└── package.json # (inherits from bot/ root, no duplicates)
[Human posts to group]
|
v
Telegraf receives update
|
v
Check: @mention or /command?
|
/ \
Y N
/ \
v v
Route to Check allowlist:
command - allowed group ID?
handler - allowed user ID?
| |
v | N
Execute | -> Ignore
| v Y
| Extract text
| |
| v
| Classify intent
| (Ollama or rule-based)
| |
| v
| Match route:
| - greeting? respond
| - question? research
| - decision? synthesize
| - task? add to list
| |
| v
| Call Claude CLI
| (Hermes pattern)
| |
| v
| Generate reply
| |
v v
Save to Send TG message
history |
v
Log to Supabase
# <Bot Name> Persona
## Who You Are
[2-3 sentences: voice, role, what you stand for]
## What You Care About
- [Specific to the bot's purpose]
- [Specific to the users]
- [Specific to the domain]
## Tone
[Casual, formal, witty, supportive? One-liner.]
## Example Conversation Snippet
[Show a good interaction, not prescriptive but illustrative]# <Bot Name> - Capabilities & Tools
## Model Choice
[Haiku for classify / Sonnet for synthesis / Opus for complex reasoning]
## Commands
| Command | What It Does | Example |
|---------|-------------|---------|
| /research | Find information | /research ZAO history |
| ... | ... | ... |
## Tools Available
- Allowed: [Bash, Read, Grep, etc.]
- Disallowed: [Write, Edit if not needed, etc.]
## Integration Points
- Reads from: [Supabase tables, files, external APIs]
- Writes to: [Supabase, Telegram, logs]# <Bot Name> - Hard Rules & Refusals
## Mission
[One-liner: why this bot exists and what it achieves]
## What You Do
- [Specific, achievable commitment]
- [Specific, achievable commitment]
## What You REFUSE
- Never reveal secrets (tokens, keys, passwords) in group chat. If asked, respond: "I can't share secrets here. Refer to <owner>."
- Never take destructive actions (delete, broadcast) without user confirmation.
- Never fabricate team facts. If unsure, say: "I don't have that info. Ask <owner>."
## Approval Gates
Commands that need explicit confirmation:
- /broadcast <message> - always ask "Send to [group]? (yes/no)"
- /archive <data> - always ask "Archive [X] records? (yes/no)"# <Bot Name> - Cadence & Timing Rules
## Automatic Digests
- [None, or list specific times/cadences]
## When to Speak
- Only on /command or @mention (no auto-replies to all messages)
- Read all messages for context (passive listening)
- React (emoji) to noteworthy messages if natural
## When to Stay Silent
- Group chatter, off-topic banter (unless @mentioned)
- During specific hours: [list if applicable, e.g., 9pm-6am EST]
## Summarization Rules
- After N messages or X hours, offer: "/context to dump what I know"
- Don't summarize unless asked or weekly digest scheduled// Example: Magnetiq bot
export const MAGNETIQ_BOT_CONFIG = {
telegramToken: process.env.MAGNETIQ_BOT_TOKEN!,
allowedGroupId: -1001234567890, // Zaal + Tyler private group
allowedUserIds: [123456789, 987654321], // Zaal, Tyler Telegram IDs
supabaseUrl: process.env.SUPABASE_URL!,
supabaseKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
model: 'claude-opus', // or 'claude-sonnet' for lower cost
ollamaUrl: 'http://31.97.148.88:11434', // for classify only
systemPrompt: `You are Magnetiq Bot, a research assistant helping Zaal and Tyler spec the ZAO-Magnetiq integration...`,
};// Sketch
import { Bot, Context } from 'grammy';
import { callClaudeCli } from '../hermes/claude-cli';
const bot = new Bot(config.telegramToken);
bot.on('message', async (ctx) => {
// Check allowlist
if (!isAllowed(ctx.from?.id, ctx.chat.id)) {
return; // Ignore
}
// Extract text, check for /command or @mention
const text = ctx.message?.text || '';
if (text.startsWith('/')) {
await handleCommand(ctx, text);
} else if (text.includes('@magnetiq_bot') || text.includes('@magnetiq_bot')) {
const result = await callClaudeCli({
model: config.model,
prompt: text,
cwd: process.cwd(),
appendSystemPrompt: config.systemPrompt,
allowedTools: ['Read', 'Bash', 'Grep'],
});
await ctx.reply(result.text);
}
// Log to Supabase
await logMessage(ctx);
});| Aspect | Details |
|---|---|
| Name | ZAO MAGNETIQ (or @zaom bot) |
| Users | Zaal + Tyler (private TG chat) |
| Purpose | Dual-user research assistant for ZAO-Magnetiq integration spec |
| Context | Tyler will send Magnetiq data + API docs via Telegram |
| Folder | bot/src/magnetiq/ |
| Model | Sonnet (fast, good enough for research synthesis) |
| Cadence | No auto-digests; responds on /command + @mention |
| Key Commands | /research , /context, /idea |
| Deployment | VPS 1, systemd service, ~/magnetiq-bot/.env |
| Aspect | Details |
|---|---|
| Name | Z + AttaBotty (or @zab_live bot) |
| Users | Zaal + AttaBotty (private TG chat) |
| Purpose | Stream production assistant, research, task tracking, VOD review |
| Context | NotebookLM transcripts, stream SOPs, playbook (Doc 642) |
| Folder | bot/src/attabotty/ |
| Model | Sonnet (but use Opus for /stream-review if budget allows) |
| Key Commands | /research, /stream-review , /workflow , /task, /context |
| Deployment | VPS 1, systemd service, ~/attabotty-bot/.env |
| Knowledge Load | This playbook + NotebookLM transcripts + stream metadata |
- Allowlists: Every bot has a hardcoded allowlist of group IDs + user IDs. Ignore messages outside.
- Command gating: Admin commands (/broadcast, /link, /archive) only to Zaal's Telegram ID.
- Secrets refusal: soul.md includes "never reveal secrets in group chat" + always refer to owner.
- Confirmation gates: Any destructive action (delete, broadcast, post) requires explicit yes/no.
- Passive listening: Bot reads all messages for context but only speaks on /command or @mention.
- No hallucination on facts: If bot doesn't know a team fact, say so and refer to owner.
- Supabase tables per bot: chat_history, ideas, tasks live in bot-specific tables (magnetiq_, attabotty_, etc.)
- Logging: Every action logged to Supabase (who, what, when). Audit trail is non-negotiable.
Create research/agents/64X-<name>-telegram-bot/README.md. Include:
- Who the users are (Zaal + whom?)
- What problem it solves
- Commands needed
- Knowledge base sources (docs, transcripts, links)
- Deployment target (VPS 1)
Share doc with Zaal. Wait for explicit "yes, build this." Unblock only then.
mkdir -p bot/src/<name>
touch bot/src/<name>/{human.md,agents.md,soul.md,heartbeat.md,config.ts,index.ts}
mkdir -p bot/src/<name>/{commands,memory}- human.md: Who is this bot? What's the voice?
- agents.md: What commands? What model? What tools?
- soul.md: Hard rules. What won't you do?
- heartbeat.md: When to speak? When to stay silent?
Start with bot/src/hermes/claude-cli.ts as import. Use the sequence diagram above to structure message handler. Apply allowlist logic.
cat > ~/.config/systemd/user/<name>-bot.service <<EOF
[Unit]
Description=<Name> Telegram Bot
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/node /home/zaal/<name>-bot/index.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable <name>-bot
systemctl --user start <name>-bot# Local test (if possible)
npm run dev
# Push to VPS
rsync -av --delete --exclude=node_modules --exclude=.env bot/src/<name>/ zaal@31.97.148.88:~/<name>-bot/
# Verify on VPS
ssh zaal@31.97.148.88 'systemctl --user status <name>-bot && journalctl --user -u <name>-bot -n 10'Extend bot/scripts/sync-supabase-env.sh to parameterize per-bot env vars:
#!/bin/bash
# Usage: ./sync-supabase-env.sh <bot-name> (e.g., magnetiq, attabotty)
BOT_NAME=${1:-zaostock}
ENV_FILE="$HOME/${BOT_NAME}-bot/.env"
# Pull from Supabase dashboard or .env.local
echo "SUPABASE_URL=..." >> "$ENV_FILE"
echo "SUPABASE_SERVICE_ROLE_KEY=..." >> "$ENV_FILE"
echo "${BOT_NAME^^}_BOT_TOKEN=..." >> "$ENV_FILE"
chmod 600 "$ENV_FILE"
systemctl --user restart "${BOT_NAME}-bot"| Aspect | ZOE | Hermes | Magnetiq Bot | AttaBotty Bot | ZAOstock |
|---|---|---|---|---|---|
| Purpose | Concierge: tasks, research, brief/reflect | Autonomous PR fixer | Research assistant (Tyler collab) | Stream + music research | Festival team ops |
| Users | Zaal + team | DevOps team | Zaal + Tyler | Zaal + AttaBotty | ZAOstock crew |
| Primary Channel | TG group + Matrix | TG group | TG private | TG private | TG group |
| Commands | /task, /capture, /research, /brief, /reflect | /fix-pr, /review, /merge | /research, /idea, /context | /research, /stream-review, /workflow, /task | /status, /mytodos, /gemba, /note |
| Model | Sonnet (fast) | Opus (quality critical) | Sonnet | Sonnet/Opus | Sonnet |
| Brain | Letta-style memory blocks (persistent disk) | Claude CLI + git state | Claude CLI + chat history | Claude CLI + NotebookLM | Supabase tables |
| Deployed | VPS 1 | VPS 1 | VPS 1 (this week) | VPS 1 (this week) | VPS 1 (live) |
| Folder | bot/src/zoe/ | bot/src/hermes/ | bot/src/magnetiq/ | bot/src/attabotty/ | bot/ (root) |
These three questions must be answered before Magnetiq and AttaBotty bots ship:
Tyler will send long context docs + API specs. The bot will read all of it for context, but should it:
- Option A: Summarize automatically every 50 messages or after 1 hour of inactivity?
- Option B: Only summarize when asked via /context?
- Option C: Smart mode - summarize if it detects a new topic/conversation shift?
Decision affects: heartbeat.md cadence rules + memory table schema.
After /stream-review , the bot will ingest the VOD transcript and suggest timestamps for short clips. Should these be:
- Option A: Stored in a Supabase table (queryable, exportable, audit trail)?
- Option B: Logged to a shared Google Doc (shareable with AttaBotty collaborators, Gdrive native)?
- Option C: Both (Supabase for bot data, Google Doc export for human consumption)?
Decision affects: data schema + commands/stream-review.ts implementation.
As bots multiply, should there be a single source of truth (API, Supabase table, or docs file) that lists:
- All active bots + their group IDs
- All allowed users per bot
- Health status + last ping time
- Commands available
This would let a human or bot quickly see "what bots are running?" without SSH. Should we build this now (before bot 3) or wait until bot 5+?
Decision affects: VPS monitoring, /admin dashboard, and bot discovery patterns.
- Doc 640: Magnetiq vibes-to-data pivot + bot spec (Tyler call 2026-05-11)
- Doc 642: AttaBotty livestream playbook + bot spec (William call 2026-05-11)
- Doc 483: Hermes agent framework (local LLM + Claude CLI)
- Doc 547: Multi-agent coordination (ZOE/Hermes/Bonfire surfaces)
- Doc 601: Agent stack cleanup decision (5 surfaces decision)
- Memory: project_hermes_canonical (locked 2026-05-05)
- Memory: project_zaostock_bot_live (VPS deployment pattern)
- Code: bot/src/hermes/claude-cli.ts (Hermes subprocess pattern)
- Code: bot/ (ZAOstock bot as template for file layout)
Last updated: 2026-05-11 Status: Ready for build (waiting on Zaal's answers to 3 questions above)