diff --git a/README.md b/README.md index 50d7dc2..262d5d8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Python-based Telnet helper for connecting to MUD servers, handling login flows, - Loads credentials and connection settings from a local `.env` file. - Interactive console session that mirrors server output and lets you type commands directly. - Optional always-on tool mode plus an on-demand `#execute ` escape hatch for ad-hoc automations. -- Higher-level agents (`FixedStrategyAgent` so far) that can string multiple tools together via `#agent `. +- Higher-level agents (`fixed`, `loop`) that can string multiple tools together via `#agent `. - Built-in tools (`SimpleTool`, `ExploreTool`, `CommunicationTool`, `MovementTool`, `IntelligentCommunicationTool`) with a pluggable interface for custom behaviours. ## Requirements @@ -50,6 +50,14 @@ Python-based Telnet helper for connecting to MUD servers, handling login flows, This example uses the fixed strategy agent to run `move` and then `explore` once. The first token after `#agent` selects the agent type (`fixed` today, more to come), and any remaining text is passed as that agent's configuration. +7. To run a looping agent that repeats tools, use: + + ```text + #agent loop move,explore + ``` + + Append `:delay` to pause between iterations, e.g. `#agent loop move,explore:2.5`. + ## Environment Variables All variables can be placed in the `.env` file (one `KEY=value` per line) or provided through the shell environment. diff --git a/agents.py b/agents.py index 0734819..fdb3119 100644 --- a/agents.py +++ b/agents.py @@ -2,9 +2,10 @@ from __future__ import annotations import sys from abc import ABC, abstractmethod +import itertools from dataclasses import dataclass from threading import Event -from typing import Callable, Iterable +from typing import Callable ToolInvoker = Callable[[str], bool] @@ -39,6 +40,30 @@ class FixedStrategyAgent(Agent): break +@dataclass +class LoopAgent(Agent): + """Continuously execute a fixed strategy until stopped.""" + + plan: str + delay: float = 0.0 + + 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 loop strategy", file=sys.stderr) + return + + for step in itertools.cycle(steps): + if stop_event.is_set(): + break + success = invoke_tool(step) + if not success: + break + if self.delay > 0: + if stop_event.wait(self.delay): + break + + def build_agent(spec: str) -> Agent: normalized = spec.strip() if not normalized: @@ -50,5 +75,15 @@ def build_agent(spec: str) -> Agent: if kind in {"fixed", "strategy", "fixedstrategy"}: return FixedStrategyAgent(config) + if kind in {"loop", "cycle"}: + delay = 0.0 + if ":" in config: + plan, delay_str = config.split(":", 1) + config = plan.strip() + try: + delay = float(delay_str.strip()) + except ValueError: + print(f"[Agent] Invalid delay '{delay_str}', defaulting to 0") + return LoopAgent(config, delay=delay) raise RuntimeError(f"Unknown agent type '{kind}'")