Run AI coding agents on your computer, from your phone.
Status: alpha, actively developed. Patchbay has run my studio for months. It is currently pi-only: the Claude Agent SDK backends (
cc-sdk,cc-sdk-mop) were removed because Anthropic is ending subscription (Max) coverage for embedded SDK use, so pi (multi-model via litellm) is the viable engine now. TheHarnessprotocol is kept as an internal seam for adding a future non-Claude backend, so this stays a harness-agnostic design with one backend live today. MIT-licensed; fork freely.
Patchbay bridges Telegram to a coding agent running on your own machine — currently pi, a multi-model agent — letting you develop software, manage infrastructure, and run autonomous agents from a mobile messaging app while your real workstation does the actual work.
Most "AI coding from your phone" tools mean chatting with an LLM that has no access to your real code, your real tools, or your real environment — fine for asking questions, useless for actual work. Patchbay inverts that: the real machine in another room does the real work, the phone just drives.
The architecture leverages Telegram's forum-mode group topics — persistent threaded conversations that already provide the routing primitives multi-project work needs. Each topic binds to a project directory and an agent session. Topic #myproject is an agent session in ~/Developer/myproject. Topic #another is one in ~/Developer/another. Switching between them is one tap. Each topic has full filesystem access, full tool access, and full process control on the actual workstation where the code lives.
Patchbay is the transport: a Python service that listens for Telegram messages, routes each to the right project, agent, and harness, runs the turn, and streams the response back. It survives its own self-edits, recovers from its own crashes, and is agnostic to which agent runs the turn.
Concrete examples of work driven from a phone with the workstation in another room:
- Long-form drafting on the move. A topic bound to a research/writing agent reads work history and drafts long-form documents on demand — cover letters, project briefs, design memos — written to a synced markdown vault, ready to review on landing.
- Repo work from anywhere. A topic bound to a project directory accepts plain-language change requests. The agent pulls logs, finds bugs, fixes code, runs tests, commits, deploys, and replies with the diff. The phone never holds a checkout; the workstation does.
- Inbox triage on a commute. A topic bound to an email-triage agent batches overnight inbox into priority buckets, flags items that need a human, drafts replies for the rest. Tap, dictate edits, send.
- Survives its own bad edits. Because the bridge is edited from a phone through itself, a broken self-edit can't brick it: pre-flight validation rolls back to a known-good snapshot, and a crash loop backs off and logs the traceback for fix-forward.
- Repo provisioning by message. A single message provisions a new GitHub repo — branch protection, default-branch convention, lint hooks, initial scaffold — all via API. Done in seconds without leaving Telegram.
The unifying property: the phone never holds the work. The workstation does. The phone is just the keyboard.
Each Telegram forum topic maps to an independent agent session. Multiple topics run in parallel, each with its own project directory, harness backend, and session state. Sessions persist across messages and auto-expire after configurable inactivity.
flowchart TD
TG["Telegram Bot API<br/>(long-polling)"] --> BR["bridge.py<br/>message router"]
BR --> CP["chat_projects.json<br/>topic → directory + model"]
BR --> H{"harness<br/>(per topic)"}
H -->|pi| PI["pi<br/>(multi-model via litellm)"]
PI --> OUT["response chunking<br/>+ typing indicators"]
OUT --> SEND["Telegram Bot API<br/>send response"]
The Harness protocol allows other backends, but pi is the only one live today (see the status note above). New harnesses slot in at the harness node.
bridge.py Entrypoint -- Telegram handlers, commands, lifecycle
patchbay/ Core package
config.py Environment variables, paths, constants, logging
sessions.py Session persistence, sanitization, pending messages
parser.py Agent CLI output parsing (JSON array, NDJSON, single-object)
models.py Per-chat pi model mapping (litellm aliases, default "small")
efforts.py Per-chat thinking-effort setting
quota.py Quota/rate-limit detection (is_quota_error)
activity.py Structured JSON-lines activity logging
projects.py Chat-to-project directory + harness mapping
self_heal.py In-process repair handlers (corrupt session, OOM retry)
singleton.py Single-instance lock (avoids 409 getUpdates conflicts)
outbound.py Outbound send/queue plumbing
telegram_send.py Telegram send helpers (chunking, markdown, media)
text_split.py Message splitting
file_send.py File / photo delivery
markdown_config.py Markdown rendering config
log_filters.py Log filtering
logrotate.py Log rotation
runtime.py Process runtime state
reply_store.py Reply-text cache
commands/ Telegram command handlers
lifecycle.py /start, /clearnew, /kill, /restart, /ping
project.py /setproject, /project, /harness, /model, /effort, /remote_control
observability.py /usage, /health, /activity, /soak
context.py /context, /compact
heartbeat.py /heartbeat
inquiry.py Shared one-shot harness resolver (/context, /usage)
harness/ Pluggable agent backends (pi live; protocol kept as a seam)
base.py Protocol + TurnEvent types + capability protocols
pi.py badlogicgames/pi multi-model agent (the live backend)
pi_session.py Read context/usage from pi session transcripts
context_estimate.py Client-side token/context estimator (litellm-backed)
validate.py Pre-flight validation (syntax, imports, parser smoke tests)
run.sh Entry point with crash-loop detection + known-good rollback
A single Telegram chat would force the user to pick "what project am I working on" with every message. By making each forum topic an independent session, the user picks the project once when they enter the topic; routing is implicit thereafter. Multi-project work becomes tab-switching. Group permissions and notifications work per topic, so silencing or pinning a project is one tap.
The bridge does not care which agent runs the turn. The Harness protocol (patchbay/harness/base.py) is a streaming-event interface that all backends conform to. Today there is exactly one live backend, badlogicgames/pi (pi, patchbay/harness/pi.py); the earlier Claude Agent SDK backends (cc-sdk, cc-sdk-mop) were removed when Anthropic ended embedded-subscription SDK use. The protocol is kept deliberately, so a future non-Claude framework can be added by implementing one class. Capabilities (resume, mid-turn push, MCP, tool streaming) are advertised via HarnessCapabilities so the bridge can degrade gracefully when a backend doesn't support a feature. Note pi already gives multi-model coverage via litellm; the harness seam is for a different framework, not a different model.
Patchbay is frequently edited by an agent running through itself, which means a bad self-edit could brick the bridge. The safety net is validate.py pre-flight plus rollback to a .bridge-known-good.py snapshot (next section), so a broken self-edit can't stop the bridge from running. On top of that, three or more crashes in five minutes is detected as a loop: run.sh saves the crash tail to logs/crash-loop.log, backs off, and leaves it for fix-forward. (An earlier version spawned an autonomous Claude Code repair session here; that was removed once rollback proved to cover the bricking case, and because it depended on the Claude CLI.) Separately, in-process handlers in self_heal.py quarantine corrupt session files and recommend retries on OOM.
Before every bridge start, validate.py runs syntax checks, import checks, and parser smoke tests on the current source. If any fail, run.sh rolls back to a .bridge-known-good.py snapshot and starts that instead. This means a syntactically broken self-edit cannot stop the bridge from running.
Two bridge processes polling Telegram's getUpdates simultaneously will trigger 409 conflicts, log them 1000 times each, and never exit cleanly. patchbay/singleton.py holds an exclusive lock on .bridge.lock. If a stale PID is holding it (process is dead), the new bridge steals it and continues. If a live PID holds it, the new bridge waits for it to exit, then takes over. No babysitting required during launchd restarts.
Test suite is 640 tests across 44 test files covering the bridge, the parser, the pi harness, all command handlers, the self-heal handlers, the singleton lock, and chaos cases (partial JSON, slow drip, hangs, OOM-style exits). Pre-push hook runs the full suite. No CI on the remote — quality gates are local.
- Harness-agnostic (pi-only today) — the
Harnessprotocol is retained as an internal seam; the one live backend ispi(badlogicgames/pi, multi-model via litellm). Thecc-sdk/cc-sdk-mopbackends were removed under Anthropic's embedded-subscription change. - Multi-project routing — Each Telegram topic binds to a project directory via
chat_projects.json. Each topic can target a different codebase. - Per-topic agent identity — Topics can load identity files (e.g.
SOUL.md,IDENTITY.md,AGENTS.md) into the agent's system prompt to specialize behavior per persona. - Crash-loop detection + known-good rollback —
validate.pypre-flight rolls back to a known-good snapshot on a bad self-edit; 3+ crashes in 5 minutes backs off and logs the crash tail for fix-forward. (The old autonomous Claude-repair trigger was removed; rollback already covers the bricking case.) - Pre-flight validation —
validate.pychecks syntax, imports, and parser behavior before every bridge start. On failure, rolls back to a known-good snapshot. - Rate-limit notice — When the agent hits a quota/rate limit, the turn returns a plain retry notice. (An earlier Forge background-resume handoff was removed with Forge's retirement.)
- Rich messaging — Markdown rendering, code blocks, photo and image handling with captions.
- Pending message batching — messages arriving mid-turn are queued and batched into a single follow-up invocation when the current turn finishes. (Mid-turn push was a
cc-sdkstreaming feature; with pi the debounce queue is the path.) - Remote control — Start
claude remote-controlsessions from Telegram for direct CLI access. - Session persistence — Sessions resume across messages and bridge restarts.
Anyone with a Mac and 30 minutes should be able to follow this end-to-end.
- macOS (uses launchd for process management)
- Python 3.13+
- uv for dependency management
- pi on your
PATH— the live harness (multi-model via litellm).uv syncinstalls the Python deps.
- Message @BotFather on Telegram, run
/newbot, follow the prompts. - Save the bot token he gives you. This goes in
.envasTELEGRAM_BOT_TOKEN. - Get your Telegram user ID by messaging @userinfobot. This goes in
.envasALLOWED_USER_IDS(comma-separated if multiple).
- Telegram → New Group → add your bot.
- Group settings → "Topics" → enable. (This converts the group into a forum.)
- Make the bot an admin with permission to read all messages and post in topics.
- Find the chat ID by sending a message and reading
bridge.logonce patchbay is running, or by using a tool like @RawDataBot.
git clone https://github.com/synodic-studio/patchbay-relay.git
cd patchbay-relay
uv sync
# Configure environment
cp .env.example .env
# Edit .env: set TELEGRAM_BOT_TOKEN, ALLOWED_USER_IDS, CLAUDE_PATH, CLAUDE_WORKING_DIR
# Optional: pre-bind topics to projects
cp chat_projects.example.json chat_projects.json
# Edit chat_projects.json with your real chat-and-thread IDs and project dirs./run.sh
# In another terminal:
tail -f logs/bridge.logSend a message in any topic. The bridge logs the chat-and-thread ID; copy it into chat_projects.json, set the project directory, and the next message routes to that project.
# Update paths in com.synodic.claude-telegram-bridge.plist (look for YOURUSER placeholders)
cp com.synodic.claude-telegram-bridge.plist ~/Library/LaunchAgents/com.synodic.patchbay-relay.plist
launchctl load ~/Library/LaunchAgents/com.synodic.patchbay-relay.plistThe plist sets KeepAlive: SuccessfulExit=false so the bridge auto-restarts on crash, with a 30-second ThrottleInterval backstop to prevent rapid-fire restart storms. run.sh does pre-flight validation; if validate.py fails, it rolls back to a known-good snapshot.
Homebrew upgrades change Cellar binary paths, which silently revokes any TCC permissions (Full Disk Access, Calendar, Contacts) granted to the old binary — a known cause of mid-turn hangs for agents that touch those resources. scripts/tcc/ ships a separate optional launchd agent that watches for these resets and reports them. It does not start automatically with the bridge. See scripts/tcc/README.md for install and the brew-upgrade workflow.
| Command | Description |
|---|---|
/start |
Show command list and your Telegram user ID |
/clearnew |
Start a fresh session (conversation history lost) |
/setproject |
Bind topic to a project directory |
/project |
Show current project and agent for this topic |
/harness <name> |
Switch the agent backend for this topic |
/kill |
Kill the active agent subprocess (session preserved) |
/restart |
Restart the bridge process |
/remote_control |
Start or stop a claude remote-control session |
/ping |
Liveness check with active session status |
The bridge is frequently edited by an agent running through itself. Five layers prevent self-edits from bricking it:
validate.py— Standalone validation: syntax check, import check, parser smoke testsrun.shpre-flight — Runsvalidate.pybefore starting; rolls back to known-good on failure- Crash-loop detection — 3+ crashes in 5 minutes backs off and logs the crash tail (
logs/crash-loop.log) for fix-forward /restartgate — Validates before restarting; blocks restart on failureThrottleInterval: 30in launchd — backstop against rapid respawn
uv run pytest tests/ -q # run tests (640 tests)
uv run ruff check . # lint
uv run python validate.py # pre-flight smoke tests
uv run pytest tests/ --cov --cov-report=term-missing # with coveragePre-push hook runs the full test suite. There is no CI on the remote — quality gates are local.
- model-output-protocol — output-shaping layer between agent and user. Was wired as the
cc-sdk-mopharness (removed with the Claude SDK backends); kept as a reference for when a harness needing output filtering returns. - patchbay-url-scheme-wrapper — Cloudflare Worker that rewrites custom URL schemes (
obsidian://,x-apple-reminderkit://, ...) into tappablehttps://links for Telegram. Point the bridge at your deployment withPATCHBAY_URL_WRAPPER=https://your-domain.example.
MIT