Observed since 20 Oct, 23:28 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
HTTP and Telegram relay to agents-harness runner.loop (stdlib only)
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).
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).
[!TIP] 🤖 Agent-Driven Setup: Give your coding agent this repo (clone or URL), then tell it to "install agents-relay and configure it to relay between your chat interface and agents-harness."
| 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 |
runner.loop process per request. No in-process LLM logic.~/.agents. Relay files (approvals, inbox) stay in AGENTS_RELAY_STATE.http.server, urllib.request, subprocess)./v1/turn, /v1/inject): Buffered turn execution with secret header validation.send, inject, approve): Host-initiated push messages, simulated turns, and Telegram Approve/Deny for mutating tools./jobs, /stop, /stop <id> kill the loop process group./v1/turn attachments, are passed to the loop as --attach.runner.loop process per request. No in-process LLM state./v1/stream): Direct server-sent events for client web UIs./v1/a2a/mail, /v1/a2a/peers): Peer-to-peer agent relay for board instances./v1/webhook/{source}): Direct ingress for CI/CD and deployment alerts.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).
| 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.
LOOP_CMD per turn.pytest
or via standard library:
python -m unittest discover -s tests -p "test_*.py"
MIT. See LICENSE.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 6 Oct 2026 | 0 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:28 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:eeacc2901c1dfa974556e610bd0ba7ea1c35360f91b858186efd357e1ebb6ccc