The gateway: CLI, HTTP, Telegram, one brain
A good architecture separates 'what does this thing do' from 'how does it talk'. Our agent loop is the what. The CLI, the HTTP endpoint, and the Telegram handler are different hows. They all call the same run_agent_turn. The HTTP channel uses Flask, a tiny Python web framework that is already in the workshop dependencies: you create one app object, decorate functions as routes, and call run to serve them.
from flask import Flask, request, jsonify
flask_app = Flask(__name__)
def start_http():
print("[http] Server running on http://localhost:5000")
flask_app.run(host="0.0.0.0", port=5000, debug=False)The Flask wiring: one import, one app object, and a start_http function that serves it on port 5000. main() launches this in a daemon thread.
# Channel 1: Telegram
async def handle_telegram(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = str(update.effective_user.id)
reply = run_agent_turn(user_id, update.message.text)
await update.message.reply_text(reply[:4096])
# Channel 2: HTTP (Flask)
@flask_app.route("/chat", methods=["POST"])
def http_chat():
data = request.get_json()
user_id = data.get("user_id", "http-user")
reply = run_agent_turn(user_id, data.get("message", ""))
return jsonify({"reply": reply})
# Channel 3: CLI
def start_cli():
user_id = "cli-user"
while True:
user_text = input("you> ").strip()
if user_text in {"exit", "quit"}: break
if user_text: print(f"bot> {run_agent_turn(user_id, user_text)}")The three channel handlers. They unpack a request, call the same brain, and ship the reply back. Nothing about the agent leaks into any of them.
Running them at the same time needs threading. Flask gets a daemon thread. Telegram owns the main loop because it already has its own asyncio. CLI takes its place if you skip Telegram.
def main():
has_telegram = "--telegram" in sys.argv
has_http = "--http" in sys.argv or has_telegram
if has_http:
threading.Thread(target=start_http, daemon=True).start()
if has_telegram:
start_telegram()
else:
start_cli()The launcher. --telegram opts in to the bot mode. --http opts in to the HTTP endpoint. With neither, you get just the CLI. They compose freely.
State is per user_id. The Telegram channel uses the Telegram user id. HTTP defaults to 'http-user'. CLI uses 'cli-user'. So they live in separate sessions on disk. Inside the same user_id, if two requests race, they could trample each other's session file. The concurrency lesson later in the course adds per-session locks to fix exactly that.
# Terminal 1
make 06-gateway # CLI is up, HTTP is up on port 5000
# Terminal 2
curl -X POST http://localhost:5000/chat \
-H 'Content-Type: application/json' \
-d '{"user_id": "http-tester", "message": "list files in this directory"}'Start the gateway with HTTP on, then poke it from another terminal.
Quiz: Quiz
Loading practiceโฆ