Skip to main content
RemoteAgent is built on a serverless-first architecture. There is no custom server running 24/7 to maintain, no daemon process on the cloud side, and no persistent connection between Telegram and your agent. Everything is event-driven and stateless except for the local agent process on your machine.

Message flow

The complete path of a message from your phone to your agent and back:
You (Telegram app)

     │ HTTPS POST

Telegram servers

     │ HTTPS POST (webhook)

Vercel — /api/telegram
  [Grammy.js verifies HMAC-SHA256 signature]
  [Identifies your user ID and target agent]
  [Publishes command to Redis channel]

     │ Redis PUBLISH
     │ channel: agent:{agentId}

Redis (Upstash)

     │ Redis SUBSCRIBE (long-lived, in your agent process)

Your machine — remoteagent process
  [Receives command event]
  [Calls AI runner with prompt + working directory]
  [Streams output chunks as they are produced]

     │ Redis PUBLISH (per chunk)
     │ channel: output:{sessionId}

Redis (Upstash)

     │ Redis SUBSCRIBE (Vercel function, per session)

Vercel — output forwarder
  [Receives chunk events]
  [Calls Telegram Bot API to send/edit messages]

     │ HTTPS POST

Telegram servers


You (Telegram app) — see output streaming in real time

Serverless-first design

The web app runs entirely on Vercel as serverless functions. There is no always-on server process on the cloud side:
  • Telegram webhook — Vercel function wakes on each incoming message, processes it, publishes to Redis, and exits.
  • Output forwarder — a separate Vercel function subscribes to the output Redis channel for the duration of a session and forwards chunks to Telegram.
  • Cron jobs — Vercel cron invocations for trial reminder notifications.
The only persistent process in the entire system is the agent on your machine. It maintains long-lived Redis subscriptions and runs the AI runner when commands arrive.

The agent on your machine

The agent is a Node.js process that:
  1. Subscribes to agent:{agentId} on Redis at startup.
  2. On receiving a command event, calls the configured AI runner with the prompt.
  3. Streams output chunks back to output:{sessionId} as the runner produces them.
  4. Sends a done event when the runner finishes.
  5. On receiving an abort event, kills the runner process and publishes a done event.
The agent process never initiates outbound connections to Telegram or to the RemoteAgent web app — it only communicates via Redis channels.

Why Redis pub/sub?

Redis pub/sub provides:
  • Low latency — typically under 10ms for a message to travel from publisher to subscriber.
  • Streaming-friendly — publish individual chunks as they are produced, subscribe and forward immediately.
  • Stateless relay — the Redis server does not need to store messages; subscribers receive them in real time or miss them. No database required for the relay layer.
  • Serverless compatible — Vercel functions can subscribe to a Redis channel for the duration of a request without holding a persistent connection from a dedicated server.

Database

PostgreSQL (via Neon) stores persistent state:
  • users — Telegram user ID, plan, subscription status
  • agents — agent metadata, token hash, online/offline status
  • sessions — session records with prompt preview and status
  • pairing_codes — short-lived codes with TTL and single-use enforcement
The database is not involved in the real-time message relay path. It is only read/written for authentication, session tracking, and plan enforcement.

Pairing flow

1. remoteagent init
   → POST /api/pair { projectName }
   → Server generates pairing code, stores in DB with 10-minute TTL
   → Returns { code: "XK9-4TZ" } to CLI

2. User opens remoteagent.chat/connect
   → Authenticates with Telegram widget (user ID verified by Telegram)
   → Enters code → POST /api/pair/confirm { code }
   → Server verifies code, creates agent record, issues JWT
   → Publishes { event: "paired", payload: { jwt, agentId } } to Redis

3. Agent CLI receives "paired" event
   → Saves JWT and agentId to ~/.remoteagent/agents/{agentId}.json
   → Confirms pairing in terminal

4. Agent starts
   → Subscribes to agent:{agentId} with JWT-authenticated connection
   → Ready to receive commands