feat: autoplay mode

This commit is contained in:
Daniel Eder 2026-02-09 10:15:12 +01:00
parent 9af879eadc
commit 348398616a
No known key found for this signature in database
GPG key ID: CE7446DFCE599F32
7 changed files with 88 additions and 8 deletions

7
GOAL.md Normal file
View file

@ -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.

View file

@ -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()`.

View file

@ -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
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

29
app.py
View file

@ -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(

13
goal_loader.py Normal file
View file

@ -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 ""

View file

@ -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,
)

View file

@ -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()