Everything an operator or integrator needs to understand what the harness does, what happens when you run it, and what you are (and aren't) trusting.
Status: pre-release / design-stage. The configuration keys, command names, and adapter flags below are the planned v1 interface. They are documented up front deliberately — this doc doubles as the spec the implementation targets — and will be kept in sync as the code lands.
| Piece | Runs where | Trust surface |
|---|---|---|
| AI port (engine PR) | inside your GoMud server | core engine code, reviewed upstream |
playtest module |
compiled into your GoMud server | full server privileges — auditable Go source |
mudagent adapter |
a separate process on your machine | a network client; no server privileges |
| personalities / goals / report spec | data your agent reads | inert files |
The only piece that runs inside your server with full privileges is the
playtest module. The adapter is "just" a telnet client. The personalities
and goals are data. Keep that mental model — it's the whole basis of the
trust discussion in §8.
- A GoMud build with the AI port. The port was contributed to GoMud core
(see
docs/pr/2026-06-05-engine-ai-port-pr.md) and is merged intomaster— any recentmasterhas it; on an older build use a branch that includes it. You'll also need Go installed and a workinggo build(this is how every GoMud module is compiled in). - The AI port enabled. It ships disabled (
Network.AI.Port: 0). To turn it on, open_datafiles/config.yamlin the root of your GoMud checkout (the folder withmain.go), find theNetwork:section, and underAI:setPort: 55555(or any non-zero port). Full key reference in §4; the exact YAML and an update-safe alternative are in step 3 of the README Quick Start. - The
gmcpmodule if you want structured state / Phase-2 beacons. It's bundled with GoMud by default (you don't install it separately). The adapter degrades to plain text without it, but goal verification is weaker.
go run . module install playtest
go generate && go build -o go-mud-server # rebuild; modules compile inWhat module install actually does (verified against GoMud's module manager):
- Fetches the central
module-registry.yamlfromGoMudEngine/GoMud-Modules. - Finds the
playtestentry and downloads the.tar.gzat itsurl(a tagged release on this repo). - Verifies the archive's sha256 against the value in the registry entry.
- Extracts it to
modules/playtest/and records the install inmodules/modules.lock.yaml.
The archive contains Go source (playtest.go + files/...), not a
binary. You then compile it yourself. See §8 for why that matters.
Skip the registry entirely: copy modules/playtest/ from this repo into your
GoMud checkout's modules/ directory, then go generate && go build. You're
building the exact source you can read here.
go run . module remove playtest
go generate && go build -o go-mud-serverThere are two separate config surfaces, edited in two different files. Getting this right is the single most common stumbling block, so read this before you touch anything:
| What | Lives in | How to set it |
|---|---|---|
Engine / network (Network.AI.*) |
_datafiles/config.yaml (or _datafiles/config-overrides.yaml) in your checkout root |
Edit the file directly. Read at boot → restart to apply. |
Module (Modules.playtest.*) |
modules/playtest/files/data-overlays/config.yaml (the module's bundled defaults, present after install) |
Edit that overlay file. Restart to apply. |
⚠️ The module-config gotcha. It is tempting to add aModules:→playtest:block toconfig.yamlorconfig-overrides.yaml. Don't — it won't take effect. During boot testing we confirmed that the module's own overlay default overrides a hand-edited baseconfig.yamlvalue, and a nestedModules.*block inconfig-overrides.yamldoes not merge into the module config map (it leaves the value empty). So your setting is silently ignored. Until the admin web config UI path is finalized (seedocs/followups.md), the confirmed working way to set module config is editingmodules/playtest/files/data-overlays/config.yaml. (The module needs no account/password, so this rarely matters in practice — the defaults are usable as-is.)
| Key | Default | Meaning |
|---|---|---|
Network.AI.Port |
0 |
AI telnet port. 0 = disabled. Set 55555 to enable. |
Network.AI.MaxConnections |
20 |
Max concurrent AI connections. |
Network.AI.CommandsPerRound |
2 |
Max commands per AI connection per round. |
| Key | Default | Meaning |
|---|---|---|
Enabled |
true |
Master switch for the module. |
SafeMode |
true |
Apply structural safety to AI-port testers (see §6). |
SandboxZoneTag |
(empty) | If set, confine AI-port testers to rooms carrying this tag. Empty = no confinement. |
DeathProtection |
true |
Protect AI-port characters from permadeath (high extra-lives). |
Beacons |
true |
Emit a Playtest.Round GMCP beacon each round to AI-port sessions (requires the bundled gmcp module; see §4a). |
There is no AccountName/AccountPassword — the module does not provision
or own an account. The agent logs in (or creates a character via the normal
new-player flow) like any player; the module's behaviors key off the AI-port
connection.
When Beacons is on, the module hooks each game round (events.NewRound) and
sends a Playtest.Round GMCP package to every live session on the AI port:
{"round": <n>, "hp": <int>, "hp_max": <int>, "sp": <int>, "sp_max": <int>, "room_id": <int>}The adapter surfaces this as a beacon event
({"type":"beacon","event":"Round","data":{...}}). It gives the agent a
reliable per-round pacing tick (replacing the brittle quiescence heuristic)
plus an atomic state snapshot to score goals against.
Dependency: beacons require the bundled gmcp module (the module calls its
exported SendGMCPEvent). If gmcp is absent, beacons disable themselves with a
startup warning — no crash — and the adapter falls back to quiescence pacing.
The module creates nothing at boot — no account, no character. If Enabled,
it simply registers behaviors that key off the AI-port connection:
- The per-round
Playtest.Roundbeacon (ifBeacons). - The structural safe-mode snap-back (if
SafeMode+ aSandboxZoneTag) and death protection on AI-port spawn (ifDeathProtection) — see §6. - The
ai-flag/ai-listadmin commands.
The test character is created/logged-in by the agent at run time (next), not provisioned by the module.
your agent ──spawn──▶ mudagent --target host:55555 --manifest run.yaml
▲ │ JSON events (stdout) │ telnet + GMCP, on the AI port
│ ▼ ▼
decide command ──stdin──▶ [GoMud + AI port + playtest + gmcp]
- Your agent spawns
mudagentwith a run manifest. - The adapter opens a telnet connection to the AI port and performs IAC/GMCP
negotiation (server
WILL GMCP→ clientDO GMCP→Core.Hello/Core.Supports.Set). With--user/--passwordit auto-logs-in; otherwise the agent drives login — and creates a character via the normal new-player flow if none exists (new→ username → password → …). New GoMud characters start as a pre-tutorial ghost; the agent advances past it like a real player. - The adapter streams one JSON object per line to stdout:
Because the connection is on the AI port, the game text is already ANSI-stripped by the engine;
{"type":"status","state":"connected"} {"type":"status","state":"logged_in"} {"type":"output","text":"<clean text>","raw":"<ansi>"} {"type":"gmcp","package":"Char.Vitals","data":{ }} {"type":"gmcp","package":"Room.Info","data":{ }} {"type":"beacon","event":"command_ack","data":{ }} // Phase 2 {"type":"error","message":"..."}rawpreserves the original if your agent wants it. - Your agent decides the next command and writes it to the adapter's stdin —
a plain line is sent to the MUD verbatim;
{"control":"quit"}(and future control verbs) drive the adapter itself. - The adapter sends the command, waits one round, and streams the response.
Remember the rate limit: the engine allows
AI.CommandsPerRound(default 2) commands per round; excess are dropped with a notice. Your agent should pace itself to the round tick. - Your agent verifies goals against GMCP/beacon state plus text and, on completion, writes a report per the report-format spec.
See it concretely.
framework/goals/examples/has a worked example per personality — the scenario, the goals YAML you'd write, and the report you should expect back. The bug-finder one is a real find that became upstream fix GoMud PR #602.
The original design imagined "intercept harmful actions and refuse them." Validation against GoMud showed that's not possible the way you'd hope: combat applies damage synchronously inside the combat step, so a module event listener can't cancel an attack after it's begun. Safe mode is therefore structural — it prevents the tester from being in a position to harm live players, rather than vetoing individual actions:
- Sandbox confinement (optional). If
SandboxZoneTagis set, an AI-port tester is confined to rooms carrying that tag (room tags are GoMud's documented extensibility hook). A move that would leave the sandbox is snapped back. This fails closed: if the destination isn't provably inside the sandbox, the tester is returned, not allowed to roam. - Death protection. With
DeathProtection, an AI-port character is shielded from permadeath (high extra-lives) on spawn, working within GoMud's global permadeath setting (which a module can't toggle per-account). - No-combat restriction is deferred (see Limitations /
docs/followups.md). Applying a buff by id isn't yet resolved, so combat safety today is delivered by confinement + death protection, not a no-combat flag.
If you run without a sandbox zone, the tester roams your live world as a
death-protected character. That's fine for many servers, but if you have PvP or
destructive interactions you care about, set a SandboxZoneTag and tag a
contained area.
Leaderboards: vanilla GoMud has no player leaderboard, so there's nothing to exclude the tester from out of the box. The engine PR adds the
IsAIprimitive precisely so that any present or future leaderboard (DOGMud already has one as a module) can filter test accounts with a one-line check.
| Command | What it does |
|---|---|
ai-flag <username> [on|off] |
Set or clear the IsAI flag on an account. |
ai-list |
List accounts flagged IsAI and any currently-connected AI-port sessions. |
These are module-provided (registered via the plugin command API), keeping the engine PR free of policy.
This is the section to read before installing any GoMud module, including this one.
module install downloads a .tar.gz of Go source and you compile it
into your own server binary. You are not running an opaque executable handed
to you — you're building readable source you can audit in this repo. This is
the same trust posture as adding any open-source Go dependency.
- Does: guarantee integrity — the archive you download matches the exact bytes recorded in the registry entry. The registry entry is added by a PR the GoMud maintainer reviews and merges, so the author can't silently swap the tarball later without another reviewed registry PR.
- Does NOT: guarantee safety. A matching hash means "these are the blessed bytes," not "these bytes are harmless." Integrity ≠ intent.
A GoMud module is compiled-in Go with no sandbox. It can do anything the server process can do — read/write data files, open connections, modify game state. This is true of every module in the registry, not just this one. There is no technical containment to fall back on.
- Auditable source. Read
modules/playtest/in this repo before you compile it. It's small and focused. - Reviewed registry entry. The hash is gated by a maintainer-reviewed PR.
- Build from source if you prefer. Skip the registry; vendor the source directly (§3, Option B).
- Least privilege for the tester character. Keep
SafeModeon, set aSandboxZoneTag, give the character a strong password when you create it, and don't grant it admin.
- Don't expose the AI port to the public internet without deliberate thought. It's a real login surface (anyone reaching it can create/log into a character). Bind it where only your agent runner can reach it, or firewall it.
- Choose a strong password when the agent creates the tester character — it's a normal account on a real login surface.
- The cap and rate limit are guardrails, not security.
AI.MaxConnectionsandAI.CommandsPerRoundbound resource use; they aren't authentication.
| Symptom | Likely cause | Fix |
|---|---|---|
| "Invalid login" / can't log in | The character doesn't exist yet (nothing is provisioned). | Run mudagent without --user/--password and have the agent create one via the new-player flow (new → username → password → …); pass those credentials on later runs to auto-login. |
| "…not flagged as AI but connected on the AI port" | Benign. The module keys off the AI-port connection, not the IsAI flag, so it doesn't pre-flag accounts. |
Nothing to fix — beacons/safe-mode work regardless. Use ai-flag <name> if you want the character excluded from a leaderboard. |
| Adapter can't connect | AI port not enabled (Network.AI.Port: 0), wrong port, or firewalled. |
Set a non-zero Network.AI.Port in _datafiles/config.yaml, restart, confirm it's listening (netstat -an | grep 55555), and make sure --target matches. |
| A config change had no effect | Engine config (Network.*) is read at boot; module config lives in the overlay; module code changes need a recompile. |
Restart after config/overlay edits; run go generate ./... && go build after changing module code. |
| "AI connection pool is full" | Network.AI.MaxConnections reached. |
Close stale sessions or raise the cap. |
| Commands silently dropped | Hitting Network.AI.CommandsPerRound. |
Pace your agent to the per-round beacon tick. |
No beacon events |
gmcp module absent, Beacons: false, or no client logged in on the AI port yet. |
Ensure gmcp is present (bundled by default) and Beacons: true; beacons fire per round once a client is logged in on the AI port. |
| No GMCP state at all | gmcp module not present, or the client didn't complete the GMCP handshake. |
Confirm the gmcp module is compiled in. |
| Tester wandered into live areas | No SandboxZoneTag set, or the target zone has no rooms carrying that tag. |
Set SandboxZoneTag and tag a contained area. |
go run . module remove playtest
go generate && go build -o go-mud-serverAny character the agent created persists as normal account data unless you
delete it through normal admin tooling. If you flagged it IsAI (via ai-flag),
clear it with ai-flag <name> off while the module is still installed.