他訂了 AI 程式助手,想讓它一口氣把整個專案的幾十個檔案改成可以編譯的現代寫法,於是丟了一句「全部幫我改好」就去睡覺
這篇文章的作者花了三個月,讓 AI 代理替一款老牌第一人稱射擊遊戲做 反編譯把已經編譯成機器碼的程式,反推回人類看得懂的原始碼,目標是得到「可以編譯、行為準確、功能完整」的 C++ 原始碼。但作者特別強調:這篇不是在講遊戲,也不太是在講反編譯,而是在講怎麼 編排替多個 AI 分配工作、決定誰做什麼、誰檢查誰,像導演調度演員一樣 長時間運作的自主代理。
他們的做法不是找一個超強的 AI 單打獨鬥,而是搭一整套工作環境:每個 .cpp 檔案對應一張 GitHub 工單(issue),用標籤分組與排序;所有代理都待在同一個 Discord 頻道裡,可以互相發話,真人也能直接插話;當 CI持續整合,每次有人提交程式碼,系統就自動編譯、測試並回報結果 失敗時,一個 webhook網站在事件發生時,自動把訊息推送到另一個地方的機制 會把失敗通知丟進這個頻道。
人力配置上,最初階段是 4 個代理:3 個工作代理負責反編譯與提交,1 個審查代理負責被動協調與把關。反編譯工具則使用 Hex-Rays 官方的 ida-mcp,它是無介面(headless)、運作穩定,並且支援專案所需的全部功能。這裡的 MCP一種讓 AI 能用統一方式呼叫外部工具的通訊規格 就是代理和反編譯軟體之間的橋。
作者還提到:他們同時使用 Claude 與 Codex 兩家的訂閱,模型也輪流換用;至於代理的外殼工具(harness包住模型、替它提供檔案存取、指令執行等能力的外圍程式)換來換去影響不大,所以直接用預設值。重點因此落在「環境、基礎設施與流程」,而不是挑哪個外殼。
| 面向 | 一個代理一次做完 | 多代理+工單+頻道 |
|---|---|---|
| 進度記錄 | 只存在對話裡,容易忘 | 存在 GitHub 工單,可查詢可接手 |
| 出錯怎麼知道 | 要自己再叫它跑一次 | CI 失敗經 webhook 主動通知頻道 |
| 真人介入 | 要守在同一段對話前 | 在 Discord 頻道留言即可 |
| 品質把關 | 自己寫自己檢查 | 有獨立審查代理與 CI |
下面是下載專案裡 orchestra.py 的核心片段,模擬「工單板+共用頻道+審查員+CI 失敗通知」這條迷你流水線。真實專案用的是 GitHub 與 Discord,這裡用本機檔案代替,方便你不用任何帳號就能跑。
def make_board(): return [{'unit': u, 'status': 'open', 'owner': None} for u in units]def worker_step(board, name): if issue['status'] == 'open':def ci_passes(): issue['status'] = 'open' post('CI-webhook', issue['unit'] + ' 編譯失敗,退回工單') with CHANNEL.open('a', encoding='utf-8') as f:這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。
import json
import random
from pathlib import Path
BOARD = Path('board.json')
CHANNEL = Path('channel.log')
def post(who, msg):
# 只增不改:所有代理共用同一個頻道,附加寫入才不會互相覆蓋
line = '[' + who + '] ' + msg
print(line)
with CHANNEL.open('a', encoding='utf-8') as f:
print(line, file=f)
def make_board():
# 一個 .cpp 檔 = 一張工單,和文章的切法一致
units = ['player.cpp', 'weapon.cpp', 'net.cpp', 'render.cpp', 'sound.cpp', 'menu.cpp']
return [{'unit': u, 'status': 'open', 'owner': None} for u in units]
def worker_step(board, name):
for issue in board:
if issue['status'] == 'open':
issue['status'] = 'claimed'
issue['owner'] = name
post(name, '認領並提交 ' + issue['unit'])
return
def ci_passes():
# 真實專案是編譯與測試;這裡用亂數模擬約 30% 失敗
return random.random() > 0.3
def reviewer_step(board):
for issue in board:
if issue['status'] != 'claimed':
continue
if ci_passes():
issue['status'] = 'done'
post('reviewer', issue['unit'] + ' 審查通過,關閉工單')
else:
issue['status'] = 'open'
post('CI-webhook', issue['unit'] + ' 編譯失敗,退回工單(原作者 ' + issue['owner'] + ')')
issue['owner'] = None
def main():
random.seed(7)
CHANNEL.write_text('', encoding='utf-8')
board = make_board()
workers = ['worker-1', 'worker-2', 'worker-3']
rounds = 0
while any(i['status'] != 'done' for i in board) and rounds < 20:
rounds += 1
post('system', '=== 第 ' + str(rounds) + ' 輪 ===')
for w in workers:
worker_step(board, w)
reviewer_step(board)
BOARD.write_text(json.dumps(board, ensure_ascii=False, indent=2), encoding='utf-8')
done = sum(1 for i in board if i['status'] == 'done')
post('system', '結束:' + str(done) + '/' + str(len(board)) + ' 張工單完成,共 ' + str(rounds) + ' 輪')
if __name__ == '__main__':
main()