#!/usr/bin/env python3 """Agent runner — entry point for running a Teleo Telegram agent. Usage: python3 agent_runner.py --agent rio python3 agent_runner.py --agent theseus python3 agent_runner.py --agent rio --validate Systemd template unit: teleo-agent@.service ExecStart=/usr/bin/python3 /opt/teleo-eval/telegram/agent_runner.py --agent %i Each agent runs as a separate process for fault isolation. Template unit means `systemctl start teleo-agent@rio` and `systemctl start teleo-agent@theseus` are independent services with separate log streams (journalctl -u teleo-agent@rio). Epimetheus owns this module. """ import argparse import sys import os from pathlib import Path AGENTS_DIR = Path(__file__).parent / "agents" def find_config(agent_name: str) -> Path: """Resolve agent name to config file path.""" config_path = AGENTS_DIR / f"{agent_name}.yaml" if not config_path.exists(): print(f"ERROR: Config not found: {config_path}", file=sys.stderr) print(f"Available agents: {', '.join(p.stem for p in AGENTS_DIR.glob('*.yaml'))}", file=sys.stderr) sys.exit(1) return config_path def validate(agent_name: str) -> bool: """Validate agent config and runtime dependencies. Returns True if valid.""" config_path = find_config(agent_name) # Add telegram dir to path for agent_config import sys.path.insert(0, str(Path(__file__).parent)) from agent_config import validate_agent_config try: warnings = validate_agent_config(str(config_path)) if warnings: for w in warnings: print(f" WARNING: {w}", file=sys.stderr) print(f" Config OK: {agent_name} ({config_path})") return True except ValueError as e: print(f" FAILED: {e}", file=sys.stderr) return False def run(agent_name: str): """Run the agent bot process.""" config_path = find_config(agent_name) # Validate before running (fail fast) if not validate(agent_name): sys.exit(1) # Set sys.argv so bot.py's main() picks up the config sys.argv = ["bot.py", "--config", str(config_path)] # Import and run bot — this blocks until the bot exits sys.path.insert(0, str(Path(__file__).parent)) import bot bot.main() def list_agents(): """List available agent configs.""" configs = sorted(AGENTS_DIR.glob("*.yaml")) if not configs: print("No agent configs found in", AGENTS_DIR) return print("Available agents:") for p in configs: # Quick parse to get agent name from YAML name = p.stem try: import yaml with open(p) as f: data = yaml.safe_load(f) domain = data.get("domain", "unknown") print(f" {name:12s} domain={domain}") except Exception: print(f" {name:12s} (config parse error)") def main(): parser = argparse.ArgumentParser( description="Run a Teleo Telegram agent", epilog="Systemd: teleo-agent@.service uses --agent %%i" ) parser.add_argument("--agent", help="Agent name (e.g., rio, theseus)") parser.add_argument("--validate", action="store_true", help="Validate config and exit") parser.add_argument("--list", action="store_true", help="List available agents") args = parser.parse_args() if args.list: list_agents() return if not args.agent: parser.error("--agent is required (or use --list)") if args.validate: ok = validate(args.agent) sys.exit(0 if ok else 1) run(args.agent) if __name__ == "__main__": main()