| topic | agents |
|---|---|
| type | decision |
| status | research-complete |
| last-validated | 2026-05-21 |
| original-query | How do we build imanagent (Telegram bot for cowork-zaodevz) as Iman's first ground-up VPS agent with a from-scratch walkthrough? (reconstructed) |
| related-docs | 467, 487, 495, 644, 645, 646, 647, 648, 649 |
| tier | STANDARD |
Goal: Audit what's already in
songchaindao-dot/cowork-zaodevz(it's mature — most of the agent is already specified). Design the imanagent as Iman's first ground-up Telegram bot on a fresh VPS. Write the walkthrough Zaal runs through with Iman.
- Standalone, not folded under ZAO/ZAO Festivals. cowork-zaodevz stays under
songchaindao-dot. It's its own thing. - Iman owns it operationally. He drives the agent build, deploys it, runs the VPS, and is the day-to-day maintainer. Zaal is co-lead on the data but Iman is the primary builder/operator.
- Universal action tracker — every brand, every todo. Not just "the 4 cowork users' personal todos." This is THE board where work across every ZAO-adjacent brand lives: COC Concertz (Thy Revolution), WaveWarZ (Samantha, plus Zambia ops), Magnetiq, Attabotty, ZAOstock, BCZ Strategies, ZAO Devz. One place. One audit log.
- Categories likely need to expand to cover the full brand surface (see "Category expansion needed" below). Iman + Zaal decide the final list before the agent build, so the bot's
/list <category>filter matches reality.
| # | Decision | Why |
|---|---|---|
| 1 | Use cowork-zaodevz as the single source of truth for todos across Zaal + Iman + ThyRev + candytoybox. Repo is already substantial: 4-user-aware in env + types (one tweak needed for candytoybox), DMAIC + Kanban structure, GitHub-Contents-API audit log. | Repo is real and shippable today; the brief Zaal described IS what's already built. |
| 2 | Add candytoybox / Samantha as a 4th user. Currently 3: Zaal + Iman + ThyRev. Env + Owner enum + auth need a one-line each. | Zaal's stated workspace = 4 people. |
| 3 | imanagent v1 = Telegram bot on a fresh VPS, slash-command-driven, GitHub Contents API as backend (skip Supabase / dedicated Bot API for v1). | BACKLOG Phase 4 lists 3 prereqs (Supabase + Bot API + agent); for Iman's first build, drop the prereqs and use the same GitHub backend the web app uses today. Storage swaps to Supabase later without changing bot logic — same pattern as the web app's data.ts. |
| 4 | The agent inherits ZOE's lineage (voice, anti-patterns, format rules) per the elder/lineage pattern from doc 648. | Voice consistency across the bot fleet. Iman doesn't define a new voice. |
| 5 | No Claude CLI in v1. Slash commands are deterministic + cheap. Free-text NL goes to a Claude turn in v2. | Iman learns the bot pipeline without the complexity + cost of an LLM subprocess. |
| 6 | Fresh VPS, not VPS 1. Per Zaal: a new box, taught from scratch. Provider-agnostic; this doc uses Hostinger commands but any Ubuntu 24+ VPS works identically after SSH is in. | Iman owns the box, learns sysadmin basics, doesn't risk VPS 1. |
The repo is not empty and not a fresh app to design — it's a working Next.js app with a clear roadmap. Key facts from gh api reads (verified 2026-05-15):
| Surface | Status |
|---|---|
| App | Next.js 15 App Router + React 19 + TS + Tailwind v3. Created 2026-05-07, last push 2026-05-15. |
| Auth | HMAC-signed cookie, one password per user. ZAAL_PASSWORD, IMAN_PASSWORD, THYREV_PASSWORD already wired in .env.example. |
| Backend | data/actions.json persisted via GitHub Contents API — every save is a commit; git history = audit log. |
| Data model | ActionItem with status (TODO/WIP/BLOCKED/DONE), priority (P1/P2/P3), phase (DMAIC), category (14 options), owner enum (Zaal/Iman/Both/ThyRev/Open), taskType (task/work_order/incident/approval_request/goal/maintenance), important + urgent flags, comments[], updates[], activity[]. See src/lib/types.ts. |
| Existing AI | MINIMAX_API_KEY env slot + @anthropic-ai/sdk dependency. src/components/TodoPanel.tsx (27KB) is likely an in-app todo-parsing assistant. Separate from the Telegram imanagent. |
| Supabase prep | supabase/schema.sql (12KB) already drafted. @supabase/supabase-js in deps. Not yet wired — Phase 2. |
| Roadmap | BACKLOG.md lays out Phase 2 (Supabase) → Phase 3 (Bot API) → Phase 4 (Hermes imanagent on VPS) → Phase 5 (Six Sigma metrics). |
| Process docs | SIX-SIGMA.md — DMAIC, 5S, TIMWOODS, weekly review. The discipline that keeps the tracker honest. |
Currently data/actions.json has 3 example items (item 1 done by iman, item 2 done by iman, item 3 TODO/Both). Real data is flowing.
The repo currently has 14 categories (see src/lib/types.ts):
ZAO Devz, Site / Tech, Ops, Bounty, Other, WaveWarZ Zambia, Recording, Distribution, Release, Artist Onboarding, Social, Brand, Content, Campaigns
Per the "universal action tracker" decision, add the missing brand categories before the agent build so /list <category> returns sensible results from day one. Candidate additions Zaal + Iman should agree on:
- COC Concertz — Thy Revolution's brand
- Magnetiq — the brand bot project
- Attabotty — the brand bot project
- ZAOstock — the Oct 3 festival (currently lives in ZAO OS repo; may graduate here)
- BCZ Strategies — Zaal's agency work (clients like Riverside)
- The ZAO (catch-all for org-level work that isn't Devz / WaveWarZ / a specific brand)
Same single PR as the "add Samantha" change — edit Owner + Category together, ship once.
These are the only modifications needed BEFORE the agent build to add candytoybox/Samantha:
.env.example— addSAMANTHA_PASSWORD=changeme-samanthasrc/lib/types.ts— extend theOwnerunion:export type Owner = "Zaal" | "Iman" | "Both" | "ThyRev" | "Samantha" | "Open"; export const OWNERS: Owner[] = ["Zaal", "Iman", "ThyRev", "Samantha", "Open"];
src/lib/auth.ts— addSAMANTHA_PASSWORDto the password map (mirror existing pattern).- Vercel env — add
SAMANTHA_PASSWORDvalue.
Single small PR. Ship it BEFORE the agent build so candytoybox shows up in the owner picker and can log in.
A Telegram bot named @cowork_zaodevz_bot (or similar) running on Iman's fresh VPS. Slash-command-driven. Reads/writes data/actions.json via the same GitHub Contents API the web app uses. No LLM in v1.
Persona inheritance (per doc 648 elder pattern)
The bot's persona file (~/.cowork-agent/persona.md on Iman's VPS) inherits ZOE's voice + anti-patterns + format rules verbatim. Override ONLY the identity + domain + tools sections. Use the bootloader template at ~/.zao/zoe/bootloader-template.md on Zaal's VPS as the seed.
Identity (override): "imanagent — the cowork-zaodevz Telegram bot. Talks to Zaal, Iman, ThyRev, Samantha. Manages the shared todo board."
Domain (override): "Read + write data/actions.json in the cowork-zaodevz repo via GitHub Contents API. Surface today's open items, take new todos, mark done, log activity. No outside-of-repo work."
Tools (override): "GitHub Contents API only. No Claude CLI, no shell access, no other repos. v2 may add Claude CLI for natural-language todo parsing."
| Command | Who | What it does |
|---|---|---|
/start |
anyone allowlisted | "imanagent online. Try /mine /list /add" |
/mine |
anyone allowlisted | list the caller's open items (TODO + WIP), sorted by priority then age |
/list |
anyone allowlisted | list ALL open items grouped by owner |
/list <category> |
anyone allowlisted | filter by category (e.g. /list WaveWarZ Zambia) |
/add <title> |
anyone allowlisted | creates a new item, defaults: owner = caller, status = TODO, priority = P2, category = Other, phase = Define |
/done <id> |
anyone allowlisted | marks item id as DONE; sets completedAt + completedBy |
/wip <id> |
anyone allowlisted | moves item to WIP |
/blocked <id> <reason> |
anyone allowlisted | moves item to BLOCKED with a note |
/assign <id> <Zaal|Iman|ThyRev|Samantha|Both> |
anyone allowlisted | changes owner |
/daily |
Zaal only (admin) | force-runs the daily summary (the 9am cron sends one automatically) |
Allowlist: a JSON file on the VPS at ~/.cowork-agent/users.json mapping Telegram user IDs → display names. Same shape as ZOE's groups.json allowlist.
A node-cron job inside the bot process posts to a configured Telegram chat (group or DM): open items per owner + count of blockers + items completed in the last 24h. Use the existing imanprojects audit data (every save = a commit, every commit ≈ an activity event).
src/storage.ts in the agent has two functions:
export async function readActions(): Promise<{ items: ActionItem[]; sha: string }>;
export async function saveActions(items: ActionItem[], commitMessage: string): Promise<void>;v1 implementation: hits https://api.github.com/repos/songchaindao-dot/cowork-zaodevz/contents/data/actions.json with a fine-grained PAT. Same function signatures stay when Phase 2 swaps to Supabase — the bot doesn't change, only the storage module's body changes. Mirrors the web app's src/lib/data.ts pattern.
- No LLM in v1. No
@anthropic-ai/sdk, no Claude CLI subprocess. Pure deterministic command parsing. - No Supabase in v1. Backend = GitHub Contents API (same as the web app today).
- No Bot API server (BACKLOG Phase 3). The bot writes directly to GitHub.
- No web-side integration with the agent — they share the same data file via GitHub, that's it.
- No natural-language todo parsing. "Drop coffee with Roddy on the list" → user has to type
/add coffee with Roddy. v2 adds NL parsing via Claude CLI.
- Runtime: Node 22 + TypeScript via
tsx(no build step — match the ZOE bot/src pattern) - Telegram client:
grammy(same as ZOE) - GitHub client:
@octokit/rest(well-known, typed) - Cron:
node-cron(same as ZOE scheduler) - No Anthropic / OpenAI / Minimax dep in v1
Why this stack: Iman learns the same pattern Zaal already uses for ZOE. Familiar to anyone reading the ZAO OS repo. Zero new concepts.
This is the part Zaal runs through with Iman in person or on a call. Linear, copy-paste-friendly. Each step is something Iman does on his own machine and his new VPS. Provider used here = Hostinger (matches Zaal's VPS 1); the exact same commands work on DigitalOcean / Hetzner / Linode after SSH is in.
- A GitHub account with access to
songchaindao-dot/cowork-zaodevz(Zaal adds him as a collaborator) - A Telegram account on his phone
- A laptop with a terminal (Mac terminal, iTerm, or WSL on Windows)
- ~$5/mo for a small VPS
- Sign up at hostinger.com → VPS Hosting → KVM 1 (1 vCPU, 4GB RAM, $5-6/mo is plenty for one agent).
- Choose Ubuntu 24.04 LTS as the OS.
- Choose a strong root password and write it down once (he'll disable root login after Step 2).
- Note the IPv4 address Hostinger assigns. He'll use it everywhere as
<VPS_IP>.
On his laptop:
# 1. Generate an SSH key if he doesn't have one
ls ~/.ssh/id_ed25519.pub 2>/dev/null || ssh-keygen -t ed25519 -C "iman@cowork-agent" -f ~/.ssh/id_ed25519 -N ""
# 2. Copy his public key to the VPS (uses root pass once, the last time)
ssh-copy-id root@<VPS_IP>
# 3. Confirm key login works
ssh root@<VPS_IP> "echo connected"Still on his laptop, SSH'd in as root:
ssh root@<VPS_IP>
# Inside the VPS:
adduser iman # set a password he remembers
usermod -aG sudo iman
mkdir -p /home/iman/.ssh
cp /root/.ssh/authorized_keys /home/iman/.ssh/
chown -R iman:iman /home/iman/.ssh
chmod 600 /home/iman/.ssh/authorized_keys
# Disable root SSH (optional but recommended)
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart ssh
exitNow from his laptop:
ssh iman@<VPS_IP> # should land him in /home/iman without a password promptInside the VPS as user iman:
# Install node 22 (via nodesource)
sudo apt update
sudo apt install -y curl git build-essential
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
# Verify
node --version # expect v22.x
npm --version
# Install tsx globally so we can run TypeScript directly
sudo npm install -g tsxcd ~
git config --global user.name "Iman"
git config --global user.email "iman@<his-email>"
git clone https://github.com/songchaindao-dot/cowork-zaodevz.git
cd cowork-zaodevzThis is the SAME repo the web app deploys from. The agent will live in a new directory inside it (agent/) so the web app, the agent, and the data file all sit in one place — no syncing between two repos.
On his phone (or the Telegram web app):
- DM
@BotFather. /newbot→ pick a name like "Cowork ZAO Devz Bot" and a username ending inbot(e.g.cowork_zaodevz_bot).- BotFather sends back a token like
123456:ABC-DEF.... This is a secret — don't paste it anywhere public. /setprivacy→ choose Disable. (Lets the bot read all group messages later if needed; v1 only needs DMs but disabling is the standard ZAO pattern.)/setcommands→ paste:mine - my open items list - all open items add - add a new item done - mark an item DONE wip - move item to WIP blocked - move item to BLOCKED assign - reassign an item start - show the help
The bot writes to data/actions.json via the GitHub API — it needs a token.
- https://github.com/settings/tokens?type=beta → "Generate new token"
- Name it
cowork-zaodevz imanagent. - Resource owner:
songchaindao-dot. - Repository access: "Only select repositories" → pick
cowork-zaodevz. - Permissions → Repository permissions → Contents: Read and write.
- Generate token. Copy it once — GitHub won't show it again.
The bot allowlists by Telegram numeric user ID, not username (usernames can change). To find each person's ID:
DM @userinfobot on Telegram
It replies with the user's numeric ID. Get all 4: Zaal, Iman, ThyRev, Samantha. Save them.
In the VPS, inside ~/cowork-zaodevz:
mkdir -p agent/src
cd agent
npm init -y
npm install grammy @octokit/rest node-cron
npm install -D @types/node @types/node-cron typescript tsxCreate agent/.env:
cat > .env << 'EOF'
TELEGRAM_BOT_TOKEN=<paste-the-botfather-token>
GITHUB_TOKEN=<paste-the-fine-grained-pat>
GITHUB_REPO=songchaindao-dot/cowork-zaodevz
GITHUB_BRANCH=main
ALLOWLIST_USER_IDS=<zaal_id>,<iman_id>,<thyrev_id>,<samantha_id>
USER_NAMES=<zaal_id>:Zaal,<iman_id>:Iman,<thyrev_id>:ThyRev,<samantha_id>:Samantha
ADMIN_USER_IDS=<zaal_id>
DAILY_DIGEST_CHAT_ID=<the-chat-id-where-9am-digest-posts>
EOF
chmod 600 .envCreate agent/src/storage.ts:
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const [owner, repo] = (process.env.GITHUB_REPO ?? '').split('/');
const branch = process.env.GITHUB_BRANCH ?? 'main';
export interface ActionItem {
id: string;
title: string;
createdBy: string;
owner: string;
status: 'TODO' | 'WIP' | 'BLOCKED' | 'DONE';
category: string;
priority: 'P1' | 'P2' | 'P3';
important: boolean;
urgent: boolean;
completedAt: string;
completedBy: string;
phase: string;
due: string;
notes: string;
createdAt: string;
updatedAt: string;
}
interface FileResponse { items: ActionItem[]; updatedAt: string }
export async function readActions(): Promise<{ data: FileResponse; sha: string }> {
const res = await octokit.repos.getContent({ owner, repo, path: 'data/actions.json', ref: branch });
if (Array.isArray(res.data) || res.data.type !== 'file') throw new Error('actions.json missing');
const content = Buffer.from(res.data.content, 'base64').toString('utf8');
return { data: JSON.parse(content), sha: res.data.sha };
}
export async function saveActions(data: FileResponse, sha: string, message: string): Promise<void> {
await octokit.repos.createOrUpdateFileContents({
owner, repo, branch,
path: 'data/actions.json',
message,
content: Buffer.from(JSON.stringify(data, null, 2)).toString('base64'),
sha,
});
}Create agent/src/index.ts:
import 'dotenv/config';
import { Bot, Context } from 'grammy';
import { readActions, saveActions, type ActionItem } from './storage.js';
const ALLOWLIST = new Set((process.env.ALLOWLIST_USER_IDS ?? '').split(',').map((s) => Number(s.trim())));
const ADMINS = new Set((process.env.ADMIN_USER_IDS ?? '').split(',').map((s) => Number(s.trim())));
const NAMES = new Map(
(process.env.USER_NAMES ?? '').split(',').map((p) => {
const [id, name] = p.split(':');
return [Number(id), name];
}),
);
function nameFor(id: number): string {
return NAMES.get(id) ?? `user:${id}`;
}
function allowed(ctx: Context): boolean {
const id = ctx.from?.id;
return id !== undefined && ALLOWLIST.has(id);
}
const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN!);
bot.command('start', async (ctx) => {
if (!allowed(ctx)) return;
await ctx.reply('imanagent online. Try /mine /list /add <title> /done <id>');
});
bot.command('mine', async (ctx) => {
if (!allowed(ctx)) return;
const me = nameFor(ctx.from!.id);
const { data } = await readActions();
const mine = data.items.filter(
(i) => (i.owner === me || i.owner === 'Both') && i.status !== 'DONE',
);
if (mine.length === 0) { await ctx.reply(`${me}: no open items.`); return; }
const lines = mine
.sort((a, b) => a.priority.localeCompare(b.priority))
.map((i) => `[${i.priority}] [${i.status}] (${i.id}) ${i.title}`);
await ctx.reply(`Open for ${me} (${mine.length}):\n${lines.join('\n')}`);
});
bot.command('list', async (ctx) => {
if (!allowed(ctx)) return;
const filter = ctx.match?.toString().trim();
const { data } = await readActions();
let items = data.items.filter((i) => i.status !== 'DONE');
if (filter) items = items.filter((i) => i.category.toLowerCase().includes(filter.toLowerCase()));
if (items.length === 0) { await ctx.reply('No open items.'); return; }
const byOwner: Record<string, string[]> = {};
for (const i of items) {
const o = i.owner || 'Open';
byOwner[o] = byOwner[o] ?? [];
byOwner[o].push(` [${i.priority}] [${i.status}] (${i.id}) ${i.title}`);
}
const lines = Object.entries(byOwner).map(([o, list]) => `${o}:\n${list.join('\n')}`);
await ctx.reply(lines.join('\n\n'));
});
bot.command('add', async (ctx) => {
if (!allowed(ctx)) return;
const title = ctx.match?.toString().trim();
if (!title) { await ctx.reply('Usage: /add <title>'); return; }
const me = nameFor(ctx.from!.id);
const { data, sha } = await readActions();
const newId = String(data.items.length > 0 ? Math.max(...data.items.map((i) => Number(i.id) || 0)) + 1 : 1);
const now = new Date().toISOString();
const item: ActionItem = {
id: newId, title,
createdBy: me, owner: me,
status: 'TODO', category: 'Other', priority: 'P2',
important: false, urgent: false,
completedAt: '', completedBy: '',
phase: 'Define', due: '', notes: '',
createdAt: now, updatedAt: now,
};
data.items.push(item);
data.updatedAt = now;
await saveActions(data, sha, `bot: add "${title.slice(0, 60)}" via ${me}`);
await ctx.reply(`Added (${newId}) "${title}" -> ${me}.`);
});
bot.command('done', async (ctx) => {
if (!allowed(ctx)) return;
const id = ctx.match?.toString().trim();
if (!id) { await ctx.reply('Usage: /done <id>'); return; }
const me = nameFor(ctx.from!.id);
const { data, sha } = await readActions();
const item = data.items.find((i) => i.id === id);
if (!item) { await ctx.reply(`No item with id ${id}.`); return; }
const now = new Date().toISOString();
item.status = 'DONE';
item.completedAt = now;
item.completedBy = me;
item.updatedAt = now;
data.updatedAt = now;
await saveActions(data, sha, `bot: done (${id}) by ${me}`);
await ctx.reply(`Done (${id}) "${item.title}".`);
});
// (Add /wip /blocked /assign /daily as the same pattern. Save for later.)
await bot.api.getMe().then((me) => console.log(`[imanagent] up as @${me.username}`));
await bot.start();Add a package.json script:
{
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts"
}
}And install dotenv:
npm install dotenvTest interactively:
cd ~/cowork-zaodevz/agent
npm run startIman DMs @cowork_zaodevz_bot from his phone — /start → "imanagent online." /mine, /add test, /done 1 etc.
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/imanagent.service << 'EOF'
[Unit]
Description=cowork-zaodevz imanagent
After=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/cowork-zaodevz/agent
ExecStart=/usr/bin/tsx %h/cowork-zaodevz/agent/src/index.ts
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
EnvironmentFile=%h/cowork-zaodevz/agent/.env
Environment=NODE_ENV=production
[Install]
WantedBy=default.target
EOF
# Enable user services to run without an active login session
sudo loginctl enable-linger iman
# Start + enable
systemctl --user daemon-reload
systemctl --user enable --now imanagent
systemctl --user status imanagent
journalctl --user -u imanagent -f # follow logsThat last command tails the bot live. Send a message in Telegram and watch the journal scroll.
Inside agent/src/index.ts, add at the bottom (before await bot.start()):
import cron from 'node-cron';
cron.schedule('0 9 * * *', async () => {
const chatId = process.env.DAILY_DIGEST_CHAT_ID;
if (!chatId) return;
const { data } = await readActions();
const open = data.items.filter((i) => i.status !== 'DONE');
const lines = open.map((i) => `[${i.priority}] [${i.status}] ${i.owner}: ${i.title}`);
await bot.api.sendMessage(chatId, `Daily digest (${open.length} open):\n${lines.join('\n')}`);
}, { timezone: 'UTC' });Add DAILY_DIGEST_CHAT_ID to .env (use @userinfobot for a personal chat ID, or @username_to_id_bot in a group).
Restart: systemctl --user restart imanagent.
systemctl --user status imanagent # is it running?
journalctl --user -u imanagent -n 50 # last 50 log lines
journalctl --user -u imanagent -f # live tail
systemctl --user restart imanagent # restartCommon issues:
- Token rejected — re-check
TELEGRAM_BOT_TOKENmatches what BotFather sent - GitHub 404 on read — check the PAT has Contents: Read+Write on
cowork-zaodevzspecifically - GitHub 409 conflict on save — somebody else just saved; the agent should re-read + retry (v2 hardening — for v1, the bot replies "conflict, retry" and the user resends)
- Allowlist blocking valid user — confirm the numeric ID in
.envmatches @userinfobot's output exactly
- Doc 467 — original bot-fleet pattern
- Doc 495 — team-bot patterns
- Doc 644 — team-bot template
- Doc 647 — agent quality (persona inheritance, memory, evals)
- Doc 648 — elder/lineage pattern + Tailscale federation
- Doc 649 — repo ecosystem context
- Memory
project_hermes_canonical— Hermes is the ZAO agent framework - Memory
project_zoe_soul_architecture— ZOE's runtime, the lineage elder
| Action | Owner | Type | By When |
|---|---|---|---|
| Add Samantha/candytoybox as the 4th user (env + Owner enum + auth.ts) | @Zaal | small PR | Before the agent build |
Iman gets repo collaborator access on songchaindao-dot/cowork-zaodevz |
@Zaal | GitHub setting | Before Step 5 |
| Run through Steps 1-11 with Iman in person or on a call | @Zaal + @Iman | session | Within 1 week |
After v1 works: open a PR adding agent/ to the repo (with the index.ts + storage.ts from Step 9) |
@Iman | PR | Right after the walkthrough |
| Agree the final category list (existing 14 + COC Concertz + Magnetiq + Attabotty + ZAOstock + BCZ Strategies + The ZAO?) and ship in the same PR as the Samantha-add | @Zaal + @Iman | small PR | Before the agent build |
| Brief candytoybox + ThyRev that cowork-zaodevz is now their shared workplace, walk them through login | @Iman | comms + onboarding session | After the 4-user PR ships |
v2 add Claude CLI for natural-language todo capture ("todo: ship X" -> /add flow) |
@Iman | PR | After v1 stabilizes |
| v2 swap GitHub Contents API for Supabase (BACKLOG Phase 2 + 3) | @Zaal + @Iman | PR sequence | After v1 is stable |
gh api repos/songchaindao-dot/cowork-zaodevz— repo metadata, tree, BACKLOG.md, SIX-SIGMA.md, README.md, CLAUDE.md,.env.example,src/lib/types.ts,package.json, sampledata/actions.json(verified 2026-05-15)- grammy.dev — Telegram bot framework used here + by ZOE
- docs.github.com — Contents API — the storage backend the repo + the agent share
- octokit/rest.js — official typed GitHub client
- ZAOOS
bot/src/zoe/index.ts— the Hermes-style bot pattern this design mirrors ~/.zao/zoe/bootloader-template.md(on VPS 1) — the child-bot persona seed