AI-LECTURER 速報課|2026-10-12|約 25 分鐘

五千億 token 換來什麼?學會像專案經理一樣指揮一群 AI 代理

📍 真實場景
阿凱,32 歲,接案的後端工程師,手上有一份沒人敢碰的十年舊 C++ 專案

他訂了 AI 程式助手,想讓它一口氣把整個專案的幾十個檔案改成可以編譯的現代寫法,於是丟了一句「全部幫我改好」就去睡覺

😖 卡住的地方:隔天醒來,助手改到一半就忘了前面做過什麼,有些檔案被重複改、有些根本沒碰,編譯一跑滿螢幕紅字,他分不清誰改了什麼、哪裡壞的
💡 這課拆解一個為期三個月、讓多個 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 工單不會。每個檔案一張工單,進度就變成可查詢、可接手的公開紀錄,人和代理都看得到,換一個代理上場也能接續。
真人也能隨時加入協作因為溝通走 Discord 頻道,其他參與者不需要取得機器權限,只要在頻道裡說話,代理就能讀到。這讓協作者不用都坐在同一台電腦前。

⚙️ 它是怎麼運作的

1
拆工單把整個大目標切成「一個翻譯單元(.cpp 檔)一張工單」,再用標籤分類與排優先順序,讓每個代理領到的任務大小剛好。
▼
2
工作代理動手3 個工作代理各自領工單,透過 ida-mcp 查看反編譯結果,寫出 C++ 並提交。
▼
3
共用頻道溝通所有代理都在同一個 Discord 頻道發話與讀取訊息,彼此同步狀況;真人也能在裡面下指令或提醒。
▼
4
CI 自動把關提交後由 CI 編譯檢查;一旦失敗,GitHub webhook 立刻把失敗訊息貼進頻道,代理不用輪詢就知道出事了。
▼
5
審查代理協調另有一個審查代理被動地協調與把關,不親自大量動手,而是確保進度與品質的整體走向。
單一代理硬做 vs. 這個專案的多代理編排
面向一個代理一次做完多代理+工單+頻道
進度記錄只存在對話裡,容易忘存在 GitHub 工單,可查詢可接手
出錯怎麼知道要自己再叫它跑一次CI 失敗經 webhook 主動通知頻道
真人介入要守在同一段對話前在 Discord 頻道留言即可
品質把關自己寫自己檢查有獨立審查代理與 CI

🔍 程式碼漫遊(點有 ● 的行看白話講解)

下面是下載專案裡 orchestra.py 的核心片段,模擬「工單板+共用頻道+審查員+CI 失敗通知」這條迷你流水線。真實專案用的是 GitHub 與 Discord,這裡用本機檔案代替,方便你不用任何帳號就能跑。

●def make_board():
💬 建立工單板:一個 .cpp 檔對應一張工單,和文章的切法一致。
● return [{'unit': u, 'status': 'open', 'owner': None} for u in units]
💬 每張工單有三個欄位:哪個檔案、目前狀態(open/claimed/done)、誰領走了。
●def worker_step(board, name):
💬 工作代理的一步:找一張還沒人領的工單。
● if issue['status'] == 'open':
💬 只領 open 的,這樣不會兩個代理同時改同一個檔案。
●def ci_passes():
💬 模擬 CI。真實專案是真的編譯;這裡用亂數讓 30% 失敗,好讓你看到退回流程。
● issue['status'] = 'open'
💬 CI 失敗就把工單退回 open,讓任何代理可以重新領,進度不會卡死在某個代理身上。
● post('CI-webhook', issue['unit'] + ' 編譯失敗,退回工單')
💬 失敗時主動往共用頻道貼訊息,對應文章裡 GitHub webhook 把 CI 失敗貼進頻道的設計。
● with CHANNEL.open('a', encoding='utf-8') as f:
💬 頻道用附加寫入(a):只增不改,多方寫入才不會互相覆蓋對方的訊息。

🛠️ 動手做:迷你代理工廠:用 40 行 Python 看懂「工單+頻道+審查員」

這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。

📄 orchestra.py ⬇ 下載
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()
  1. 在 PowerShell 建立並進入資料夾:mkdir D:\agent-demo; cd D:\agent-demo
  2. 把 orchestra.py 存進這個資料夾(請存成 UTF-8 編碼)
  3. 讓主控台正確顯示繁中:chcp 65001; $env:PYTHONUTF8 = '1'
  4. 執行:py orchestra.py(若沒有 py 指令,改用 python orchestra.py)
  5. 打開 channel.log,觀察哪些工單被 CI 退回、又被誰重新領走
  6. 進階練習:把 workers 改成只有 1 個,或把 ci_passes 的 0.3 改成 0.6,比較「輪數」變化,思考為什麼失敗率高時更需要自動通知與退回機制

🧠 工程思維透鏡(資深工程師看到的是什麼)

🔭 為什麼要「一個 .cpp 檔一張工單」,而不是「一張大工單寫整個遊戲」?
這是在任務粒度上取捨:太大,代理的上下文裝不下、失敗了也不知道哪裡壞;太小,管理成本爆炸。以檔案為單位剛好有天然邊界,能獨立編譯、獨立驗收,失敗只需重做一張。工單同時成為進度的單一事實來源,人與代理看到的是同一份狀態。
🔭 為什麼用共用頻道+CI webhook,而不是讓一個「總指揮代理」逐一派工?
總指揮會成為單點瓶頸,也會吃掉大量上下文;共用頻道讓資訊廣播給所有人,每個代理自己決定要不要反應。文中的審查代理是「被動協調」,也印證了這種低耦合設計:中心不下命令,而是維持可見度。代價是訊息可能變多、需要約定好格式,這就是設計時要權衡的地方。
🔭 作者說外殼(harness)選哪個影響不大,卻花大力氣在基礎設施上,這代表什麼?
代表邊際效益的位置變了:當模型與外殼都夠用時,真正拉開差距的是「任務如何被切分、狀態存在哪裡、錯誤如何被發現」。這是典型的工程思維:不追逐單一元件的極致,而是讓整體系統在長時間運行下穩定且可恢復。

📝 隨堂考(點選答案,立即回饋)

Q1. 文章中,代理們用什麼來追蹤每個翻譯單元(.cpp 檔)的進度?
✅ 文中明確說明:用 GitHub CLI 管理 GitHub issues,一個翻譯單元一張工單,並用標籤分組與排優先順序。
Q2. CI 失敗時,代理是怎麼得知的?
✅ 文中提到有一個 GitHub webhook 把 CI 失敗貼進共用頻道,代理就會被通知有東西壞了。
Q3. 作者對「換不同的代理外殼(harness)」這件事的看法是?
✅ 文中寫道他們也試過其他外殼,但選擇幾乎沒差,所以沿用預設值。
Q4. 在迷你範例中,為什麼 CI 失敗後要把工單狀態改回 open 並清空 owner?
✅ 退回 open 並清空擁有者,工單就重新回到可領取池,系統不會因為某個代理失敗而停擺。

🃏 翻牌記憶卡(先想答案,再點開對答)

這個專案用什麼切分任務?點我翻面
一個翻譯單元(.cpp 檔)一張 GitHub 工單,再用標籤分組與排優先順序。
代理之間怎麼溝通?點我翻面
全部在同一個 Discord 頻道發話與讀取;真人也能直接加入,不需要機器權限。
CI 失敗如何通知代理?點我翻面
GitHub webhook 把 CI 失敗貼進共用頻道。
反編譯用的工具是什麼?點我翻面
Hex-Rays 官方的 ida-mcp:無介面、穩定,支援專案所需的全部功能。
初期代理怎麼分工?點我翻面
4 個代理:3 個工作代理負責反編譯與提交,1 個審查代理被動協調。

✅ 離開前自測(全勾=這課真的學會了)

0%