From 348398616a389caf83801a62ee884455f90de1b2 Mon Sep 17 00:00:00 2001 From: Daniel Eder Date: Mon, 9 Feb 2026 10:15:12 +0100 Subject: [PATCH] feat: autoplay mode --- GOAL.md | 7 +++++++ README.md | 7 +++++++ SYSTEM.md | 7 +++++-- app.py | 29 +++++++++++++++++++++++++---- goal_loader.py | 13 +++++++++++++ mud_env.py | 3 +++ textual_ui.py | 30 ++++++++++++++++++++++++++++-- 7 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 GOAL.md create mode 100644 goal_loader.py diff --git a/GOAL.md b/GOAL.md new file mode 100644 index 0000000..11d82d7 --- /dev/null +++ b/GOAL.md @@ -0,0 +1,7 @@ +Explore cautiously and map nearby rooms. + +Priorities: +- Avoid obvious combat when possible. +- Prefer gathering room information before moving. +- Respond politely to direct tells. +- Keep actions short and reversible. diff --git a/README.md b/README.md index b31223c..038fcaa 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ All variables can be placed in the `.env` file (one `KEY=value` per line) or pro | `MISTLE_TOOL_MODE` | ❌ | Enable full-time tool thread when set to truthy values (`1`, `true`, `yes`, `on`). Defaults to interactive-only mode. | | `MISTLE_TOOL` | ❌ | Select which tool class to instantiate when tool mode is active. Accepted values: `look` (default, alias `simple`), `explore`, `communication`, `movement`, `intelligent`/`intelligentcommunication` (LLM-backed), or custom spec `module:ClassName`. | | `MISTLE_SIDELOAD_TOOL` | ❌ | Comma-separated list of tool specs that should always run in the background (same syntax as `MISTLE_TOOL`). Useful for running `intelligent` alongside another primary tool. | +| `MISTLE_AUTOPLAY` | ❌ | When truthy (`1`, `true`, `yes`, `on`), starts an intelligent agent automatically at startup and injects instructions from `GOAL.md`. | | `MISTLE_LLM_MODEL` | ❌ | Override the `litellm` model used by the intelligent tool (defaults to `mistral/mistral-small-2407`). | | `MISTRAL_API_KEY` | ❌ | API key used by `IntelligentCommunicationTool` (via `litellm`) when calling the `mistral/mistral-small-2407` model. | @@ -96,6 +97,12 @@ All variables can be placed in the `.env` file (one `KEY=value` per line) or pro - This applies to both the intelligent agent and the intelligent communication tool. - If `SYSTEM.md` is missing or empty, the app falls back to built-in defaults and logs a warning. +## Goal File + +- `GOAL.md` is used when autoplay is enabled (`MISTLE_AUTOPLAY=true`). +- On startup, the app launches the intelligent agent automatically and passes the contents of `GOAL.md` as runtime instructions. +- If `GOAL.md` is missing or empty, autoplay still starts but without extra instructions. + ## Tool Development - Implement new tools by subclassing `tools.Tool` and overriding `observe()` and `decide()`. diff --git a/SYSTEM.md b/SYSTEM.md index a514404..031d469 100644 --- a/SYSTEM.md +++ b/SYSTEM.md @@ -9,10 +9,13 @@ Your goal is to level up as much as possible and explore the world. ## Tool Usage Use tools only when appropriate. Think how you can solve problems without -using a tool. Do not use the "exploration" tool multiple times in a row. +using a tool. Only use the "explore" tool as a last resort to gather information +as it requires a lot of time to run. ## Mud Commands The following MUD commands may be helpful to you schau - get a description of the current environment info - examine your own stats -hilfe - get additional help in general and about specific commands \ No newline at end of file +hilfe - get additional help in general and about specific commands +untersuche - examine a single object or detail in the current room or your inventory +inv - show your current inventory \ No newline at end of file diff --git a/app.py b/app.py index a480a79..8d7cfc9 100644 --- a/app.py +++ b/app.py @@ -6,6 +6,7 @@ from typing import Optional from agent_runtime import run_agent from agents import Agent, build_agent +from goal_loader import load_goal_instructions from mud_env import load_env_file, read_connection_settings, read_tool_settings from mud_session import ( SessionState, @@ -148,7 +149,10 @@ def main() -> int: print(f"[Agent] Failed to configure '{spec}': {exc}", file=sys.stderr) return - _prime_agent(temp_agent, state.snapshot_output()) + _start_agent_thread(temp_agent, label=spec) + + def _start_agent_thread(agent: Agent, *, label: str) -> None: + _prime_agent(agent, state.snapshot_output()) def run_tool_instance(tool: Tool) -> bool: run_tool_loop( @@ -160,12 +164,12 @@ def main() -> int: auto_stop=True, auto_stop_idle=AUTO_STOP_IDLE_SECONDS, ) - _prime_agent(temp_agent, state.snapshot_output()) + _prime_agent(agent, state.snapshot_output()) return True thread = Thread( target=run_agent, - args=(temp_agent,), + args=(agent,), kwargs={ "build_tool": build_tool, "run_tool": run_tool_instance, @@ -175,9 +179,26 @@ def main() -> int: daemon=True, ) agent_threads.append(thread) - print(f"[Agent] Executing {spec!r}") + print(f"[Agent] Executing {label!r}") thread.start() + if tool_settings.autoplay: + try: + autoplay_agent = build_agent("intelligent", allowed_tools=TOOL_DESCRIPTIONS) + except RuntimeError as exc: + print(f"[Autoplay] Failed to configure intelligent agent: {exc}", file=sys.stderr) + else: + goal = load_goal_instructions() + if goal: + setattr(autoplay_agent, "instruction", goal) + print("[Autoplay] Loaded instructions from GOAL.md") + else: + print( + "[Autoplay] GOAL.md missing or empty; starting without extra instructions", + file=sys.stderr, + ) + _start_agent_thread(autoplay_agent, label="intelligent (autoplay)") + interrupted = False try: interactive_session( diff --git a/goal_loader.py b/goal_loader.py new file mode 100644 index 0000000..3fc7fc8 --- /dev/null +++ b/goal_loader.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from pathlib import Path + + +def load_goal_instructions(path: str = "GOAL.md") -> str: + goal_path = Path(path) + if not goal_path.exists(): + return "" + try: + return goal_path.read_text(encoding="utf-8").strip() + except OSError: # pragma: no cover - environment specific + return "" diff --git a/mud_env.py b/mud_env.py index c7df8ee..25e9de9 100644 --- a/mud_env.py +++ b/mud_env.py @@ -20,6 +20,7 @@ class ToolSettings: tool_mode: bool tool_spec: str sideload_specs: list[str] + autoplay: bool def load_env_file(path: str = ".env") -> None: @@ -83,8 +84,10 @@ def read_tool_settings() -> ToolSettings: tool_mode = parse_bool(os.environ.get("MISTLE_TOOL_MODE", "")) tool_spec = os.environ.get("MISTLE_TOOL", "") sideload_specs = parse_csv(os.environ.get("MISTLE_SIDELOAD_TOOL", "")) + autoplay = parse_bool(os.environ.get("MISTLE_AUTOPLAY", "")) return ToolSettings( tool_mode=tool_mode, tool_spec=tool_spec, sideload_specs=sideload_specs, + autoplay=autoplay, ) diff --git a/textual_ui.py b/textual_ui.py index a2defea..15389f3 100644 --- a/textual_ui.py +++ b/textual_ui.py @@ -12,6 +12,7 @@ from textual.widgets import Footer, Header, Input, Label, Log, Static from agent_runtime import run_agent from agents import Agent, build_agent +from goal_loader import load_goal_instructions from mud_env import load_env_file, read_connection_settings, read_tool_settings from mud_session import SessionState, graceful_shutdown, login, run_tool_loop from mud_tools import TOOL_DESCRIPTIONS, build_tool @@ -196,6 +197,9 @@ class MudUI(App): sideload_seen.add(lowered) self._launch_persistent_tool(spec) + if tool_settings.autoplay: + self._start_autoplay_agent() + self._set_status(f"Connected to {connection.host}:{connection.port}") self.input.focus() self.log_mud(f"Connected to {connection.host}:{connection.port}") @@ -326,7 +330,28 @@ class MudUI(App): self.log_brain(f"[Agent] Failed to configure '{spec}': {exc}") return - self.log_brain(f"[Agent] Executing {spec!r}") + self._start_agent_instance(agent, label=spec) + + def _start_autoplay_agent(self) -> None: + try: + autoplay_agent = build_agent("intelligent", allowed_tools=TOOL_DESCRIPTIONS) + except RuntimeError as exc: + self.log_brain(f"[Autoplay] Failed to configure intelligent agent: {exc}") + return + + goal = load_goal_instructions() + if goal: + setattr(autoplay_agent, "instruction", goal) + self.log_brain("[Autoplay] Loaded instructions from GOAL.md") + else: + self.log_brain( + "[Autoplay] GOAL.md missing or empty; starting without extra instructions" + ) + + self._start_agent_instance(autoplay_agent, label="intelligent (autoplay)") + + def _start_agent_instance(self, agent: Agent, *, label: str) -> None: + self.log_brain(f"[Agent] Executing {label!r}") self._prime_agent(agent) def run_tool_instance(tool: Tool) -> bool: @@ -355,7 +380,8 @@ class MudUI(App): stop_event=self._stop_event, ) - self._start_worker(run, name=f"agent-{spec}") + worker_name = f"agent-{label.replace(' ', '-').lower()}" + self._start_worker(run, name=worker_name) def _prime_agent(self, agent: Agent) -> None: last_output = self.state.snapshot_output()