agents-relay
Stdlib HTTP and Telegram relay in front of agents-harness runner.loop.
One subprocess per turn. Trailer parsing for session metadata. No identity store, no traces, no writes under ~/.agents.
Approval decisions and downloaded files live in the relay state dir (AGENTS_RELAY_STATE, default ~/.agents-relay).
Quickstart
pip install agents-relay
Needs runner.loop on PATH (LOOP_CMD, default python -m runner.loop). That comes from agents-harness 0.1.0 or newer (approvals, --detached-session, and the {user} approval substitution need it).
Architecture
| Component | Responsibility |
|---|---|
| HTTP Adapter | Serves /v1/turn and /v1/inject endpoints with secret header validation |
| Telegram Adapter | Long-poll bot updates with real-time thinking status edits, parallel jobs, and Approve/Deny callbacks |
| Loop Client | Spawns buffered runner.loop subprocess per turn with trailer parsing and repeatable --attach |
| Outbound Send | Direct Telegram push via agents-relay send for reminders and notifications |
| Approval Gate | agents-relay approve asks an allowlisted chat and blocks on a decision file |
- Subprocess Isolation: One
runner.loopprocess per request. No in-process LLM logic. - No harness state: No identity storage, no traces, no writes under
~/.agents. Relay files (approvals, inbox) stay inAGENTS_RELAY_STATE. - Streaming Status: Throttled progress streaming and real-time status edits during long-running tool rounds.
- Zero Bloat: Pure Python standard library (
http.server,urllib.request,subprocess).
Capabilities & Roadmap
Core Capabilities (Implemented)
- HTTP Turn Endpoints (
/v1/turn,/v1/inject): Buffered turn execution with secret header validation. - Telegram Long-Poll Gateway: Bot API updates with real-time thinking status message editing.
- Direct Outbound CLI (
send,inject,approve): Host-initiated push messages, simulated turns, and Telegram Approve/Deny for mutating tools. - Parallel Telegram jobs: Updates are dispatched off the poll thread. Per-chat cap (default 3) with a queue.
/jobs,/stop,/stop <id>kill the loop process group. - Long replies: Split into several Telegram messages at paragraph boundaries with balanced HTML. No silent 4096 cut.
- Attachments: Telegram photos and documents, and
/v1/turnattachments, are passed to the loop as--attach. - Mobile Formatting: Automatic Markdown table conversion to Telegram-readable bullet lists.
- Trailer Parsing: Extracts session, user ID, alias, and return code from harness stdout trailers.
- Subprocess Isolation: One
runner.loopprocess per request. No in-process LLM state. - Zero Bloat Runtime: Pure Python standard library only.
Planned (Roadmap)
- SSE Token Streaming (
/v1/stream): Direct server-sent events for client web UIs. - A2A Board Mailbox Proxy (
/v1/a2a/mail,/v1/a2a/peers): Peer-to-peer agent relay for board instances. - Inbound Webhook Hub (
/v1/webhook/{source}): Direct ingress for CI/CD and deployment alerts. - Multi-Channel Adapters: Signal / Discord gateway adapters alongside Telegram.
Commands
Machine-readable catalog: python -m agents_relay --help-json (do not scrape --help).
| Command | Purpose |
|---|---|
agents-relay serve |
HTTP /v1/turn, /v1/inject (and /v1/alert, /webhook/alert). Telegram long-poll if TELEGRAM_BOT_TOKEN is set |
agents-relay serve --no-telegram |
HTTP only |
agents-relay send --user <chat_id> --text "..." |
One outbound Telegram message (--chat-id alias). Allowlist + token from env |
agents-relay inject --user <chat_id> --text "..." |
Same inbound handler as poll (thinking edits + final). JSON on stdout. Allowlist + token from env |
agents-relay approve --user <chat_id> --timeout 300 |
Read a tool-approval JSON object on stdin. Send Approve/Deny. Exit 0 approved, 1 denied, 2 timeout or unavailable. One JSON line on stdout: {"note":"..."} |
Telegram chats can send /jobs, /stop, and /stop <id> while serve is polling. /stop kills that chat's runner.loop process group (the loop and child tools that stayed in the group).
Environment
| Variable | Description |
|---|---|
LOOP_CMD |
Loop argv (default python -m runner.loop) |
LOOP_PROVIDER |
Provider name passed through (default echo) |
RELAY_SECRET |
Shared secret (GATEWAY_SECRET fallback). Empty = no auth |
TELEGRAM_BOT_TOKEN |
Enables poll + send |
TELEGRAM_ALLOWED_CHAT_IDS |
Comma-separated numeric IDs. Empty refuses the Telegram adapter unless AGENTS_RELAY_ALLOW_ANYONE=1 |
AGENTS_RELAY_ALLOW_ANYONE |
1 opts in to an empty allowlist. Logs a loud warning. Anyone who can message the bot can run turns and approve tools |
AGENTS_RELAY_STATE |
Relay files (default ~/.agents-relay). Approvals: $AGENTS_RELAY_STATE/approvals/<id>.json. Inbox: .../inbox/ |
AGENTS_RELAY_APPROVER |
Chat id used by approve when --user is missing or not numeric (HTTP turns often pass anonymous) |
AGENTS_RELAY_ATTACH_DIR |
Only local /v1/turn attachment paths under this directory are accepted (default: the relay state dir) |
AGENTS_RELAY_MAX_JOBS |
Global cap on parallel Telegram turns (default 8) |
AGENTS_RELAY_MAX_JOBS_PER_CHAT |
Parallel turns per chat before extra messages queue (default 3, queue cap 20) |
AGENTS_RELAY_WAIT_TEXT |
Telegram placeholder while a turn runs (default One moment …). Status and fallback lines are English |
RELAY_HOST / RELAY_PORT |
Bind (default 127.0.0.1:8787; GATEWAY_HOST / GATEWAY_PORT fallbacks) |
TELEGRAM_POLL_TIMEOUT |
Long-poll seconds, clamped 1–50 (default 50) |
POST /v1/turn accepts attachments: a list of {"path": "/abs/file", "mime": "image/png"} or {"url": "https://...", "mime": "..."}. A local path must already exist and must resolve inside AGENTS_RELAY_ATTACH_DIR (default: the relay state dir). Paths outside that directory are rejected with HTTP 400. url must be http or https and is downloaded into the relay inbox (20 MB cap). When mime is set, the saved file extension matches it (image/jpeg → .jpg) so the harness can infer the type. Each file is passed to the loop as --attach. Photos and documents from Telegram take the same --attach path. The loop --user for a Telegram turn is the numeric chat id so AGENTS_APPROVAL_CMD can substitute {user}.
approve --user may be a non-numeric turn id (HTTP defaults to anonymous). Then the prompt goes to AGENTS_RELAY_APPROVER, or to the only entry in TELEGRAM_ALLOWED_CHAT_IDS when that list has exactly one chat. Otherwise approve exits 2 and prints {"note":"..."}. A numeric --user is used as given.
approve and serve must share AGENTS_RELAY_STATE on the same host. The poll loop answers callback_query and writes the decision. Only allowlisted user ids can press Approve or Deny. In a private chat that id is the chat id. In a group, list both the group chat id and the approving user id.
HTTP secret headers supported: X-Relay-Secret, Relay-Secret, X-Gateway-Secret, Gateway-Secret.
Constraints
- Python 3.10+, standard library only.
- Does not import harness internals. Strictly spawns
LOOP_CMDper turn. - Klanker / host agents own identity and traces. This process is strictly I/O.
Tests
pytest
or via standard library:
python -m unittest discover -s tests -p "test_*.py"
License
MIT. See LICENSE.
Metadata
Release files for agents-relay 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agents_relay-0.1.0.tar.gz | 49.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agents_relay-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.1 kB
Release files / agents_relay-0.1.0.tar.gz
| Download URL | agents_relay-0.1.0.tar.gz |
|---|---|
| Size | 49.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d87868ca29e713d540dd36c022ef129e13d2fe0cdfb2eda96e6947c84fc2efc8
|
|
BLAKE2b-256 checksum How to use checksums |
37bab492cb66d45425ff8836a3fcc2e93f5c1ebde60cbb3eea2985e6d8a71cad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / agents_relay-0.1.0-py3-none-any.whl
| Download URL | agents_relay-0.1.0-py3-none-any.whl |
|---|---|
| Size | 36.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
665345acb69519f6bc403f64946e1759a916fb812e6abe73bde66eabd0df99e4
|
|
BLAKE2b-256 checksum How to use checksums |
0a02a094eecb97cfa447ff12914da737fffa13cf77f3b8ecb594a17fff1e0353
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|