BOT_TEMPLATE.md — the Private Human Memory Channel (the reusable primitive)
The asset is NOT "Chanie Bot" or "Manny Bot." It's the pattern — a private, isolated, captured channel between one human and Sam, that any new person becomes by configuration, not engineering. (ZW-ENGINE-V9, 2026-06-07.) Once stable,
@MannyBot/@MildredBot/@MomBotare config — no new architecture.
The pipeline (every bot, identical)
Telegram bot (@XBot)
↓ webhook
<x>-relay Worker — PERSON const + ALLOWED_CHATS allowlist (chat-id gate)
↓ service binding (token)
ops-api /p/<person>/send — token gate; person comes from the URL, not the payload
↓
KV <person>:thread — live cache (50 msgs / 14d) ← key prefix = isolation
D1 messages person=<person> — permanent store ← person field = isolation
↓
relay → Sam (Telegram ping) + event_log audit row
Isolation is validated at FIVE layers (never trust just one)
Per ZW-ENGINE-V9 — client-grade isolation checks all of these, not only bot routing:
1. Bot token — each person has their OWN bot (separate token). A message can only arrive on one bot.
2. Person slug — hardcoded PERSON const in that person's relay (it can only post to its own /p/<person>).
3. Chat-id allowlist — ALLOWED_CHATS env (config) gates who may speak to the bot; others dropped + audited.
4. D1 person field — derived from the URL path, never the payload → rows are scoped.
5. KV key prefix — <person>:thread, derived from the URL path → cache is scoped.
Proven live both directions 2026-06-07 (see
FME_MANNY_ISOLATION_TEST.md): manny ≠ family ≠ chanie at KV and D1.
To add a NEW person (the whole recipe — config, not code)
- BotFather → create
@<Name>Bot→ get the token. cp -r manny-relay <name>-relay; setconst PERSON = "<name>".wrangler secret put ALLOWED_CHATS= that person's chat id (+ Sam's own id while self-testing).wrangler secret put INBOX_SECRET(same shared value);wrangler deploy.- Set the Telegram webhook → the new worker URL.
- (Two-way) add the person to
personBot()in ops-api + set<NAME>_BOT_TOKEN/<NAME>_CHAT_ID. - Self-test from Sam's Telegram → confirm: lands in
person=<name>, no cross-leak, Sam gets the relay, D1 stores. THEN hand it over.
Production-grade checklist (per ZW-ENGINE-V9)
- [x] Allowlist in CONFIG not code —
ALLOWED_CHATSenv. Add/remove people = configuration. - [x] Log rejected attempts —
event=unauthorized_attempt bot=… chatId=…(no relay, no reply). Visible viawrangler tail. - [x] Silent ignore (not "access denied") — reveals nothing to a prober.
- [x] Thread ownership locked —
personcomes from the URL→KV/D1, never an incoming payload field. - [ ]
/statuscommand (authorized only) → bot/KV/D1 healthy + last-stored + version. (TODO — first troubleshooting step; small, add when wiring Manny.) - [ ] Rejected-attempt events → a durable audit (today they're logs; later, an
event_logrow). (TODO)
Self-test gate (before handing a bot to the real person)
Run from Sam's own Telegram (add Sam's chat id to ALLOWED_CHATS), confirm ALL:
- messages land in person='<name>' · Sam receives the relay · D1 stores permanently ·
- other people's bots never see them, and theirs never appear here · rejected non-allowed senders are dropped + audited.
Only after this passes → swap ALLOWED_CHATS to the real person and hand it over.
Pattern proven on chanie-relay (live) + manny-relay (scaffold). The moment a 2nd and 3rd person go live unchanged, this is no longer a custom bot — it's a reusable operating system. That transition IS the sellable asset.