| name | give-robin-a-voice |
| description | Set up a two-way chat channel for the user's AI assistant ("Robin") on Telegram OR Slack, end to end: create the bot/app, let Robin push messages to the user, let the user message Robin and get a reply that acts with their context and skills, and deploy it to the cloud so it runs 24/7 with the laptop closed. Auto-detects whether it's running in Claude Code or Codex and works on both. Use when the user says "give Robin a voice", "set up Telegram", "set up Slack", "I want to text/DM my assistant", "two-way bot", "Robin in Slack", "/give-robin-a-voice", or wants their agent to message them. Walks them through it conversationally โ they never edit code by hand. |
| license | MIT |
Give Robin a Voice
You (the assistant) are running a setup wizard. Your job: get the user from
"my assistant can't message me" to "I can message Robin and he messages me back"
โ conversationally, doing the technical work yourself. The user should only
ever (a) click around in Telegram/Slack when you tell them to, and (b) paste a
token or two. You do everything else.
This wizard supports two channels โ Telegram and Slack. The 8 steps below are
identical in shape for both; only the create-the-bot, send, and listen mechanics
differ. Telegram is the default; the exact scripts live in reference.md.
For Slack, every script lives in reference-slack.md โ follow the same steps,
substituting the Slack column.
Keep it warm and concrete. One step at a time. Confirm each step worked before
moving on. If something fails, fix it for them โ don't hand them an error.
The mental model to give them up front (say this):
"Robin's going to get ears. Right now he can leave you a note โ after this you
can text him back and he reads it and acts. The trick is that someone has to be
listening: a tiny always-on program. We'll start it on your machine so you feel
it work, then put it in the cloud so it keeps listening with your laptop closed."
Step 0 โ Detect your setup (automatic โ don't ask)
You're running inside Claude Code or Codex, so you already know which one โ use
that column throughout (CC: claude -p, .claude/; Codex: codex exec,
~/.codex/). Only ask if you genuinely can't tell. There's no tier to choose:
everyone gets the always-on listener, tested locally then deployed to the
cloud. Just tell them what you're setting up and move on.
Step 0.5 โ Pick the channel: Telegram or Slack (ASK this one)
This is the one real choice. Ask: "Where do you want Robin to live โ Telegram or
Slack?" If they already named one ("set up Slack", "Robin in Slack"), skip the
question and go. Rough steer if they're unsure: Telegram if they want it on
their phone as a personal bot; Slack if their team/work already lives there
and they want Robin in a DM next to everything else.
Both reach the exact same outcome and run the same agent โ only the wiring
differs. The mapping:
| Telegram | Slack |
|---|
| Make the bot | @BotFather โ token | Slack App from a manifest |
| Secrets | TELEGRAM_TOKEN + chat_id | SLACK_BOT_TOKEN + SLACK_APP_TOKEN + user id |
| Listen (inbound) | long-poll getUpdates | Socket Mode (WebSocket) |
| Scripts live in | reference.md | reference-slack.md |
If they pick Slack: switch to reference-slack.md now and use it for every
script from here on. The steps below stay the same in shape โ each one points to
the Slack equivalent. Two Slack bonuses worth saying: no VPN needed (Slack isn't
network-blocked like Telegram), and no 409 "two listeners" trap.
Step 1 โ Make the bot (the only thing they do by hand)
Slack: instead of BotFather, they create a Slack App from a manifest
(one paste sets all scopes + Socket Mode), install it, and copy two tokens
(xoxb-โฆ bot + xapp-โฆ app-level). Full recipe: reference-slack.md โ Step 1.
Tell them, in Telegram:
- Open a chat with @BotFather โ send
/newbot.
- Pick a name and a username (must end in
bot).
- Copy the token it gives back (looks like
8123456:AAH...) and paste it to you here.
When they paste the token, do not echo it back. Store it (Step 3).
If BotFather won't open or sending fails on their network: a VPN fixes it for
this one step (creating the bot and, later, sending). Receiving works
regardless. If they can't get a VPN working, they can still do the whole rest
of the workshop on the shared workshop bot โ tell them that and continue.
Step 2 โ Get their chat_id (do this for them)
Slack: the equivalent of chat_id is their user id (Uโฆ) โ fastest is
Slack profile โ โฎ โ Copy member ID. See reference-slack.md โ Step 2.
Robin needs to know where to send. Two ways โ prefer the automatic one:
- Automatic: ask them to open their new bot in Telegram and press Start
(or send
/start) โ this is required; messages sent before Start don't reach
getUpdates. Then you call getUpdates and read result[].message.chat.id.
(See reference.md โ "Get chat_id". Updates can lag a second โ poll a few times.)
- Manual fallback: have them message @userinfobot, which replies with
their numeric id.
Confirm you've got a number. Store it with the token.
Step 3 โ Store the secrets safely (you do this)
Write the token + chat_id to a config file the scripts read โ never hard-code
them, never commit them. Create ~/.robin/telegram.conf (mode 600) and make
sure .robin/ is git-ignored. Exact commands: reference.md โ "Store secrets".
Slack: same idea, into ~/.robin/slack.conf (the two tokens + user id) โ
reference-slack.md โ Step 3.
Step 4 โ Give Robin a mouth (outbound) and test it
Install the telegram-send capability โ a tiny script that calls the
Telegram Bot API to send a message. Same on Claude Code and Codex.
Slack: the same idea is slack-send (calls chat.postMessage) โ
reference-slack.md โ "slack-send". Everything else in this step is identical.
- Drop the
telegram-send skill/script in place (reference.md โ "telegram-send").
- Test it now: send the user a real message ("๐ Robin can talk now.").
- Confirm their phone buzzed before moving on. If it 403s, it's the network /
(for a scheduled cloud run) the allowlist โ see
reference.md โ "Troubleshooting".
This is the push / "talk TO you" half. They've now felt it. Next: the ears.
Step 5 โ Give Robin ears (inbound) โ the listener
Set up a small listener that watches Telegram and, for each message the user
sends, runs their agent (their context, files, skills) and replies. Same idea
on Claude Code and Codex.
Slack: the listener is bridge_slack.py โ it uses Socket Mode (an
outbound WebSocket via slack_bolt, no public URL) instead of long-polling, and
needs one pip install slack_bolt. Same allowlist, same agent(), same
local-then-cloud flow. Full script: reference-slack.md โ "bridge_slack.py".
- Scaffold the listener (
reference.md โ "bridge.py"): it long-polls
getUpdates (offset cursor) โ allowlists their chat_id โ runs
claude -p "<msg>" --resume <session> (or codex exec) in the project dir โ
replies with telegram-send. Optional: stream with sendMessageDraft for a
live "typing" feel.
- Test it locally first โ run it on their machine (
python3 bridge.py),
have them text the bot something simple ("what's on my calendar today?"), and
watch the reply land. This proves the whole loop before any cloud setup.
Locally, Robin runs on the Claude/Codex subscription they're already signed
into โ no API key, no extra cost.
- Say the security rule plainly: an inbound message drives their agent with
their tools. The allowlist (only their chat_id) + the "draft, you confirm"
rule (Step 7) are not optional.
A local listener only answers while their machine is awake. The next step puts
it in the cloud so it keeps listening with the laptop closed โ that's the whole
point of "Robin works while you sleep."
Step 6 โ Deploy it so it runs without your computer (Railway)
Move the listener to a small always-on cloud box so it keeps answering with the
laptop shut. Railway is the right host โ a persistent process that can run
the agent. (Vercel can't: its functions cap at ~10s, which kills an agent run.)
If their local network blocks api.telegram.org, deploying also fixes sending
โ Railway's egress isn't restricted. A BotFather Bot API bot replying to its
owner is fine on Railway; their "userbot" ban targets MTProto user-account
automation, which we don't use. Full recipe: reference.md โ "Deploy to Railway".
Slack: the deploy is the same Railway worker โ Socket Mode dials out, so
still no exposed port. Only diffs: the Dockerfile adds pip install slack_bolt,
and the variables are SLACK_BOT_TOKEN / SLACK_APP_TOKEN / SLACK_USER_ID
(plus the same API key). No 409 to worry about, but still run one home at
a time. Full recipe: reference-slack.md โ "Deploy to Railway".
First โ the cost gate. Inform them, then ASK. Don't deploy silently.
The cloud listener needs a paid API key (ANTHROPIC_API_KEY; Codex: an API
key). Their subscription can't run it โ a headless OAuth token 401s within
~15 min and the terms put always-on automation on the API path. So spell out the
trade and wait for a yes:
"To run 24/7 with your laptop closed costs money: ~$5/mo for the host plus
per-use API tokens, and it needs an API key โ not your Claude/Codex
subscription. Your free option is the local listener we just tested (works
while your machine is on). Want the always-on cloud version, or keep it local?"
If they say no, stop here โ they already have a working two-way Robin. Only
continue if they opt in.
Then walk them through it (you do the wiring; they click + paste):
- A GitHub repo holds the listener + a minimal copy of their Robin context
(CLAUDE.md / AGENTS.md + the skills the listener needs).
reference.md shows
the layout and the Dockerfile.
- On Railway: New Project โ Deploy from GitHub repo, pick that repo.
- The Dockerfile installs the agent CLI on the box (
npm i -g โฆ โ reference.md).
Get the API key for them: Claude โ https://console.anthropic.com (ANTHROPIC_API_KEY);
Codex โ https://platform.openai.com/api-keys (OPENAI_API_KEY).
- Add variables in Railway:
TELEGRAM_TOKEN, TELEGRAM_CHAT_ID, and the key โ
ANTHROPIC_API_KEY for Claude, or OPENAI_API_KEY + AGENT=codex for Codex.
bridge.py reads these from the env. (Codex needs a one-time api-key login,
which the bridge does at boot โ see reference.md.)
- Deploy, then text the bot โ it answers with the laptop closed. Run only one
listener per bot (local or cloud, not both) โ two pollers fight (409).
Cost detail: Railway has no real always-on free tier โ a tiny listener is
~$1/mo of usage on Hobby ($5/mo, includes $5 usage) โ budget ~$5/mo for
the host, plus the per-use API tokens above. Cheaper host, same API tokens:
Fly.io. The only truly-free path stays the local listener (laptop on).
Step 7 โ Wire the trust contract
Make sure the reply logic drafts but does not execute anything
side-effectful (sending email, spending money, deleting) without an explicit
"yes" back over Telegram. Robin proposes โ user replies "go" โ Robin acts.
This is the W1 trust contract, now over text. Confirm it's in the listener prompt.
Step 8 โ Confirm what they have + how to use it
Recap in one line: "Robin can now text you, and you can text him โ say
'what's on today', 'draft a reply to X', 'remind me at 5'. For anything risky
he'll ask first." Point them at reference.md for the scheduled-brief tie-in
(have the morning brief land in the same chat) and troubleshooting.
If anything is still half-working, say exactly what's left and offer to finish
it now or note it for Office Hours. Never end on a silent failure.
Notes for you (the assistant running this)
- Do the work; don't lecture. They're often non-technical and on a desktop app.
- Auto-detect, don't ask: you already know if you're Claude Code or Codex โ
pick the right column from
reference.md and go. No "which tool?" question.
- Co-equal: every step works on Claude Code and Codex โ use the right column
from
reference.md (Telegram) or reference-slack.md (Slack). Do NOT use Claude
Code "Channels" (/telegram:configure): it's research-preview and has no Codex
equivalent. The Bot API / Slack API paths here work for both agents.
- Two channels, one shape: Telegram (
reference.md) and Slack
(reference-slack.md) follow the identical 8 steps โ pick the channel once in
Step 0.5, then read only that channel's reference. Don't mix scripts across them.
- One listener, two homes: the same
bridge.py runs locally (test, $0, laptop
must be on) and on Railway (24/7, ~$5/mo, laptop closed). Local first to feel it
work, then deploy. Reading secrets from a conf file locally vs env vars in the
cloud is the only difference.
- One mechanism throughout: the same
telegram-send / Bot API call powers the
live push, the scheduled brief, and the two-way reply โ there's nothing new to
learn between them.