Not a standalone agent framework — a hermit crab that lodges inside Claude Code. One command bootstraps a Telegram-connected Claude Code agent with persona, long-term memory, scheduler, and browser automation.
Claude Code closes its third-party subscription surface, so I built an agent that lodges inside Claude Code itself — fusing the best ideas from three agent-harness frameworks.
| Borrowed from | What it contributed |
|---|---|
| Claude Code | The shell. Every agent literally runs inside claude --dangerously-skip-permissions. Plugins, MCP, tools, hooks — all native, nothing reimplemented. |
| OpenClaw | Self-managed-browser pattern. Shaped scripts/chrome-launcher.sh, scripts/browser-lock.sh, per-agent Chrome profile + CDP reuse, stealth-wrapped Playwright. |
| Hermas Agent | Autonomous-evolution pattern and memory-module design. SOUL.md + MEMORY.md + daily memory/YYYY-MM-DD.md logs + dream-style consolidation all inherited. |
# Prereqs: Claude Code installed & logged in, Node 18+, brew install tmux jq, bun installed
npx create-hermit-agent
cd asst && ./start.shOpen Telegram, DM the bot you just registered with @BotFather. First DM triggers a one-shot orientation — then the agent stays out of your way.
You: remind me at 3pm to call mom
asst: scheduled — I'll ping you at 3pm today.
| Capability | Detail |
|---|---|
| Persona | SOUL / IDENTITY / USER / AGENTS / TOOLS / MEMORY.md loaded every session. Edit the files → edit the agent. |
| Long-term memory | Daily logs at memory/YYYY-MM-DD.md, curated long-term at MEMORY.md. Survives restarts. |
| Telegram I/O | Native reply / react / edit / attachment download via @claude-plugins-official/telegram. Group-chat etiquette built in. !! sigil routes messages to Claude Code commands (!!compact, !!model, !!status). |
| Lifecycle | start.sh + restart.sh wrap the agent in a named tmux session. Push alerts when context crosses 100k / 200k / ... / 950k thresholds, or when tool use gets chatty. |
| Scheduler | Three tiers — session-only cron skill, cross-restart HEARTBEAT.md, OS-durable launchd plists. |
| Browser | Dedicated Chrome profile + CDP + Playwright + stealth-init anti-detection. |
| Multi-agent | provision-agent skill spawns siblings at ../<name>/ with their own bot tokens. Optional 10-min digest LaunchAgent. |
| Safety | Images forced through safe-image.sh resize (≤1800px long edge). Tokens stored at mode 600 outside the repo. Stop hook blocks turn-end if a Telegram DM got no reply. PreToolUse hook strips markdown from outbound Telegram replies so stray **bold** / # headers don't land as literal noise in the chat. |
┌────────────── Your Mac ──────────────┐ ┌─ Telegram ─┐
│ │ │ │
│ tmux session claude-asst │ │ Bot API │
│ ┌───────────────────────────────┐ │ │ │
│ │ claude (the borrowed shell) │ │ │ │
│ │ ┌────────┐ ┌───────────────┐ │ │ │ │
│ │ │Persona │ │ Skills + Hooks│ │ │◄─────►│ @yourbot │
│ │ │*.md │ │ restart · cron│ │ │ │ │
│ │ │memory/ │ │ provision ... │ │ │ │ │
│ │ └────────┘ └───────────────┘ │ │ │ │
│ │ Telegram plugin (bun) │ │ │ │
│ └────────────────────────────────┘ │ │ │
│ │ │ │
│ ~/.claude/channels/telegram-asst/ │ │ │
│ (bot token — not in the repo) │ │ │
└───────────────────────────────────────┘ └────────────┘
Higher-res SVG: assets/arch.svg.
Prereqs (macOS):
- Claude Code — installed and logged in (
claude login) - Node ≥ 18
brew install tmux jqcurl -fsSL https://bun.sh/install | bash
Scaffold:
npx create-hermit-agentThe CLI asks for a Telegram bot token (@BotFather) and your own Telegram user ID (@userinfobot). Then it creates ./asst/, installs the Telegram plugin at project scope, and writes the token to ~/.claude/channels/telegram-asst/.env (mode 600).
Start:
cd asst && ./start.shThe agent now runs in a detached tmux session named claude-asst. DM the bot.
On first contact, asst sends a one-shot orientation — how to talk to it, available !! commands, how to spawn more agents — then deletes its own FIRST_RUN.md so it never greets you again.
Don't run npx create-hermit-agent a second time. Tell asst:
Create a new agent called
github-botwith token123:ABC.... Purpose: triage my GitHub notifications.
asst's provision-agent skill scaffolds a sibling at ../github-bot/, installs its plugin, starts it in a claude-github-bot tmux session, and replies with the new bot's @handle.
Agents are fully independent: separate bot tokens, separate memory, separate folders.
Edit these in the agent folder:
| File | What to put there |
|---|---|
IDENTITY.md |
Name, vibe, one-line purpose |
USER.md |
You — pronouns, timezone, notes |
AGENTS.md |
<!-- MISSION-START --> block — this agent's mission |
TOOLS.md |
<!-- AGENT-SPECIFIC-START --> block — API keys, repo links, domain notes |
SOUL.md is baseline disposition — don't edit unless you want a different personality.
Just tell the agent what you want:
Every 30 minutes, scan
memory/today.mdand flag urgent items.
asst's cron skill handles it. Tasks that must survive restarts go through launchd:
- Drop a plist into your agent's
launchd/folder — copylaunchd/cron-example.plist.tmpl, setLabeltocom.hermit-agent.<agent>.cron-<task>, and pointProgramArgumentsat whatever you want run. - Sync to the live LaunchAgents dir:
./scripts/launchd-sync.sh .(idempotent:LOADEDnew,RELOADchanged, skip unchanged;--dry-runto preview). - Confirm:
launchctl list | grep com.hermit-agent.<agent>.
Writing the plist alone does NOT activate it — launchd-sync.sh is the difference between "generated" and "running". Re-run it any time you add, edit, or rename a plist.
The CLI automatically installs a launchd coordinator the first time you run it on a machine. Every 10 minutes it pushes a digest of all hermits on the box to the coordinator's Telegram chat: 🟢 idle · 🟨 running · 🟥 stuck · ⚫ down.
- One coordinator per machine. When
create-hermit-agentdetects an existingcom.hermit-agent.*.status-reporter.plist, it skips — subsequent hermits don't stack their own jobs. - The first agent you install (default
asst) is the coordinator. Its plist lives at~/Library/LaunchAgents/com.hermit-agent.asst.status-reporter.plist. - To disable:
launchctl unload ~/Library/LaunchAgents/com.hermit-agent.<coordinator>.status-reporter.plist. - To hand off to a different coordinator: unload the old plist, delete it, re-run
create-hermit-agentfrom a new agent (or manuallycp launchd/status-reporter.plist ~/Library/LaunchAgents/com.hermit-agent.<new>.status-reporter.plist && launchctl load ...).
| Symptom | Fix |
|---|---|
| Agent doesn't reply | tmux attach -t claude-<name> to see live state. Also check restart.log, claude-agent.log. |
| Plugin subprocess missing | ./restart.sh auto-retries once. Still broken? Check ~/.claude/channels/telegram-<name>/.env is mode 600 with the token. |
exceeds the dimension limit image crash |
All image Reads MUST go through scripts/safe-image.sh first. Recover with restart + /compact. |
claude plugin install failed |
Ensure claude is on PATH and logged in (claude login). |
| Context bloat | Send !!compact from Telegram, or /compact directly in the tmux pane. |
| Bot silent on a fresh Mac | Claude Code may have raised the "trust this folder" or "allow dangerous mode" TUI dialog — which blocks startup. The CLI pre-acknowledges both, but if something went wrong, tmux attach -t claude-<name>, press Enter to dismiss any pending dialog, then detach with Ctrl-b d. |
Do I need to pre-install the Telegram plugin in Claude Code?
No. create-hermit-agent runs claude plugin install telegram@claude-plugins-official -s project for every new agent. First install downloads to ~/.claude/plugins/cache/; subsequent agents register against the cache per-project. Zero manual plugin setup.
Linux / Windows support?
macOS only. launchctl, sips, tmux are all macOS-shaped. PRs welcome.
Can multiple agents share a bot token?
No. Telegram's Bot API routes each bot's updates to exactly one listener. Sharing causes message hijacking. Each agent needs its own @BotFather-issued token.
Where's the bot token stored?
~/.claude/channels/telegram-<name>/.env (mode 600, outside the project). Also echoed into .claude/settings.local.json (gitignored).
Does the first DM trigger a pairing code?
No. The CLI pre-populates ~/.claude/channels/telegram-<name>/access.json with your user ID pre-allowlisted, so your own DMs pass through from message one. Strangers who find the bot's @handle still get the standard pairing challenge (dmPolicy: "pairing"), not silent delivery.
How do I delete an agent cleanly?
tmux kill-session -t claude-<name>
rm -rf <agent-folder>
rm -rf ~/.claude/channels/telegram-<name>Also revoke the bot via @BotFather if you're done with it.
MIT.