Skip to content

English Version →

本篇代碼示例:code/ 實戰練習:Project 02. 讓 agent 看懂項目、接住上次的工作

第四講. 把指令拆分到不同檔案裡

你開始認真對待 harness 了——好事。你建了個 AGENTS.md,把你能想到的所有規則、約束、歷史教訓都塞了進去。一個月後這個檔案膨脹到了 300 行,兩個月 450 行,三個月 600 行。然後你發現 agent 的表現反而變差了——改一個小 bug,agent 花大量上下文處理無關的部署指令;關鍵的安全約束埋在第 300 行,被直接忽略了;檔案裡有三條互相矛盾的代碼風格規則,agent 每次隨機選一條。

這就是"巨型指令檔案"陷阱。就像你往行李箱裡塞東西——覺得什麼都有用,什麼都往裡裝,結果拉鍊快崩了,想找換洗內衣得把整個箱子翻一遍。帶了一箱子東西,但真正用上的可能只有三分之一。

問題的根源:一個惡性循環

最常見的惡性循環是這樣的:agent 犯了個錯 → 你說"加條規則防止這個" → 加到 AGENTS.md → 暫時管用 → agent 又犯了另一個錯 → 再加一條 → 重複 → 檔案膨脹到不可控。

這不是你的錯。這是一個非常自然的反應——每次出問題就"加條規則"感覺很合理,就像每次出門忘帶東西就往包裡多塞一樣。但累積效應是災難性的。讓我們看看具體出了什麼問題。

上下文預算被吃掉了。 Agent 的上下文窗口是有限的。假設你的 agent 有 200K tokens 的窗口(Claude 的標準),一個膨脹的指令檔案可能佔掉 10-20K。看起來還有不少餘量?但一個複雜的任務可能需要讀幾十個源檔案、工具執行的輸出也占上下文、對話歷史也在累積。到真正需要理解代碼的時候,預算已經不夠了——就像你的行李箱塞滿了"萬一用得著"的東西,結果放不下真正需要帶的電腦了。

中間迷失。 "Lost in the Middle"這篇論文(Liu et al., 2023)清楚地證明了:LLM 對長文本中間部分的信息利用效率顯著低於兩端。你的 AGENTS.md 有 600 行,第 300 行寫的是"所有數據庫查詢必須用參數化查詢"——這是安全硬約束。但它被埋在中間,agent 幾乎一定會忽略它。就像你的行李箱裡那瓶防曬霜——明明在最底層,但你翻三遍也找不到,最後又去買了一瓶。

優先級衝突。 檔案裡混合了不可違反的硬約束("不得使用 eval()")、重要的設計指導("優先使用函數式風格")、和某個特定場景的歷史教訓("上週修了一個 WebSocket 內存洩漏,注意類似的模式")。這三條規則的重要性完全不同,但在檔案裡看起來一模一樣。Agent 沒有可靠的信號來區分——就像行李箱裡護照和充電線混在一起,看不出哪個更緊急。

維護衰減。 大檔案天生難維護。指令過時了沒人刪——因為刪除的後果不確定("也許別的地方依賴這條規則?"),但加新指令是無成本的。結果檔案只增不減,信噪比持續下降。這和軟件裡的技術債務積累一模一樣。

矛盾累積。 不同時期加的指令之間開始出現矛盾——一條說"用 TypeScript 嚴格模式",另一條說"某些遺留檔案允許用 any"。Agent 每次隨機選一條遵循。就像你媽說"穿厚點",你爸說"別穿太多",你站在門口不知道聽誰的。

核心概念

  • 指令膨脹:當指令檔案佔用了超過上下文窗口 10-15% 的時候,它就開始擠佔代碼閱讀和任務推理的預算。600 行的 AGENTS.md 可能佔用 10,000-20,000 tokens——對 128K 的窗口來說,這就吃掉了 8-15%。
  • 中間迷失效應:Liu 等人 2023 年的研究證明,LLM 對長文本中間部分的信息利用效率顯著低於兩端。埋在 600 行檔案中間第 300 行的關鍵約束,被有效忽略的概率非常高。
  • 指令信噪比(SNR):檔案中與當前任務相關的指令佔總指令的比例。做 bug 修復時被要求讀 50 行部署指令——SNR 很低。
  • 路由檔案:短小的入口檔案,核心功能是引導 agent 去找更詳細的文檔,而不是自己包含所有內容。50-200 行就夠了。
  • 漸進式披露:先給概要信息,需要的時候再給詳細信息。好的 harness 設計和好的 UI 設計一樣,不把所有選項一次性砸到用戶臉上。
  • 優先級模糊度:當所有指令以相同格式和位置呈現時,agent 分不清哪些是不可違反的硬約束,哪些是建議性的軟約束。

指令檔案架構

拆分思路

核心原則:常用信息放手邊,偶爾用的收起來,用不上的別帶。

入口檔案 AGENTS.md 控制在 50-200 行,只放最常用的東西——項目概覽(一兩句話說清楚這是什麼)、首次運行命令(make setup && make test)、全局硬約束(不超過 15 條不可違反的規則)、指向專題文檔的鏈接(一行描述 + 適用條件)。

markdown
# AGENTS.md

## 專案概覽
Python 3.11 FastAPI 後端,PostgreSQL 15 資料庫。

## 快速開始
- 安装:`make setup`
- 測試:`make test`
- 完整驗證:`make check`

## 硬约束
- 所有 API 必須走 OAuth 2.0 認證
- 所有資料庫查询必須用 SQLAlchemy 2.0 语法
- 所有 PR 必須通過 pytest + mypy --strict + ruff check

## 专题文檔
- [API 設計規範](docs/api-patterns.md) — 添加新端点時必讀
- [資料庫操作约束](docs/database-rules.md) — 涉及資料庫修改時必讀
- [測試標準](docs/testing-standards.md) — 编寫測試時参考

每個專題文檔 50-150 行,按主題放在 docs/ 目錄下或對應模塊目錄旁。Agent 只在需要時才去讀。就像行李箱裡的收納袋——內衣一個袋,洗漱一個袋,充電器一個袋。找東西不用翻整個箱子。

還有些信息直接放在代碼裡更合適——類型定義、接口註釋、配置檔案裡的說明。Agent 讀代碼的時候自然能看到,不用再在指令裡重複一遍。

每條指令都應該標明來源("為什麼加這條規則?")、適用條件("這條規則在什麼時候需要?")、過期條件("什麼情況下可以刪掉這條規則?")。定期審計,刪掉過時的、冗餘的、矛盾的條目。像管理代碼依賴一樣管理你的指令——用不上的依賴就該刪掉,不然它們只會拖慢系統。

如果某條指令必須在入口檔案裡,放頂部或底部——不要放中間。"中間迷失"效應告訴我們,LLM 對長文本中間部分的信息利用效率顯著低於兩端。但更好的做法是把指令放到專題文檔裡,讓 agent 按需加載。

OpenAI 和 Anthropic 都隱性支持拆分的做法。OpenAI 說入口檔案應"短小且以路由為導向",Anthropic 說長運行 agent 的控制信息應"簡潔且高優先級"。兩家都在說同一件事:別把什麼都塞進一個檔案裡。行李箱得整理,不能只靠蠻力塞。

實際案例

一個 SaaS 團隊的 AGENTS.md 從最初的 50 行膨脹到 600 行。內容混合了技術棧版本、編碼規範、歷史 bug 修復筆記、API 使用說明、部署流程、和團隊成員的個人偏好——整個行李箱塞得滿滿當當,拉鍊都快崩了。

Agent 表現開始明顯下降:簡單 bug 修復任務中 agent 花大量上下文處理無關的部署指令;安全約束"所有數據庫查詢必須用參數化查詢"埋在第 300 行,經常被忽略;三條矛盾的代碼風格規則導致 agent 隨機選擇。

團隊執行了"行李箱整理":

  1. AGENTS.md 裁剪到 80 行:只保留項目概覽、運行命令、15 條全局硬約束
  2. 創建專題文檔:docs/api-patterns.md(120 行)、docs/database-rules.md(60 行)、docs/testing-standards.md(80 行)
  3. 路由檔案添加指向專題文檔的鏈接
  4. 歷史筆記要麼轉成測試用例,要麼刪除

重構後:同一任務集的成功率從 45% 提升到 72%。安全約束遵循率從 60% 提升到 95%——因為從檔案中間移到了路由檔案頂部,不再被"中間迷失"了。

關鍵要點

  • "加條規則"是短期的止痛藥,長期的毒藥。每次加規則前想想:這條規則放專題文檔是不是更合適?——別什麼都往行李箱裡塞。
  • 入口檔案是路由器,不是百科全書。50-200 行,只放概覽、硬約束、和鏈接。
  • 利用"中間迷失"效應:重要信息放檔案頂部或底部,不重要的移到專題文檔。
  • 像管理技術債一樣管理指令膨脹。定期審計,每條指令要有來源、適用條件、和過期條件。
  • 拆分之後信噪比提升,agent 把更多上下文預算花在實際任務上,而不是處理無關指令。

延伸閱讀

練習

  1. 信噪比審計:拿你當前的入口指令檔案,列出所有指令條目。選 5 個不同的常見任務類型,標註每條指令是否跟該任務相關。計算每個任務類型的 SNR。那些對大多數任務都是噪聲的指令,移到專題文檔裡。

  2. 漸進式披露重構:如果你有一個超過 300 行的指令檔案,把它拆成:(a) 不超過 100 行的路由檔案,(b) 3-5 個專題文檔。重構前後各跑同一組任務(至少 5 個),對比成功率。

  3. 中間迷失驗證:在一個長指令檔案裡,把一條關鍵約束分別放在頂部、中間、底部各跑一組任務(每組至少 5 次),看遵循率有沒有差別。你可能會驚訝於位置效應有多大。