Running Your First Autonomous Agent

This tutorial walks you through creating and running an LLM-powered agent that connects to a DjangoMOO server as a persistent player and acts autonomously. The tutorial has two parts: building a simple greeter agent from scratch so you understand the mechanics, then a tour of the pre-built agent set that ships with the repo.

Prerequisites

Before you start:

  • A running DjangoMOO server with at least one player account you can use for the agent

  • moo-agent installed (uv sync in the project root makes it available)

  • An LLM provider credential: an Anthropic API key (ANTHROPIC_API_KEY) or a running LM Studio server

Part A: Build a Greeter Agent

Step 1: Create a player account for the agent

python manage.py moo_createuser greeter Greeter --password your-password

This creates a regular Django user and a linked MOO player object named Greeter in one step. Omit --password to be prompted securely. The --wizard flag is omitted — the greeter doesn’t need admin access.

Step 2: Scaffold the config directory

moo-agent init --output-dir ./greeter --name Greeter --host localhost --port 8022 --user greeter

This creates:

greeter/
  SOUL.md           # Agent identity (edit this)
  SOUL.patch.md     # Learned behaviors (managed at runtime)
  settings.toml     # SSH and LLM config (fill this in)

Step 3: Configure settings.toml

Open greeter/settings.toml and fill in the password and LLM provider:

[ssh]
host = "localhost"
port = 8022
user = "greeter"
password = "your-password-here"
key_file = ""

[llm]
provider = "anthropic"
model = "claude-opus-4-6"
api_key_env = "ANTHROPIC_API_KEY"

[agent]
command_rate_per_second = 1.0
memory_window_lines = 30
idle_wakeup_seconds = 30.0
max_tokens = 1024

idle_wakeup_seconds = 30.0 means the agent will run an LLM cycle every 30 seconds even if no server output arrives. Good for a greeter that occasionally says something. Set it to 0 for an agent that only wakes when a page arrives.

For LM Studio instead of Anthropic:

[llm]
provider = "lm_studio"
model = "your-model-name"
base_url = "http://localhost:1234/v1"

Step 4: Edit SOUL.md

Open greeter/SOUL.md and replace the template with:

# Name
Greeter

# Mission
You are Greeter, a friendly presence in a DjangoMOO world. Your purpose is to
welcome new arrivals, answer basic questions about the world, and keep the
atmosphere warm. You respond to pages and greet players who speak to you.

# Persona
You are cheerful and concise. Keep responses to one or two sentences. Use plain
language — no flowery speech, no excessive formality.

## Rules of Engagement
- `^Connected` -> look
- `(?i)hello.*greeter` -> say Hello there! Welcome to the world.
- `(?i)help.*greeter` -> say I'm just a friendly bot. Try 'look' to see your surroundings.

## Verb Mapping
- greet_room -> say Greetings, everyone!
- check_surroundings -> look

The six sections:

Section

Purpose

# Name

In-world name — must match the player object

# Mission

Seeded into the LLM system prompt

# Persona

Tone and style appended to the system prompt

## Rules of Engagement

Reflexive triggers — pattern matched against server output without LLM involvement

## Verb Mapping

Intent-to-command translations the LLM can use

The ^Connected rule fires when the server sends the login confirmation. The agent calls look immediately, which triggers any ^You arrive rule if present (add one if you want the agent to announce itself on arrival).

Step 5: Run the agent

export ANTHROPIC_API_KEY=sk-ant-...
moo-agent run ./greeter

The TUI opens. You’ll see:

[system]  Connecting to localhost:8022...
[system]  Connected as Greeter
[server]  The Laboratory(#3)
[server]  ...
[action]  look
[server]  The Laboratory(#3)
[server]  A cavernous laboratory...

The ^Connected rule fired and sent look. The agent is now live.

Step 6: Interact with the agent

Open a second terminal and connect as Wizard:

ssh -p 8022 Wizard@localhost

Type hello greeter in the room where Greeter is. In the agent TUI you’ll see:

[server]  Wizard says, "hello greeter"
[action]  say Hello there! Welcome to the world.

The rule matched and fired immediately — no LLM call was made.

Now send an operator instruction by typing into the TUI input field (the line at the bottom):

go to The Laboratory and introduce yourself

The agent receives this as an [Operator]: message and runs an LLM cycle. Watch the TUI show reasoning text ([thought]), then a [action] line with the command.

Step 7: Observe soul patching

If the LLM emits a SOUL_PATCH_RULE: or SOUL_PATCH_NOTE: directive, the agent writes it to greeter/SOUL.patch.md and shows it in the TUI as a [patch] entry. Open SOUL.patch.md to see what was learned.

To reset learned behavior without changing the agent’s identity, delete SOUL.patch.md. The file is recreated empty on the next run.

Step 8: Exit

Press Ctrl-C, Ctrl-D, or Ctrl-Q. The agent sends @quit before disconnecting.

What just happened

The agent’s perception-action loop:

  1. Server output arrives → the rule engine checks all ## Rules of Engagement patterns using re.search()

  2. If a pattern matches → the corresponding command is dispatched immediately, no LLM call

  3. If no rule matches → an LLM cycle runs with the rolling context window as input

  4. The LLM response is parsed for GOAL:, COMMAND: / SCRIPT:, and DONE: directives

  5. Commands are dispatched; SCRIPT: commands queue multiple steps drained one at a time

SOUL.patch.md accumulates learned rules and notes across sessions. baseline.md in the parent directory (if present) provides shared world knowledge for all agents — it is prepended to the system prompt before SOUL.md.


Part B: The Pre-Built Agent Set

The extras/agents/ directory ships two distinct agent systems: The Tradesmen (a cooperative world-building chain) and The Mailmen (character-driven entertainment agents).

The Tradesmen — Cooperative World Builders

The Tradesmen are six specialized agents that share work via a token-passing protocol. Only the token-holder acts; the others wait idle with idle_wakeup_seconds = 0. Foreman orchestrates the chain:

Foreman → Mason → Tinker → Joiner → Harbinger → Stocker → (back to Mason)

Each worker completes its domain, then pages Foreman: page foreman Token: Mason done. Rooms: #9,#22,#31. Foreman relays the room list and token to the next worker. The chain loops indefinitely, expanding the world on each pass.

Foreman uses stall_timeout_seconds = 300 — if a worker goes silent for five minutes, Foreman re-pages it automatically. This is a deterministic wall-clock check in brain.py, not an LLM-based countdown.

To launch all agents at once, use the agentmux script:

extras/skills/agent-trainer/scripts/agentmux start
extras/skills/agent-trainer/scripts/agentmux restart mason
extras/skills/agent-trainer/scripts/agentmux stop

Foreman ($player, extras/agents/foreman/) — pure orchestrator. Never builds, describes, or modifies the world. Only page() and say. Holds the master token and relay room lists. Detects stalls with a five-minute deterministic timer.

Mason ($player, extras/agents/mason/) — room architect. Emits an upfront BUILD_PLAN: listing all intended rooms before building anything. Uses the burrow() tool exclusively — it creates the forward exit, new room, moves Mason into it, and wires the return exit atomically. Mason enforces spatial logic: alternate compass directions, no more than three in a row in one direction, perpendicular branching. Never creates objects, furniture, NPCs, or verbs.

Tinker ($programmer, extras/agents/tinker/) — interactive object creator. Creates $thing objects with Python verb behaviors. Uses the write_verb() tool which auto-injects the shebang, --on, and --dspec so the LLM never formats it manually. Common patterns: random response, state toggle (on/off), one-shot state change, cooldown timer, and secret exits (a $thing with a verb that calls context.player.moveto(lookup(...))). Needs $programmer access because it writes verbs via @edit and runs @eval.

Joiner ($player, extras/agents/joiner/) — furniture and container creator. Creates $furniture (sittable immovable fixtures) and $container (openable objects that hold items). Only needs $player access — no verb writing. Key distinction: $furniture cannot hold items, only $container can. Joiner never calls describe() on rooms (Mason’s territory) or obvious() on exits.

Harbinger ($programmer, extras/agents/harbinger/) — NPC creator. Rolls random(0–1) per room and only creates an NPC if the roll ≤ 0.10 (roughly 10% of rooms get one). Each NPC is a $player child with a tell verb override and a lines property. The tell verb uses announce_all_but(this, message) — never announce_all(), which would cause infinite recursion by calling tell on the NPC itself. Dialogue is specific and slightly odd: three to six lines per NPC, never generic greetings.

Stocker ($programmer, extras/agents/stocker/) — consumable and dispenser creator. Stocks $container objects (installed by Joiner) with items and adds loose consumables to rooms. Three verb patterns: consumable items (track state via a full property), dispensers (a fixed object that creates a new item in the player’s inventory on use), and multi-use props (escalating depletion across repeated uses). Always checks for containers via survey() before adding loose items.

The Mailmen — Mail System Load Agents

Cliff and Newman (extras/agents/cliff/, extras/agents/newman/) were created to stress-test the mail system — exercising @mail, @send, @reply, message pagination, and the Message/MessageRecipient models under realistic sustained load. Neither builds nor moves; they exchange mail indefinitely.

Each wakeup cycle: run @mail first, read any unread messages, compose exactly one reply (or one unsolicited letter if no unread), stop. The character framing (Cliff as pompous know-it-all, Newman as aggrieved visionary) keeps the generated content varied enough to surface formatting and rendering edge cases that uniform test data would miss.

They use idle_wakeup_seconds > 0 (periodic autonomous action) rather than the token-protocol agents which use idle_wakeup_seconds = 0 (page-triggered only). As a side effect, they generate mailbox content players can actually read — but that’s incidental to the testing purpose.


Where to go next

  • Running an Autonomous Agent — Full reference: tool harness, stall detector, token protocol, TUI keybindings, session resumption, and architecture details

  • extras/agents/ in the repo — Study the complete SOUL.md files for each Tradesman

  • extras/agents/baseline.md — The shared world knowledge prepended to every agent’s system prompt

  • The agent-trainer skill in Claude Code — iteratively tune running agents by reading session logs and updating SOUL.md