Compare commits
No commits in common. "905e3c91abaad9449347a06727da1e8e95013929" and "f5fc6dbd145bb9f9e41b5845c1ad6a91c37f7cac" have entirely different histories.
905e3c91ab
...
f5fc6dbd14
4 changed files with 2 additions and 152 deletions
|
|
@ -8,7 +8,6 @@ Python-based Telnet helper for connecting to MUD servers, handling login flows,
|
||||||
- Loads credentials and connection settings from a local `.env` file.
|
- Loads credentials and connection settings from a local `.env` file.
|
||||||
- Interactive console session that mirrors server output and lets you type commands directly.
|
- Interactive console session that mirrors server output and lets you type commands directly.
|
||||||
- Optional always-on tool mode plus an on-demand `#execute <tool>` escape hatch for ad-hoc automations.
|
- Optional always-on tool mode plus an on-demand `#execute <tool>` escape hatch for ad-hoc automations.
|
||||||
- Higher-level agents (`FixedStrategyAgent` so far) that can string multiple tools together via `#agent <spec>`.
|
|
||||||
- Built-in tools (`SimpleTool`, `ExploreTool`, `CommunicationTool`, `MovementTool`, `IntelligentCommunicationTool`) with a pluggable interface for custom behaviours.
|
- Built-in tools (`SimpleTool`, `ExploreTool`, `CommunicationTool`, `MovementTool`, `IntelligentCommunicationTool`) with a pluggable interface for custom behaviours.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
@ -42,14 +41,6 @@ Python-based Telnet helper for connecting to MUD servers, handling login flows,
|
||||||
|
|
||||||
The command remains interactive while the tool works in the background and stops automatically a few seconds after things quiet down.
|
The command remains interactive while the tool works in the background and stops automatically a few seconds after things quiet down.
|
||||||
|
|
||||||
6. To run an agent that orchestrates several tools, use:
|
|
||||||
|
|
||||||
```text
|
|
||||||
#agent fixed move,explore
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
All variables can be placed in the `.env` file (one `KEY=value` per line) or provided through the shell environment.
|
All variables can be placed in the `.env` file (one `KEY=value` per line) or provided through the shell environment.
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from threading import Event
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from agents import Agent
|
|
||||||
from tools import Tool
|
|
||||||
|
|
||||||
ToolBuilder = Callable[[str], Tool]
|
|
||||||
ToolRunner = Callable[[Tool], None]
|
|
||||||
|
|
||||||
|
|
||||||
def run_agent(
|
|
||||||
agent: Agent,
|
|
||||||
*,
|
|
||||||
build_tool: ToolBuilder,
|
|
||||||
run_tool: ToolRunner,
|
|
||||||
stop_event: Event,
|
|
||||||
) -> None:
|
|
||||||
"""Execute *agent* by wiring it to the tool runtime."""
|
|
||||||
|
|
||||||
def invoke_tool(spec: str) -> bool:
|
|
||||||
name = spec.strip()
|
|
||||||
if not name:
|
|
||||||
print("[Agent] Ignoring empty tool spec", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
tool = build_tool(name)
|
|
||||||
except RuntimeError as exc:
|
|
||||||
print(f"[Agent] Failed to load tool '{name}': {exc}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
print(f"[Agent] Running tool '{name}'")
|
|
||||||
run_tool(tool)
|
|
||||||
return True
|
|
||||||
|
|
||||||
agent.run(invoke_tool=invoke_tool, stop_event=stop_event)
|
|
||||||
54
agents.py
54
agents.py
|
|
@ -1,54 +0,0 @@
|
||||||
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}'")
|
|
||||||
54
app.py
54
app.py
|
|
@ -8,8 +8,6 @@ from threading import Event, Lock, Thread
|
||||||
from typing import Callable, Optional, Type
|
from typing import Callable, Optional, Type
|
||||||
|
|
||||||
from tools import Tool, SimpleTool
|
from tools import Tool, SimpleTool
|
||||||
from agents import Agent, build_agent
|
|
||||||
from agent_runtime import run_agent
|
|
||||||
from telnetclient import TelnetClient
|
from telnetclient import TelnetClient
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,6 +126,7 @@ def run_tool_loop(
|
||||||
continue
|
continue
|
||||||
maybe_send()
|
maybe_send()
|
||||||
|
|
||||||
|
|
||||||
def load_env_file(path: str = ".env") -> None:
|
def load_env_file(path: str = ".env") -> None:
|
||||||
"""Populate ``os.environ`` with key/value pairs from a dotenv file."""
|
"""Populate ``os.environ`` with key/value pairs from a dotenv file."""
|
||||||
env_path = Path(path)
|
env_path = Path(path)
|
||||||
|
|
@ -258,7 +257,6 @@ def interactive_session(
|
||||||
receive_timeout: float = 0.2,
|
receive_timeout: float = 0.2,
|
||||||
exit_command: str,
|
exit_command: str,
|
||||||
tool_command: Optional[Callable[[str], None]] = None,
|
tool_command: Optional[Callable[[str], None]] = None,
|
||||||
agent_command: Optional[Callable[[str], None]] = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Keep the Telnet session running, proxying input/output until interrupted."""
|
"""Keep the Telnet session running, proxying input/output until interrupted."""
|
||||||
if exit_command:
|
if exit_command:
|
||||||
|
|
@ -281,15 +279,7 @@ def interactive_session(
|
||||||
line = line.rstrip("\r\n")
|
line = line.rstrip("\r\n")
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
lowered = line.lower()
|
if tool_command and line.lower().startswith("#execute"):
|
||||||
if agent_command and lowered.startswith("#agent"):
|
|
||||||
parts = line.split(maxsplit=1)
|
|
||||||
if len(parts) == 1:
|
|
||||||
print("[Agent] Usage: #agent <agent_spec>")
|
|
||||||
else:
|
|
||||||
agent_command(parts[1])
|
|
||||||
continue
|
|
||||||
if tool_command and lowered.startswith("#execute"):
|
|
||||||
parts = line.split(maxsplit=1)
|
parts = line.split(maxsplit=1)
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
print("[Tool] Usage: #execute <tool_spec>")
|
print("[Tool] Usage: #execute <tool_spec>")
|
||||||
|
|
@ -346,7 +336,6 @@ def main() -> int:
|
||||||
tool_thread: Optional[Thread] = None
|
tool_thread: Optional[Thread] = None
|
||||||
tool: Optional[Tool] = None
|
tool: Optional[Tool] = None
|
||||||
ephemeral_tools: list[Thread] = []
|
ephemeral_tools: list[Thread] = []
|
||||||
agent_threads: list[Thread] = []
|
|
||||||
|
|
||||||
with TelnetClient(host=host, port=port, timeout=10.0) as client:
|
with TelnetClient(host=host, port=port, timeout=10.0) as client:
|
||||||
login(
|
login(
|
||||||
|
|
@ -391,42 +380,6 @@ def main() -> int:
|
||||||
print(f"[Tool] Executing {spec!r} once")
|
print(f"[Tool] Executing {spec!r} once")
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def run_ephemeral_agent(spec: str) -> None:
|
|
||||||
spec = spec.strip()
|
|
||||||
if not spec:
|
|
||||||
print("[Agent] Usage: #agent <agent_spec>")
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
temp_agent = build_agent(spec)
|
|
||||||
except RuntimeError as exc:
|
|
||||||
print(f"[Agent] Failed to configure '{spec}': {exc}", file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_tool_instance(tool: Tool) -> None:
|
|
||||||
run_tool_loop(
|
|
||||||
client,
|
|
||||||
state,
|
|
||||||
tool,
|
|
||||||
stop_event,
|
|
||||||
min_send_interval=1.0,
|
|
||||||
auto_stop=True,
|
|
||||||
auto_stop_idle=2.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
thread = Thread(
|
|
||||||
target=run_agent,
|
|
||||||
args=(temp_agent,),
|
|
||||||
kwargs={
|
|
||||||
"build_tool": build_tool,
|
|
||||||
"run_tool": run_tool_instance,
|
|
||||||
"stop_event": stop_event,
|
|
||||||
},
|
|
||||||
daemon=True,
|
|
||||||
)
|
|
||||||
agent_threads.append(thread)
|
|
||||||
print(f"[Agent] Executing {spec!r}")
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
interrupted = False
|
interrupted = False
|
||||||
try:
|
try:
|
||||||
interactive_session(
|
interactive_session(
|
||||||
|
|
@ -435,7 +388,6 @@ def main() -> int:
|
||||||
stop_event=stop_event,
|
stop_event=stop_event,
|
||||||
exit_command=exit_command,
|
exit_command=exit_command,
|
||||||
tool_command=None if tool_mode else run_ephemeral_tool,
|
tool_command=None if tool_mode else run_ephemeral_tool,
|
||||||
agent_command=run_ephemeral_agent,
|
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print()
|
print()
|
||||||
|
|
@ -446,8 +398,6 @@ def main() -> int:
|
||||||
tool_thread.join(timeout=1.0)
|
tool_thread.join(timeout=1.0)
|
||||||
for thread in ephemeral_tools:
|
for thread in ephemeral_tools:
|
||||||
thread.join(timeout=1.0)
|
thread.join(timeout=1.0)
|
||||||
for thread in agent_threads:
|
|
||||||
thread.join(timeout=1.0)
|
|
||||||
|
|
||||||
if interrupted:
|
if interrupted:
|
||||||
graceful_shutdown(client, exit_command, state=state)
|
graceful_shutdown(client, exit_command, state=state)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue