A Telegram-fronted army of per-user AI agents running on Golem 1.5 Cloud.
Talk to a Telegram bot. Each Telegram user gets their own durable Orchestrator worker on Golem Cloud, which dispatches to 12 specialised agents.
| Agent | Purpose |
|---|---|
| Gateway | Singleton HTTP entrypoint. POST /webhook/telegram + callback-query branch + GET /health. Markdown→plain fallback. |
| Orchestrator (per scope) | LLM brain. Groq Llama-4-Scout function calling. Durable chat history (24-turn rolling). 14 slash commands (bypass LLM). Multimodal input (text + voice + photo). Inline-keyboard email confirm. Per-user TTS reply toggle (/voice on). NaN/invalid-input guards on every tool path. |
| MemoryStore (per scope) | Per-scope shared memory. Facts with Cohere embed-v4.0 semantic recall (keyword fallback). Timeline of events. |
| ReminderAgent (per user) | Durable one-shot and recurring reminders via Golem's .schedule(). Each fire re-arms .schedule() for recurring - durable loop, no setInterval. Writes events to shared MemoryStore. Survives simulate-crash. |
| BriefingScheduler (per user) | Durable daily cron. Self-schedules a recurring briefing - weather + today's reminders + news - every day at the user's chosen UTC hour. |
| BookingAgent (per user) | Saga-pattern transactions. Multi-step trip booking (flight + hotel + calendar) with automatic compensation/rollback if any step fails. |
| WeatherAgent | OpenWeatherMap current + 5-day forecast. |
| SearchAgent | Tavily web search. |
| EmailAgent | Brevo (Sendinblue) send - free tier 300/day, any recipient after one verified sender. |
| NewsAgent | Tavily news topic search (7-day window). |
| CalendarAgent (per user) | Lists shared-memory events. |
| FinanceAgent | exchangerate.host FX/crypto conversion. |
Scope = per-user in DMs.
Telegram webhook (HTTPS)
→ https://<your-app-domain>/webhook/telegram (Golem Cloud)
→ Gateway agent (mount: '/') ← singleton
callback_query → Orchestrator.handleCallback (inline-keyboard taps)
message → Orchestrator.handleMessage (text + voice + photo)
→ Orchestrator(<userId>) ← durable, per-user
↔ MemoryStore(<scope>) (Cohere-embedded facts + timeline)
↔ ReminderAgent(<userId>) (durable .schedule())
↔ BriefingScheduler(<userId>) (durable cron)
↔ BookingAgent(<userId>) (saga + compensation)
↔ Weather | Search | Email | News | Finance | Calendar
→ Gateway.push / pushKeyboard / editText → Telegram sendMessage
12 @agent() classes in one Golem component warpmind:agents. Plus an MCP server on <your-mcp-domain>/mcp (cloud) / warpmind-mcp.localhost:9007/mcp (local) exposing WeatherAgent / SearchAgent / NewsAgent / FinanceAgent over Model Context Protocol.
| Requirement | Implementation |
|---|---|
| Telegram UI | text + voice in + photo + inline keyboards + voice out (TTS) + Mini App WebApp |
| Per-user agents | One Orchestrator(userId) + MemoryStore(userId) + ReminderAgent(userId) per Telegram user |
| Local memory | Orchestrator durable chat history (24-turn rolling) |
| Shared memory | MemoryStore(<scope>) - facts + timeline. ReminderAgent/BookingAgent/CalendarAgent all coordinate through it |
| Reminders (set / list / cancel) | ReminderAgent + /remind, /list, /cancel slash commands |
| Weather | WeatherAgent (OpenWeatherMap) + /weather <city> |
| Web search | SearchAgent (Tavily) |
EmailAgent (Brevo) - with inline ✅/❌ confirm before send |
|
| Free tier only | Groq + OpenWeatherMap + Tavily + Brevo + exchangerate.host + Cohere (free trial). No card anywhere. |
| Deployed on Golem Cloud | Single component, all 12 agents, live HTTP + MCP endpoints |
- Slash commands -
/help,/remind,/every,/weather,/news,/cal,/fx,/list,/cancel,/facts,/forget,/voice,/reset,/start(bypass LLM, faster + cheaper) - Recurring reminders -
/every 1h drink water,/every 1d standup. ReminderAgent re-schedules itself on each fire via.schedule()- durable across restarts, no setInterval anywhere. - Voice input - Groq
whisper-large-v3-turbotranscribes Telegram voice notes - Voice output (TTS) -
/voice ontoggles per-user TTS replies. Groqcanopylabs/orpheus-v1-englishsynthesises a 24kHz WAV (default voiceautumn); Gateway pushes via TelegramsendAudioso duration renders correctly. Markdown stripped before TTS so the voice doesn't read "asterisk". Skips replies over 500 chars. - Image input - Telegram photos → Groq llama-4-scout multimodal vision (OCR + describe)
- Inline keyboard email confirm - bot drafts email, user taps ✅ to send / ❌ to cancel
- Proactive durable cron -
BriefingSchedulerself-schedules a daily 8am briefing (weather + today's reminders + news headline). Showcases Golem's.schedule(Datetime)durable timers. - Saga-pattern transactions -
BookingAgentorchestrates flight + hotel + calendar with automatic rollback on any step failure (usesimulate_hotel_failto force the demo). - Semantic memory - Cohere
embed-v4.0(free trial). Cosine-sim recall when key configured; transparent keyword fallback otherwise. - MCP server -
<your-mcp-domain>/mcp(cloud) /warpmind-mcp.localhost:9007/mcp(local) exposes WeatherAgent / SearchAgent / NewsAgent / FinanceAgent over MCP Streamable HTTP transport. Any MCP client (Claude Desktop, Cursor, MCP Inspector) can talk to it. - Telegram Mini App - tap the
📊 Dashboardmenu button to open a full-screen WebApp inside Telegram. Shows facts, reminders, timeline with one-tap cancel/forget. Static HTML at<your-mini-app-url>; reads/writes via CORS-enabled Cloud endpoints/api/state,/api/cancel-reminder,/api/forget-fact. - Markdown→plain Telegram fallback - bot never silently drops messages when LLM output has unbalanced markdown.
- Conflict detection - orchestrator queries shared timeline via
check_conflictsbefore scheduling. - Input hardening - NaN/invalid-ISO/out-of-range guards on every tool path (k, duration_min, hour_utc, amount, when, currency codes). History snapshot/restore on LLM error so a failed turn never corrupts chat state. Photo download failure now user-visible (no silent drop).
The point of running on Golem: agent state and scheduled work survive crashes with no recovery code. To see it:
# 1. Set a reminder ~60 seconds out via Telegram.
# 2. Kill the worker mid-wait:
golem -C -Y agent simulate-crash 'ReminderAgent("<your-telegram-userid>")'
# 3. Wait. The reminder still fires on time - the worker resumed from its oplog.ReminderAgent.fire is scheduled through Golem's .schedule(Datetime, …), so the
pending invocation lives in the durable oplog, not in memory. A crash, restart, or
redeploy can't drop it. Recurring reminders re-arm .schedule() inside fire()
itself, so the whole loop is durable, not just a single timer.
Reset one user's state (deletes their per-user agents; they respawn on next message):
./scripts/wipe-user.sh <your-telegram-userid>- Golem 1.5.2 - TS SDK
@golemcloud/golem-ts-sdk@1.1.1 - Groq -
meta-llama/llama-4-scout-17b-16e-instruct(chat + function calling + multimodal vision) +whisper-large-v3-turbo(STT) +canopylabs/orpheus-v1-english(TTS) - Telegram Bot API - text, voice, photo, inline keyboards, callback queries
- Cohere -
embed-v4.0for semantic recall (optional, keyword fallback otherwise) - Tavily - web search + news
- OpenWeatherMap - weather
- Brevo - email (free tier 300/day, any recipient after one verified sender)
- exchangerate.host - FX / crypto (no key)
All free tier, no card.
If the bot is already deployed, you don't need any of the setup below:
- Open Telegram and find the bot.
- Send
/start. - Try the prompts in Try it.
Confirm the cloud endpoint is live:
curl -s https://<your-app-domain>/health
# {"status":"ok"}- Node 24+, npm
rustup target add wasm32-wasip2golem+golem-cliv1.5.2 from https://github.com/golemcloud/golem/releases/tag/v1.5.2 (golem-hostwas renamedgolemin 1.5.2)- (optional, only if running locally)
cloudflared
- @BotFather on Telegram →
/newbot→ token - console.groq.com → free signup → API key (
gsk_...) - openweathermap.org → API key
- tavily.com → API key
- brevo.com → free signup → SMTP & API → API keys → create v3 key. Then Senders & IP → Senders → Add a sender → verify your Gmail/whatever via the confirmation email (this must be done or send fails with
400 Sender not valid). Put the verified address inBREVO_SENDER_EMAIL. - dashboard.cohere.com (optional) → API key for semantic recall
Copy the template and fill in real values:
cp .env.example .env.local
$EDITOR .env.localRequired keys (see .env.example):
TELEGRAM_BOT_TOKEN- from @BotFatherGROQ_API_KEY- chat + Whisper STT + Orpheus TTSOPENWEATHERMAP_API_KEY- weather toolTAVILY_API_KEY- web search + newsBREVO_API_KEY+BREVO_SENDER_EMAIL+BREVO_SENDER_NAME- email sendCOHERE_API_KEY(optional) - semantic memory recall; falls back to keyword scoring when unset
npm install
# Create your manifest from the template, then edit the domains inside it:
cp golem.example.yaml golem.yaml
# `cloud` profile ships built in. Trigger OAuth (GitHub web flow):
golem-cli --profile cloud account get
# Register the two domains (httpApi + mcp) on Golem Cloud
golem-cli -C -Y api domain register <your-app-domain>
golem-cli -C -Y api domain register <your-mcp-domain>
# Build + deploy component to Golem Cloud
golem -C -Y deploy
# Set secrets (one-time)
set -a; source .env.local; set +a
for pair in \
"telegramBotToken=$TELEGRAM_BOT_TOKEN" \
"botUsername=$BOT_USERNAME" \
"groqApiKey=$GROQ_API_KEY" \
"openweathermapApiKey=$OPENWEATHERMAP_API_KEY" \
"tavilyApiKey=$TAVILY_API_KEY" \
"brevoApiKey=$BREVO_API_KEY" \
"brevoSenderEmail=$BREVO_SENDER_EMAIL" \
"brevoSenderName=$BREVO_SENDER_NAME" \
"cohereApiKey=${COHERE_API_KEY:-unset}"; do
name="${pair%%=*}"; val="${pair#*=}"
golem-cli -C -Y secret create "$name" --secret-type string --secret-value "\"$val\""
done
# To update an existing secret later:
# golem-cli -C -Y secret update-value <name> --secret-value "\"<new-value>\""
# Wire Telegram webhook to Cloud
curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook?url=https://<your-app-domain>/webhook/telegram"
# Optional: enable the Mini App menu button via @BotFather:
# /setmenubutton -> select bot -> title: "📊 Dashboard" -> url: <your-mini-app-url># 1. Start the local server (ports 9881 control, 9006 HTTP, 9007 MCP)
golem server run &
# 2. Start a cloudflared quick tunnel pointing at the HTTP port
cloudflared tunnel --no-autoupdate --url http://localhost:9006 &
# Note the tunnel URL printed (e.g. https://foo-bar-baz.trycloudflare.com).
# 3. Update golem.yaml -> httpApi.deployments.local.domain to that tunnel hostname.
# 4. Provision the same secrets in the local store (one-time)
set -a; source .env.local; set +a
for pair in \
"telegramBotToken=$TELEGRAM_BOT_TOKEN" \
"botUsername=$BOT_USERNAME" \
"groqApiKey=$GROQ_API_KEY" \
"openweathermapApiKey=$OPENWEATHERMAP_API_KEY" \
"tavilyApiKey=$TAVILY_API_KEY" \
"brevoApiKey=$BREVO_API_KEY" \
"brevoSenderEmail=$BREVO_SENDER_EMAIL" \
"brevoSenderName=$BREVO_SENDER_NAME" \
"cohereApiKey=${COHERE_API_KEY:-unset}"; do
name="${pair%%=*}"; val="${pair#*=}"
golem-cli -L -Y secret create "$name" --secret-type string --secret-value "\"$val\""
done
# 5. Deploy
golem -L -Y deploy
# 6. Point Telegram webhook at the tunnel
curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook?url=<your-tunnel>/webhook/telegram"Open Telegram → find the bot → /start. Then:
/help- full slash command list/remind 30s test warpmind- fires in 30s/every 1h drink water- recurring; loop re-armed durably on every fire/voice on- get TTS audio reply alongside textweather in delhinews about airemember I'm allergic to peanuts→ later,find me a Thai restaurant and email a reservationwhat's on my calendar?book a trip to paris on 2026-06-15 returning 2026-06-22 simulate hotel fail- watch the saga roll backenable daily briefing for Bangalore at 7 UTC- Send a voice note - Whisper transcribes
- Send a photo - vision describes
convert 100 USD to EUR- Tap the
📊 Dashboardmenu button → Mini App opens inside Telegram
warpmind/
├── golem.example.yaml # manifest template (copy to golem.yaml, fill domains)
├── .env.example # secret-key template (copy to .env.local)
├── package.json
├── tsconfig.json
├── LICENSE
├── assets/ # logo + icon
├── scripts/
│ └── wipe-user.sh # delete a user's per-user agents (fresh-start helper)
├── webui/ # Telegram Mini App (static, deploy to Vercel free)
│ ├── index.html
│ ├── config.example.js # copy to config.js, set your cloud API URL
│ └── vercel.json
└── src/
├── main.ts # barrel - imports every agent module
├── types.ts
├── llm.ts # Groq chat + Whisper STT + Orpheus TTS + keyword scorer
├── embed.ts # Cohere semantic embed wrapper
├── telegram.ts # send / sendKeyboard / sendAudio / answerCallback / editText / downloadVoice / downloadFile
└── agents/
├── gateway.ts # @endpoint /webhook/telegram + /api/state + /api/cancel-reminder + /api/forget-fact + /health
├── orchestrator.ts # per-user brain, LLM tools, slash commands, multimodal, TTS, callback handler
├── memory.ts # semantic + keyword recall, timeline, listFactsWithIds
├── reminder.ts # durable .schedule() one-shot + recurring loop
├── briefing.ts # durable cron self-scheduler
├── booking.ts # saga pattern with compensation
├── weather.ts
├── search.ts
├── email.ts # Brevo REST
├── calendar.ts
├── news.ts
└── finance.ts
golem.yaml, .env.local, and webui/config.js hold real deployment values and are gitignored - create them from the *.example.* templates.
MIT.
