AutomatedEdges
← Back to all articles Automation

Building a Telegram-Gated AI Approval System for Autonomous Agents

May 2026  ·  9 min read

When you build an AI agent that operates autonomously — trading, posting, modifying systems — you quickly run into a trust problem. How much do you let it do on its own? When does it need to ask permission?

The answer I landed on: a Telegram-gated approval system with risk tiers. Low-risk actions execute automatically. Medium-risk actions require a tap of a button in Telegram. High-risk actions require explicit confirmation plus a simulation run. Critical actions are blocked entirely until you manually intervene.

This article walks through the architecture and how to build it from scratch.

Why Telegram?

You could build a web dashboard for this. I chose Telegram for three reasons:

The Risk Tier Model

Before writing a line of code, define your tiers. Here's what I use for my trading system:

The tier system is the most important design decision. Get it wrong and you'll either be approving everything constantly (alert fatigue) or letting risky actions slip through automatically. Start conservative and loosen over time.

Setting Up the Telegram Bot

  1. Open Telegram, search for @BotFather
  2. Send /newbot, follow the prompts, get your token
  3. Message your new bot once to activate the chat
  4. Get your chat ID by visiting: https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates

Store both values as environment variables — never hardcode them.

TELEGRAM_BOT_TOKEN=your_token_here
TELEGRAM_CHAT_ID=your_chat_id_here

The Core Python Structure

The governance bot is a single Python script running as a persistent agent. Here's the skeleton:

import asyncio
import os
from telegram import Bot, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Application, CallbackQueryHandler

BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
CHAT_ID = os.environ["TELEGRAM_CHAT_ID"]

async def send_approval_request(action_id, description, risk_tier, simulation_result=None):
    bot = Bot(token=BOT_TOKEN)
    
    text = f"⚠️ *Approval Required*\n\n"
    text += f"*Action:* {description}\n"
    text += f"*Risk:* {risk_tier}\n"
    
    if simulation_result:
        text += f"\n*Simulation result:*\n{simulation_result}"
    
    keyboard = InlineKeyboardMarkup([
        [
            InlineKeyboardButton("✅ Approve", callback_data=f"approve:{action_id}"),
            InlineKeyboardButton("❌ Reject", callback_data=f"reject:{action_id}"),
            InlineKeyboardButton("💬 Explain", callback_data=f"explain:{action_id}")
        ]
    ])
    
    await bot.send_message(
        chat_id=CHAT_ID,
        text=text,
        parse_mode="Markdown",
        reply_markup=keyboard
    )

Handling Responses

The approval handler listens for button presses and routes accordingly:

async def handle_callback(update, context):
    query = update.callback_query
    await query.answer()
    
    action, action_id = query.data.split(":", 1)
    
    if action == "approve":
        execute_action(action_id)
        await query.edit_message_text(f"✅ Approved and executed: {action_id}")
    
    elif action == "reject":
        cancel_action(action_id)
        await query.edit_message_text(f"❌ Rejected: {action_id}")
    
    elif action == "explain":
        explanation = get_ai_explanation(action_id)
        await query.edit_message_text(f"💬 {explanation}\n\nOriginal action still pending.")

Wiring It Into Your Agent

The governance layer sits between your AI agent's decision and its execution. Your agent proposes an action, the governance layer classifies the risk tier, and routes accordingly:

def govern(action):
    tier = classify_risk(action)
    
    if tier == "LOW":
        execute(action)
        log(action, "auto-executed")
    
    elif tier == "MEDIUM":
        pending_actions[action.id] = action
        asyncio.run(send_approval_request(action.id, action.description, "MEDIUM"))
        # Execution waits for callback
    
    elif tier == "HIGH":
        sim_result = simulate(action)
        pending_actions[action.id] = action
        asyncio.run(send_approval_request(action.id, action.description, "HIGH", sim_result))
    
    elif tier == "CRITICAL":
        send_alert(f"🚨 CRITICAL action blocked: {action.description}")
        log(action, "blocked")

Running It as a Background Service

On macOS, the cleanest way to run the governance bot persistently is a launchd agent — a plist file that keeps the process alive and restarts it if it crashes.

On Linux (Railway, a VPS), use a systemd service or just a supervised process manager like supervisor.

The bot should run independently from your main agent. They communicate via a shared database (SQLite works fine for single-machine setups) or a lightweight queue.

What This Unlocks

Once this is running, you have genuine autonomy with a human-in-the-loop fallback. Your agent can operate 24/7 while you sleep. Risky decisions wait for you. Critical failures alert you immediately.

The goal isn't to automate everything. It's to automate everything that doesn't need you — and to make the things that do need you frictionless to handle from anywhere.

A Telegram message with two buttons is about as frictionless as it gets.

This architecture is what I use in my live trading system across four instruments. It's also what convinced me the governance pattern is worth packaging as a standalone product — but that's a story for a future article.