A chat-resident assistant for WhatsApp and Telegram. Claude, Codex, Grok, or Gemini under the hood, durable SQLite queues, per-sender timezone scheduling, two-track architecture so browser work never blocks the chat.
WhatsApp / Telegram ─► inbound ─► chat workers ─► outbound ─► WhatsApp / Telegram
│ ▲
├──────► async / browser ─┤
└──────► memory_writes ───┘
- Long-term memory per person, per chat, per topic. Files on disk. The agent decides what's worth keeping; background workers consolidate while you're not chatting.
- A relevance watchlist. Open loops the agent tracks on your behalf — questions you'd forget, things you're waiting on — surfaced naturally when the moment matches. Built like external working memory for the user.
- Scheduling in the sender's timezone. Natural language →
[REMIND: 2026-05-26 09:00 — ...]or[CRON: 0 9 * * 1 PROMPT — ...]. Fires at the user's wall-clock 9am, not the server's. Cron variants: deliver text, run AI, kick off async work, or drive a browser. - A real Chrome. Browser delegation via
[ASYNC-BROWSER: ...]to a parallel provider session on a shared logged-in Chrome over CDP. TikTok, Instagram, anywhere the owner is logged in. SSH-tunneled noVNC for setup. - Per-reply footer with model, thinking, and confirmation tags. Every side effect from the turn is visible:
_9.9s · gpt-5.6-sol · xhigh · fresh · +remind · +thread-new · +digest_. No guessing whether a schedule actually got created. - Default-deny chat activation. Groups and DMs only answer when their own
triggerModeis set inconfig/access.json; missing meansoff. Per-role token quotas, file-size caps, tool restrictions.
For the why behind these — claim primitives, tag-as-side-effect channel, per-category learning, provider abstraction, the trade-offs that didn't survive the first revision — see docs/architecture.md.
npm install -g @anthropic-ai/claude-code
npm install -g @c4t4/heyamigo
claude # log in once, then exit
heyamigo setup # wizard: pair WhatsApp, pick personality
heyamigo start # background, auto-restart
heyamigo logs # tailTelegram is optional. Create a bot with BotFather, set telegram.enabled: true and telegram.botToken in config/config.json, then allow users/groups in config/access.json. Telegram user keys use tg_<user_id>; Telegram group entries use addresses like tg:group:-1001234567890.
Other providers:
- Codex: install
@openai/codexand setai.provider: "codex"inconfig/config.json. - Grok Build: install with
curl -fsSL https://x.ai/cli/install.sh | bash, rungrok login, and setai.provider: "grok". Chat and non-browser async work are supported; browser jobs fail closed because Grok does not currently expose invocation-scoped MCP isolation. - Gemini: set
ai.provider: "gemini". Heyamigo uses the already-installedgeminiCLI with--yolo, pinsgemini-3.6-flashby default, and uses the CLI's existing login for chat, async, and task-scoped browser jobs.
HeyAmigo ships with a provider-scoped connector to cloud Amigospace. Authorize the installation once; the command stores only a rotating refresh credential in an owner-only file and enables the connector:
heyamigo amigospace connect
heyamigo amigospace statusThe connector is injected into Claude, Codex, and Gemini invocations only when the active role has
tools: "all" or explicitly allows mcp__amigospace__*. The default user and guest roles
therefore cannot read the owner's workspace. Its path is HeyAmigo → bundled authenticated MCP connector → agentgateway → space.heyamigo.org/mcp; workspace and principal selection come from the
validated access token, never model arguments. Access and refresh tokens are never passed through
model context, command arguments, environment variables, or normal logs. Grok remains fail-closed
because its current CLI does not provide invocation-scoped MCP configuration.
When the network or Amigospace is unavailable, HeyAmigo itself continues to work but Amigospace tools fail explicitly. There is no local Amigospace service and no silent knowledge-store fallback that could create a divergent copy.
Browser jobs use browser.cdpUrl (default http://127.0.0.1:9222). Claude, Codex, and Gemini receive an invocation-scoped Playwright MCP pointing only at that endpoint; ambient Chrome integrations and stale global Playwright entries are not available to the browser worker. A shared SQLite lease registry identifies tabs by stable CDP target ID and filters each MCP to only the tabs owned by its task. Up to browser.maxWorkers tasks (default 3) can therefore drive separate background tabs in parallel without mutable global tab indexes. A task can own several tabs, automatically adopts popups opened by an owned tab, and can explicitly claim an existing user tab by stable ID; claimed user tabs stay open, while task-created tabs are cleaned up. Before work is claimed, heyamigo also opens the browser-level CDP WebSocket and runs Browser.getVersion. If that check fails, the job stays pending instead of falling back to another browser.
Chrome has its own lifecycle, separate from the bot. Every automatic path uses the single authenticated VNC profile at ~/.config/google-chrome-novnc; the profile is not configurable, so setup and runtime cannot silently create or select another one. Use heyamigo chrome status|start|stop|restart. heyamigo chrome restart also recovers the Xvfb, x11vnc, and noVNC stack using the same hardened launcher as setup; it reports missing packages instead of installing them and prints the exact profile it loaded. noVNC binds directly to port 6090 by default, or automatically uses backend port 6080 when an nginx frontend already owns 6090. Generated viewer links enable local scaling by default with resize=scale. The command matches both CDP port and profile path before operating and refuses to touch an unknown browser. heyamigo restart continues to restart only the Node bot.
| Command | What it does |
|---|---|
/reset |
Fresh AI session for this chat |
/status |
Session info, context utilization |
/thinking [level] |
Show or set this chat's Codex reasoning level; default clears the override |
/personality [name] |
Show or globally switch personality; admin-only changes reset all provider sessions |
/queues |
Live queue depths |
/crons · /reminders |
List recurring schedules + one-shots (token cost included) |
/threads |
List the relevance watchlist; resolve / drop / pause / weight |
/digest |
Force a memory consolidation now |
config/access.json. Three default roles, easily extended.
| Role | Memory | Tools | Notes |
|---|---|---|---|
| admin | everything | all | unrestricted |
| user | own profile | none | can't see other users or internals |
| guest | none | none | prompt-injection resistant |
config/personalities/*.md — system-prompt fragments that define the bot's voice. The default (unfiltered-realist.md) is direct, politically non-aligned, evidence-first, and explicitly avoids moral policing. Use /personality unfiltered-realist (or another installed name) to switch globally at runtime. The command resets provider sessions so an old session cannot retain the previous prompt.
A VPS (Hetzner, DO) at ~$5/mo is the path of least resistance. Home server or Raspberry Pi also fine. Needs Node 18+, a persistent filesystem, and outbound access to the enabled chat channels. Not serverless-compatible.
The bot writes markdown files under storage/memory/ as it learns. git init in your project root and commit periodically gives you a readable diff of what the assistant has come to believe about people and topics. Skip storage/auth/ (WhatsApp keys) and storage/logs/.
MIT. Built by Catalin Waack · LinkedIn.
