# Agent 自主迭代

> 让 Coding Agent 自己跑局、读结果、改代码、再跑：它需要一条能无人值守的命令、一份结构化的对局报告和一个明确的目标。

来源: https://war3ai.com/docs/agent-loop/

[用大模型写一个 Bot](https://war3ai.com/docs/ai-bot/) 里，「跑一局 → 看现象 → 告诉模型」这一步由你来做。能执行命令的 Coding Agent（Claude Code、Codex、Cursor 的 Agent 模式等）可以把这一步也接过去，形成闭环：

```text
  改代码 ──► 跑一局（无人值守）──► 读对局报告 ──► 找出最影响结果的一处 ──┐
    ▲                                                                   │
    └───────────────────────────────────────────────────────────────────┘
```

要让这个闭环真的收敛，Agent 需要三样东西。

## 1. 一条无人值守的命令

```bash
python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair
```

- `--minutes` 让这一局一定会结束（墙钟分钟），Agent 不会卡在一局里；
- `--speed 200` 用 2 倍速省时间 —— 但 Bot 里**按游戏时钟等待**（`g.clock()`），别按墙钟 `sleep`；
- `--fair` 让它从第一天起就按擂台规则写，只看得见视野里的东西；
- 运行结束时终端会打印结束原因，例如 `我方没有单位了`、`到时间了`；Bot 自己 `print` 的内容也在终端里。

> **注意**
>
> 窗口最小化时游戏模拟是停着的。让 Agent 用默认的窗口模式起游戏，并且别和你正在用的实例撞号（`--inst`）。

## 2. 一份结构化的对局报告

终端输出是给人看的。给 Agent 看的应该是一份 JSON：发生了什么、什么没做成、为什么。SDK 已经把原料都给了你 —— 回执带原因码，事件流带生产完成和伤亡。把它们收集起来即可：

```python title="recorder.py"
import collections, json, time
from openwar3 import Bot

class Recorder(Bot):
    """给 Bot 加一份对局报告。继承它，再在自己的 on_start / on_event 里调 super()。"""

    def on_start(self, g):
        self.rejects = collections.Counter()   # "train hfoo: rejected（人口不够）" -> 次数
        self.timeline = []                     # [游戏秒, 类别, 四字码]：训练 / 研究 / 建造 / 升级完成
        self.lost = collections.Counter()      # 我方死了什么
        self.killed = collections.Counter()    # 我方打死了什么

    def check(self, r, what):
        """包住命令记录被拒原因：self.check(g.train(b, "hfoo"), "train hfoo")"""
        if r is not None and not r:
            self.rejects[f"{what}: {r.reason}"] += 1
        return r

    def on_event(self, g, ev):
        me = g.me()
        if ev.kind == "production.done" and ev.owner == me:
            self.timeline.append([round(ev.clock), ev.done_kind, ev.done_code])
        elif ev.kind == "unit.died":
            (self.lost if ev.owner == me else self.killed)[ev.type] += 1

    def on_end(self, g, reason):
        report = {"reason": reason, "timeline": self.timeline, "lost": self.lost,
                  "killed": self.killed, "rejects": self.rejects.most_common(10)}
        try:                                   # 游戏可能已经退出，读不到就算了
            report |= {"clock": g.clock(), "resources": g.resources(),
                       "army": len(g.my_army()), "workers": len(g.my_workers())}
        except Exception:
            pass
        with open(f"run_{int(time.time())}.json", "w", encoding="utf-8") as f:
            json.dump(report, f, ensure_ascii=False, indent=1)
```

这份报告能回答的问题：

| 信号 | 从哪来 | 能看出什么 |
|---|---|---|
| 被拒最多的原因 | 回执 `reason` / `verdict` | 人口一直卡（3）、钱不够却一直下令（8 / 9）、打迷雾里的目标（1001）、英雄死了还在训练（221） |
| 生产时间线 | `production.done` 事件（带用了多少游戏秒） | 第几秒出第一个英雄、第几秒升本、兵营有没有一直在出兵；可以和职业选手的开局对比 |
| 双方伤亡 | `unit.died` 事件 | 是不是一直在送兵、英雄死了几次、打野有没有赚 |
| 结束原因 | `on_end(g, reason)` | `我方没有单位了` = 输了；`到时间了` = 还没分出胜负 |
| 最终兵力和资源 | `on_end` 时读一次快照 | 钱攒着没花 = 生产跟不上；工人太少 = 经济没起来 |

> **说明**
>
> 程序化判定胜负是 [对战平台](https://war3ai.com/arena/) 的地基实验之一，还在路线图上。现在可以用「我方没有单位了」判输，用「看得见的敌方建筑全灭」近似判赢。

## 3. 一个明确的目标和几条约束

把下面这段交给 Agent，按你的目标改：

```text
目标：让 brains/my_bot.py 在 Echo Isles 上稳定打赢「简单」难度的电脑（人族对随机种族）。

每一轮：
1. 运行 python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair
2. 读终端输出和最新的 run_*.json：结束原因、生产时间线、被拒最多的原因、双方伤亡
3. 找出最影响结果的「一个」问题，只改这一处；在代码注释里写下改动理由和依据的数据
4. 回到第 1 步。连续 3 局没有进步就停下，把报告和你的判断告诉我

约束：
- 只能用 docs/api.json 里的方法，不要编造接口
- 不要每拍给同一个单位重复下同一条命令；只给闲着的单位下令
- 保持 --fair（只用视野里看得见的敌人）
- 改代码前先跑 python tools/run_tests.py，确认没有把示例改坏
```

## 让闭环更快收敛的几个习惯

- **一次只改一处。** 同时改三处，赢了不知道是哪处起作用，输了也不知道是哪处搞坏的。
- **对比要打够局数。** 同一局面随机性很大；两局只能看出很大的差距。判断「是否进步」至少看几局的趋势。
- **先修「被拒」再调策略。** 回执里被拒最多的原因，往往就是 Bot 最大的 bug。
- **把判断写进注释。** 下一轮的 Agent（或者下一次对话）能从注释里知道为什么这么写，不会把修好的地方改回去。
- **离线测试兜底。** 给关键逻辑写不需要开游戏的单元测试（示例 Bot 的测试在 `brains/examples/tests/`），Agent 每次改完先跑一遍。
