A remote control plane for AI work sessions. Issue commands via Telegram (voice or text) from any device, while a local server runs sessions 24/7 — coding, writing, research, and more.
Terminals are a bottleneck. You sit at a desk, type prompts, wait for responses, approve permissions — one session at a time, one screen at a time.
AFK breaks that loop:
- Work from anywhere. Send a voice message from your phone while commuting. The agent runs on your server back home.
- Run multiple sessions. Each Telegram forum topic is an isolated agent session. Start one for frontend, another for backend, check in when you want.
- Stay in control without being present. Permission requests arrive as push notifications with approve/deny buttons. No terminal window required.
- See what's happening. A built-in web control plane shows live session activity, message history, and daemon logs — all at
localhost:7777. - Verify remotely. Start a dev server tunnel with
/tunneland preview your app from your phone.
Get off your ass.
- You send a message (text or voice) in a Telegram forum topic
- AFK routes it to the agent subprocess tied to that topic
- The agent streams responses back, rendered to Telegram silently
- When the agent needs permission to run a tool, you get a notification with Allow/Deny buttons
- On completion, you see cost and duration
Each session runs in an isolated git worktree with its own branch. For architecture details, see ARCH.md.
| Requirement | Notes |
|---|---|
| macOS or Linux | Tested on Apple Silicon; Linux works for always-on servers |
| Python 3.11+ | Check with python3 --version |
| uv | Python package manager (install) |
| Git | git --version |
| Claude Code CLI | Default agent runtime — must be installed and authenticated (see step 2) |
| Telegram account | For the bot and supergroup setup |
Create a bot:
- Open Telegram and message @BotFather
- Send
/newbot, follow the prompts to name your bot - Copy the bot token (looks like
7349288428:AAF32REWFK55Ygt_...)
Create a supergroup with Topics:
- Create a new Telegram group (any name — e.g. "AFK Workspace")
- Go to Group Settings → scroll down → enable Topics (forum mode)
- Add your bot to the group as admin with these permissions:
- Manage Topics
- Send Messages
- Edit Messages
- Delete Messages
Get the group ID:
- Add @raw_data_bot to the group temporarily
- It will print the chat ID — a negative number like
-1001234567890 - Save this number, then remove the bot from the group
AFK uses Claude Code as the default agent runtime. Install and authenticate it:
# Install
npm install -g @anthropic-ai/claude-code
# Authenticate (opens browser)
claude
# Verify
claude --versionMake sure claude is available in your PATH. AFK launches it in headless mode (--input-format stream-json --output-format stream-json) per session.
Choose one of the two options below:
bash <(curl -fsSL https://raw.githubusercontent.com/Donghh0221/afk/main/install.sh)The script checks prerequisites, clones the repo, installs dependencies, creates .env (prompts for the bot token and group ID from Step 1), and optionally sets up a launchd daemon. After it finishes, skip to Step 4. Run.
git clone https://github.com/Donghh0221/afk.git
cd afk
# Install dependencies with uv
uv syncIf you don't have uv:
# Install uv first
curl -LsSf https://astral.sh/uv/install.sh | sh
# Or use pip instead
pip install -e .Then create the .env file:
cp .env.example .envEdit .env and set the required values:
# Required
AFK_TELEGRAM_BOT_TOKEN="your-bot-token-here"
AFK_TELEGRAM_GROUP_ID="-100xxxxxxxxxx"
# Optional — base directory for /project init
# AFK_BASE_PATH="~/workspace"
# Optional — web dashboard port (default: 7777)
# AFK_DASHBOARD_PORT="7777"
# Optional (experimental) — enables voice messages (Whisper) and Deep Research agent
# AFK_OPENAI_API_KEY="sk-..."uv run afkYou should see:
AFK is running. Press Ctrl+C to stop.
Web control plane running at http://localhost:7777
AFK is now listening for Telegram messages and serving the web dashboard.
- Open Telegram → go to your supergroup
- Register a project:
/project add ~/projects/myapp myapp(or/project init myappifAFK_BASE_PATHis set) - Send
/new myapp— a new forum topic is created with an active agent session - Switch to the new topic and type a prompt — the agent should respond
All interaction happens in your Telegram supergroup.
Before starting a session, register the project:
/project add ~/projects/myapp myapp # Register existing directory
/project init myapp # Create + register under $AFK_BASE_PATH
/project list # List registered projects
/project remove myapp # Unregister
/project init requires AFK_BASE_PATH — it creates $AFK_BASE_PATH/myapp (with git init) if it doesn't exist, or registers the existing directory.
/new myapp # Start session (project must be registered)
/new myapp -v # Verbose — show full tool input/output
/new myapp --template nextjs # Apply workspace template
Each /new creates a forum topic (e.g. myapp-260218-143022) with an isolated git worktree and starts an agent subprocess.
Switch to the session topic and type (or voice-message) your instructions:
Add Stripe payment integration with webhook handling
The agent works on it. You'll see streaming tool calls and responses. When it's done:
✅ Done ($0.0523, 12.3s)
Start a dev server tunnel from the session topic:
/tunnel # Auto-detect dev server and expose via cloudflared
/tunnel stop # Stop the tunnel
Requires cloudflared to be installed.
/sessions # List all active sessions (General topic)
/status # Check current session state (session topic)
/stop # Stop current session and remove worktree (session topic)
/complete # Commit, merge branch into main, clean up (session topic)
Templates provide pre-configured scaffolds and agent context:
/template list # List available templates
/new myapp --template nextjs # Next.js 15 project scaffold
Open http://localhost:7777 in a browser to:
- Create and manage sessions
- Send prompts and approve permissions
- View per-session message history and daemon logs
- Stream live agent events via SSE
The web CP also provides a REST API:
GET /api/sessions # List sessions
POST /api/sessions # Create session
GET /api/sessions/{channel_id}/status # Session status
GET /api/sessions/{channel_id}/messages # Message history
POST /api/sessions/{channel_id}/message # Send text
POST /api/sessions/{channel_id}/stop # Stop session
POST /api/sessions/{channel_id}/complete # Complete & merge
POST /api/sessions/{channel_id}/permission # Permission response
GET /api/sessions/{channel_id}/files/{name} # Download session file
GET /api/events # SSE event stream
GET /api/projects # List projects
POST /api/projects # Add project
DELETE /api/projects/{name} # Remove project
GET /api/logs # Daemon log tail
| Variable | Required | Default | Description |
|---|---|---|---|
AFK_TELEGRAM_BOT_TOKEN |
Yes | — | Telegram bot token from @BotFather |
AFK_TELEGRAM_GROUP_ID |
Yes | — | Supergroup chat ID (negative number) |
AFK_BASE_PATH |
No | — | Base directory for /project init |
AFK_DASHBOARD_PORT |
No | 7777 |
Web control plane port |
Experimental:
| Variable | Required | Default | Description |
|---|---|---|---|
AFK_OPENAI_API_KEY |
No | — | Enables voice transcription (Whisper) and Deep Research agent. Also reads OPENAI_API_KEY |
AFK_AGENT |
No | claude |
Agent runtime: claude, codex, or deep-research |
AFK_DEEP_RESEARCH_MODEL |
No | o4-mini-deep-research |
OpenAI Deep Research model |
AFK_DEEP_RESEARCH_MAX_TOOL_CALLS |
No | — | Max tool calls for Deep Research cost control |
The install script can set this up automatically (bash install.sh), or manually:
# Create plist
cat > ~/Library/LaunchAgents/com.afk.daemon.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.afk.daemon</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/uv</string>
<string>run</string>
<string>afk</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/afk</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/afk.out.log</string>
<key>StandardErrorPath</key>
<string>/tmp/afk.err.log</string>
</dict>
</plist>
EOF
# Update paths in the plist, then:
launchctl load ~/Library/LaunchAgents/com.afk.daemon.plistManage the daemon:
launchctl load ~/Library/LaunchAgents/com.afk.daemon.plist # Start
launchctl unload ~/Library/LaunchAgents/com.afk.daemon.plist # Stop
tail -f /tmp/afk.out.log /tmp/afk.err.log # Logscat > ~/.config/systemd/user/afk.service << 'EOF'
[Unit]
Description=AFK Daemon
After=network.target
[Service]
Type=simple
WorkingDirectory=/path/to/afk
ExecStart=/path/to/uv run afk
Restart=always
RestartSec=5
EnvironmentFile=/path/to/afk/.env
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now afk
journalctl --user -u afk -f # Logs# Install with test dependencies
uv sync --extra test
# Run tests
uv run pytest
# Run a specific test
uv run pytest tests/test_session_manager.py -v
# Run the daemon in development
uv run afkMIT