from __future__ import annotations import sys from abc import ABC, abstractmethod from dataclasses import dataclass from threading import Event from typing import Callable, Iterable ToolInvoker = Callable[[str], bool] class Agent(ABC): """Interface for higher-level behaviours that orchestrate tools.""" @abstractmethod def run(self, *, invoke_tool: ToolInvoker, stop_event: Event) -> None: """Execute the agent strategy until finished or *stop_event* is set.""" @dataclass class FixedStrategyAgent(Agent): """Run a comma-separated list of tool specs in order.""" plan: str def run(self, *, invoke_tool: ToolInvoker, stop_event: Event) -> None: steps = [part.strip() for part in self.plan.split(",") if part.strip()] if not steps: print("[Agent] No tools configured for fixed strategy", file=sys.stderr) return for step in steps: if stop_event.is_set(): break success = invoke_tool(step) if not success: # Abort on first failure to avoid confusing behaviour break def build_agent(spec: str) -> Agent: normalized = spec.strip() if not normalized: raise RuntimeError("Agent specification must not be empty") parts = normalized.split(maxsplit=1) kind = parts[0].lower() config = parts[1].strip() if len(parts) > 1 else "" if kind in {"fixed", "strategy", "fixedstrategy"}: return FixedStrategyAgent(config) raise RuntimeError(f"Unknown agent type '{kind}'")