stephecw commited on
Commit
7afb96c
·
verified ·
1 Parent(s): f4c3b74

Upload explanations.md

Browse files
Files changed (1) hide show
  1. explanations.md +409 -0
explanations.md ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Building a Reliable MCP Agent for Zork-Style Text Adventures
2
+
3
+ Text adventures sound trivial: you read a paragraph, type a command, get a new paragraph.
4
+ But once you put an LLM in that loop, you learn quickly that the hardest enemies aren’t in the dungeon—they’re in the interface.
5
+
6
+ What kills most LLM agents in Zork-like games is a predictable set of failure modes:
7
+
8
+ - **Parser brittleness**: the game rejects slightly-wrong phrasing.
9
+ - **Looping**: the model repeats actions, rooms, or “no-op” moves.
10
+ - **Move budget waste**: doing “admin” actions that consume moves.
11
+ - **Prompt bloat**: raw history gets too long and too noisy.
12
+ - **Goal drift**: the model forgets what it was trying to do.
13
+
14
+ Some of these ideas are also exposed in the article "TextQuests: How Good are LLMs at Text-Based Video Games?" (https://arxiv.org/pdf/2507.23701) namely memory, coherence and planning.
15
+
16
+ So we didn’t build “a prompt.” We built a **system** with two main components:
17
+ - an **MCP server** that exposes the game through robust tools and instrumentation
18
+ - and an **agent** that treats the LLM as one component among others (memory, planning, recovery policies)
19
+
20
+ Our focus was on the previous failure modes, and how to design around them with tools and guardrails.
21
+
22
+ This is a high-level tour of the approach, focusing on the big ideas, without getting into implementation details.
23
+
24
+ The code is available in the HuggingFace space: https://huggingface.co/spaces/LLM-course/Text-game-agent-EILLES
25
+
26
+ ---
27
+
28
+ ## The Setup: Two Pieces, One Loop
29
+
30
+ ### 1) `mcp_server.py` — the game adapter + instrumentation layer
31
+ The MCP server acts like the game interface for the agent. It:
32
+ - owns the environment (`TextAdventureEnv`)
33
+ - runs commands (`play_action`)
34
+ - tracks exploration metadata (rooms, transitions, tried actions)
35
+ - exposes tools that help reasoning **without spending moves**
36
+ - provides safety mechanisms like checkpoints and action simulation
37
+
38
+ ### 2) `agent.py` — the policy engine + ReAct decision-maker
39
+ The agent:
40
+ - outputs strict **ReAct** steps (THOUGHT -> TOOL -> ARGS)
41
+ - can only interact via MCP tools (never “talks to the game” directly)
42
+ - uses guardrails to keep the LLM from hallucinating tools/commands, looping, spamming, etc.
43
+ - uses *two additional LLM calls* as specialized modules:
44
+ - **memory compression** (long-term, high-signal memory)
45
+ - **objective planning** (goal updates + suggested next actions)
46
+
47
+ vWe treat the LLM as a reasoning module, not as a reliable system controller.
48
+ All safety, consistency, and state tracking are enforced outside the model.
49
+
50
+ ---
51
+
52
+ ## Why “Tooling” is important: The MCP Server as a Game Interface
53
+
54
+ A Zork parser is not a friendly API. If the model invents commands like *“look around carefully”*, the game will often respond with something like:
55
+ > “That sentence isn’t one I recognize.”
56
+
57
+ If you only expose `play_action`, the agent becomes a guessing machine.
58
+
59
+ So the MCP server provides a richer interface that makes the world “legible”:
60
+
61
+ - **Structured state** (score, moves, inventory, room, “done”, a stable hash)
62
+ - **Inventory without spending a move**
63
+ - **Valid actions** (best-effort list) for recovery
64
+ - **A map/graph** of explored rooms and transitions
65
+ - **Actions tried per room** to avoid repeating
66
+ - **Checkpoints** to rollback after loops or risky moves
67
+ - **Action probing** (simulate before committing)
68
+
69
+ This set of tools is what turns the text game into something the agent can navigate reliably.
70
+
71
+ ---
72
+
73
+ # Part 1 — The MCP Server: Turning a Game into a Usable API
74
+
75
+ ## The Server’s Core Idea: Track More Than the Game Tracks
76
+
77
+ The environment gives you:
78
+ - observation text
79
+ - score/moves (usually)
80
+ - maybe inventory (depending on wrapper)
81
+
82
+ But it *doesn’t* give you the extra structure an agent needs to be efficient:
83
+ - Where have I been?
84
+ - What did I already try here?
85
+ - How do rooms connect?
86
+ - Am I stuck in a loop?
87
+
88
+ So the server maintains that meta-state itself:
89
+ - a short **history** of actions and results
90
+ - a set of **locations** (rooms) discovered
91
+ - a **transition graph** (`room --action--> room`)
92
+ - an index of **actions tried per location**
93
+ - checkpoint snapshots for rollback
94
+ - a stable-ish **state hash** used to detect loops
95
+
96
+ This is *not* just logging. It becomes actionable tool output the agent can rely on.
97
+
98
+ ---
99
+
100
+ ## Room Awareness: The Small Heuristic That Makes Everything Work
101
+
102
+ Most downstream reasoning depends on “what room am I in?”
103
+
104
+ The server uses a heuristic to extract the room title from the observation:
105
+ - pick the first plausible “header-like” line
106
+ - ignore copyright/revision boilerplate
107
+ - ignore long narrative sentences
108
+
109
+ This matters because room identity powers:
110
+ - mapping
111
+ - “tried actions” grouping
112
+ - loop detection context
113
+ - objective tracking (“return to grating”, “open mailbox”, etc.)
114
+
115
+ If you don’t have stable room identity, the agent’s memory becomes confused.
116
+
117
+ ---
118
+
119
+ ## The Minimal but Critical Tools
120
+
121
+ ### `play_action(action)`
122
+ The main interaction tool:
123
+ - runs the command
124
+ - returns the observation
125
+ - appends optional “+points” signals and “GAME OVER”
126
+ - never crashes the tool (so the run doesn’t die on edge cases)
127
+
128
+ This tool is deliberately boring—but highly reliable.
129
+
130
+ ### `inventory()`
131
+ A huge move-saver: it returns inventory **without advancing the game**.
132
+ In text adventures, calling `inventory` as a game command costs a move in many setups, so treating inventory as a *tool query* is a big advantage.
133
+
134
+ ### `memory()`
135
+ A compact summary tool that provides “authoritative state”:
136
+ - location
137
+ - score/moves
138
+ - recent action heads
139
+ - last observation
140
+
141
+ It’s a sanity anchor when the agent gets confused.
142
+
143
+ ### `valid_actions()`
144
+ An helpful tool when stuck:
145
+ - tries to fetch the actual valid actions if the environment exposes them
146
+ - otherwise falls back to a canonical action menu
147
+
148
+ The agent uses it sparingly—only when stuck or after parser failures.
149
+
150
+ ### `tried_actions()`
151
+ The anti-loop tool:
152
+ - returns actions already attempted in each room
153
+ - helps the agent choose *new* high-value actions instead of repeating `open mailbox` 10 times
154
+
155
+ ### `get_map()` and `graph()`
156
+ These expose exploration as:
157
+ - a human-readable map (for prompts)
158
+ - a structured JSON graph (for future logic/visualization)
159
+
160
+ Mapping gives the agent an explicit “where have I been?” memory that the LLM doesn’t have to hallucinate.
161
+
162
+ ---
163
+
164
+ ## Guardrail Tools That Make the System Feel "Serious"
165
+
166
+ ### Checkpoints (`checkpoint_save`, `checkpoint_restore`)
167
+ Checkpoints are a reliability hack with real impact:
168
+ - if the agent detects a loop or makes a catastrophic move, it can rollback
169
+ - we keep at least one “loop” checkpoint as a stable anchor
170
+ - we can also maintain a “best” checkpoint after scoring gains
171
+
172
+ This transforms the exploration strategy:
173
+ - you can take risks, because you can recover
174
+
175
+ ### `action_probe(action)` — action simulation without commitment
176
+ This is one of the more original parts of the server.
177
+
178
+ The idea:
179
+ - save a snapshot
180
+ - perform the action
181
+ - record deltas (score, moves, hash, location changes)
182
+ - restore the snapshot
183
+ - restore tracking metadata too (so probing doesn’t poison history/map)
184
+
185
+ It returns a compact JSON “what would happen if…?” report.
186
+
187
+ This enables a strong behavior: evaluating candidate actions via simulation and rollback, without committing a move (when snapshot/restore succeeds).
188
+
189
+ We keep it cheap (probe only a couple of actions) but it’s an excellent tie-breaker when stuck.
190
+
191
+ ---
192
+
193
+ # Part 2 — The Agent: ReAct, But Constrained and Safe
194
+
195
+ ## Strict ReAct as a Contract (Not a Style)
196
+
197
+ The agent uses a strict format:
198
+ - THOUGHT: one short sentence
199
+ - TOOL: one of the allowed tool names
200
+ - ARGS: valid JSON
201
+
202
+ That format is useful for stability:
203
+ - the agent becomes machine-parseable
204
+ - tool calls are consistent
205
+
206
+ ---
207
+
208
+ ## Important Policy: Command Grammar Discipline
209
+
210
+ Text adventure parsers punish creativity.
211
+
212
+ So the agent enforces a tight grammar:
213
+ - movement is single-word: `north`, `in`, `up`, …
214
+ - interaction is short verb+noun: `open mailbox`, `take lamp`, …
215
+ - exotic multiword commands are allowed **only if** they appear exactly in `valid_actions`
216
+
217
+ That last rule is a big deal:
218
+ - it prevents the LLM from inventing fancy commands
219
+ - it converts “language” into “API calls”
220
+ - it makes the agent much more robust across seeds
221
+
222
+ ---
223
+
224
+ ## The Agent’s Guardrails: How We Stop Thrashing
225
+
226
+ Here are the big guardrail categories (conceptually, not line-by-line):
227
+
228
+ ### 1) Tool validation
229
+ If the model requests an unknown tool:
230
+ - we don’t execute it
231
+ - we inject feedback listing allowed tools
232
+ - we force recovery behavior next
233
+
234
+ ### 2) Parser failure detection
235
+ If the observation looks like a parser error (“I don’t know the word…”, “sentence isn’t recognized”):
236
+ - we switch into recovery mode
237
+ - we fetch valid actions (once)
238
+ - we force a simpler action selection
239
+
240
+ ### 3) Anti-repeat behavior (local)
241
+ We track:
242
+ - the last action
243
+ - actions blocked in the current room
244
+ - actions tried in the current room
245
+
246
+ If the model repeats a no-progress action:
247
+ - we refuse it
248
+ - we force a new choice
249
+
250
+ ### 4) Loop detection (global)
251
+ The agent uses the server’s `state_hash`:
252
+ - if the same hash repeats several times, we’re looping
253
+
254
+ Then we can:
255
+ - restore a checkpoint
256
+ - re-orient with `look`
257
+ - switch strategy
258
+
259
+ ### 5) Movement bias (Zork-specific optimization)
260
+ When multiple movement options exist:
261
+ - “in / up / down” tend to unlock deeper progress
262
+ - cardinal directions tend to be broad exploration
263
+
264
+ So we bias toward `in/up/down` (especially after seeing them in valid actions).
265
+
266
+ It’s a small heuristic that often pays off.
267
+
268
+ ---
269
+
270
+ ## Two Specialized LLM Modules: Memory and Planning
271
+
272
+ This is where the project becomes more than a typical ReAct agent.
273
+
274
+ ### Specialized module #1: Memory Compression (Long-Term Memory)
275
+ Raw history is short-term memory. It’s verbose, expensive, and noisy.
276
+
277
+ So we maintain a **synthesized memory JSON**, updated periodically by an LLM whose only job is to compress experience into decision-useful facts:
278
+
279
+ - durable facts learned
280
+ - obstacles + what is needed
281
+ - what items/tools to search for
282
+ - open threads worth returning to
283
+ - important visited places
284
+
285
+ We keep it:
286
+ - short
287
+ - deduplicated
288
+ - structured
289
+ - bounded (so it doesn’t explode)
290
+
291
+ If that LLM call fails or returns invalid JSON:
292
+ - we simply skip the update
293
+ - the run continues safely
294
+
295
+ The goal is to make the agent stay coherent over long runs.
296
+
297
+ In addition to this long-term synthesized memory, the agent retrieves an authoritative short-term memory summary every 10 steps via the get_memory() tool, ensuring local consistency and correcting possible drift in recent reasoning.
298
+
299
+ ### Specialized module #2: Objective Planning (Goal Management)
300
+ Action selection is short-horizon.
301
+ But Zork requires long-horizon intent.
302
+
303
+ So we run a separate “planner” LLM that:
304
+ - updates objectives (explore, open, unlock, acquire key/lamp, return somewhere)
305
+ - proposes up to a few suggested next actions
306
+ - provides short evidence
307
+
308
+ Crucially:
309
+ - planner suggestions are **not auto-executed**
310
+ - they are injected into the prompt as guidance
311
+ - the main ReAct decision still chooses the next tool/action
312
+
313
+ This separation reduces goal drift:
314
+ - the agent behaves like it has a mental TODO list
315
+ - and doesn’t wander aimlessly as often
316
+
317
+ ---
318
+
319
+ ## Deterministic Overrides: Sometimes We Don’t Ask the LLM
320
+
321
+ Some policies are too important to leave to “model mood.”
322
+
323
+ Example: **treasure acquisition**
324
+ If we see obvious treasure nouns in visible objects:
325
+ - we immediately `take <item>`
326
+ - no debate, no planning, no cleverness
327
+
328
+ ---
329
+
330
+ ## Checkpoints as a Strategy, Not Just a Feature
331
+
332
+ The agent uses checkpoints like a game speedrunner would:
333
+ - keep a “loop” checkpoint as a stable anchor
334
+ - save a “best” checkpoint after scoring gains
335
+
336
+ That means:
337
+ - progress is protected
338
+ - exploration can be more aggressive
339
+ - loop recovery is fast
340
+
341
+ It’s a pragmatic way to make the system resilient under a move budget.
342
+
343
+ ---
344
+
345
+ # What You Get From This Approach
346
+
347
+ Compared to a vanilla “LLM + play_action” loop, this system is:
348
+
349
+ - **more reliable** (fewer parser deaths, fewer infinite loops)
350
+ - **more efficient** (less move waste, less repeated actions)
351
+ - **more scalable** (memory doesn’t balloon)
352
+ - **more coherent** (objectives keep the agent on track)
353
+ - **more intentional** (action_probe and valid_actions are used strategically)
354
+
355
+ ---
356
+
357
+ ## Final Takeaway
358
+
359
+ Text adventures punish the exact things LLMs love:
360
+ - improvisation in language
361
+ - repetition
362
+ - vague intent
363
+ - verbose context
364
+
365
+ So we respond with the opposite:
366
+ - strict grammar
367
+ - structured state
368
+ - explicit recovery
369
+ - bounded but long term memory
370
+ - deliberate planning
371
+
372
+ ---
373
+
374
+ # Evaluations
375
+
376
+ The evaluation has been made on 100 steps and 3 seeds, using lostpig as test game.
377
+ The agent showed improved stability, there are fewer loops and parser errors.The tools are used more strategically, especially `valid_actions` and `action_probe` which are called mostly when the agent is stuck. The agent also seems to be more intentional, with a better sense of direction and progress, likely thanks to the planning module and the memory compression that keeps track of important facts and objectives.
378
+
379
+ However, the score progression compared to a vanilla ReAct baseline is not as big as expected: mean of 2 points for our approach and 1 point for the vanilla one.
380
+
381
+ We can hypothesize that the agent is still not using the tools as effectively as it could, and that the planning module is not providing useful guidance.
382
+ We can also hypothesize that the evaluation budget (100 steps) is too low to see the benefits of the approach, which is designed to be more effective in longer runs where reliability and coherence matter more.
383
+
384
+ Here are the results of the evaluation:
385
+
386
+ Evaluation Results: Text Adventure Agent Submission
387
+ ==================================================
388
+ Game: lostpig
389
+ Trials: 3/3 successful
390
+ Max steps per trial: 100
391
+
392
+ Score Statistics:
393
+ Mean: 2.00
394
+ Std: 0.00
395
+ Min: 2
396
+ Max: 2
397
+
398
+ Exploration:
399
+ Mean moves: 65.7
400
+ Mean locations: 14.3
401
+
402
+ Per-Trial Scores: [2, 2, 2]
403
+
404
+ # Potential Improvements
405
+
406
+ - **Navigation tool** — a `go_to(location)` tool that uses the transition graph to find a sequence of moves to go from the current location to the target location (with a BFS algorithm for example) and apply them automatically instead of letting the LLM guessing the path. The agent could reduce move waste and improve reliability.
407
+
408
+
409
+