Session Handoffs 如何在 AI 編碼會話之間維持上下文
AI 編碼助理在工作階段結束時會忘記所有事情。一份簡短的交接筆記會記載您已完成的工作、未完成的工作以及接下來的步驟。
AI 編碼代理程式的上下文視窗有限,而實際任務很少能完全容納其中。當前的對話階段會滿載,或者您會結束一天的工作,或者您會為了維持模型的速度而開啟一個新的對話。當這種情況發生時,代理程式所知關於此工作的一切都會消失:您變更了什麼、為什麼變更、還有什麼問題未解決,以及您接下來打算做什麼。解決方法既老舊又乏味。當人類交接班時,他們會留下一張便條。一個 AI 對話階段也應該留下同樣的便條,而下一個對話階段應該先閱讀它。
為何 AI 會話會失去脈絡
上下文視窗是代理人的整個工作記憶體。它包含了你開啟的檔案、你反覆思辨過的決策、你已經排除的死胡同,以及你腦中從未寫下的半成品計畫。所有這些都只存在於一次對話中,別無他處。
那份記憶是揮發性的。基於三個常見原因,它無法在會話結束後存留。在執行長任務時,視窗會被填滿,最早的訊息會脫離範圍。你關上筆記型電腦,隔天回來面對一個全新的對話。或者你刻意重新開始,因為臃腫的上下文會讓模型變慢,也更容易失焦。在每種情況下,下一個會話都是從空白開始,而你得付出同樣的代價:重新解釋程式碼庫、重申目標,並重新推導你一小時前就已做出的決定。對於一個橫跨三、四個會話的任務來說,大部分的重新解釋純粹是浪費。
問題不在於代理人健忘。問題在於它的記憶從未被寫入任何持久的地方。你在這個會話中所說的任何話都沒有存到磁碟上。解決了這個問題,會話的邊界就不再是懸崖了。
將記憶體外部化至磁碟
整個概念只有一個動作:將原本存在於內容脈絡視窗中的狀態,放入一個檔案中。交接筆記就是那個檔案。它是一個小而持久的紀錄,記錄著工作進度,在一次會話結束時寫入,以便下一次會話開始時可以載入。
這反映了人們既有的工作方式。輪班工作者會留下日誌。要去度假的開發人員會撰寫交接文件,以便團隊成員可以接手處理工單。沒有人會期望下一個人能從一個差異比對 (diff) 中,重建三天的推理過程。這份筆記承載了推理過程,跨越了這個鴻溝。一次 AI 會話出於同樣的原因也需要同樣的東西,只不過這個鴻溝不是存在於兩個人之間,而是存在於與同一個工具的兩次對話之間。
一旦狀態存在於檔案中,連續性就不再依賴記憶體。無論是今天還是下週的任何一次會話,都可以讀取該筆記,並以完整的全貌繼續進行。內容脈絡視窗就變得可以拋棄,因為重要的部分已經被儲存下來了。
交接文件實質內容
一份好的交接文件只回答四個問題,別無其他。它是一份狀態報告,不是日記。篇幅是這裡的敵人,因為無論是人或模型在掃描相關資訊時,都不會去讀一大堆文字。
| 章節 | 包含內容 | 篇幅限制 |
|---|---|---|
| 已完成 | 本次工作階段完成的事項,每項一行 | 幾個項目符號 |
| 關鍵決策 | 形塑程式碼的選擇及其原因 | 會讓人感到意外的那些決策 |
| 待辦與後續 | 未完成的事項,以及下個工作階段應避免的陷阱 | 最重要的事情放第一位 |
| 參考資料 | 去哪裡找:檔案、commit、票證、日誌 | 確切的路徑,而非模糊的提示 |
第一部分是成果摘要,好讓下個工作階段知道哪些範圍已經涵蓋。第二部分是重新建構起來最耗費成本的部分:決策背後的理由、你拒絕的選項及其原因、以及從程式碼中看不出來的限制。第三部分是連續性實際發生的所在,因為它指出了下一步以及附近的隱藏地雷。第四部分將筆記變成一份索引,這樣下個工作階段只需閱讀三個檔案,而不是三十個。
保持內容精煉。一份含糊其辭又重複的交接文件,是沒有人會讀完的,這就違背了初衷。
交接範本
固定的格式讓筆記可以快速撰寫與快速掃描。每次都使用相同的標題,意味著下一個工作階段能確切知道要去哪裡尋找下一步。這裡有一個範本,在長時間的任務中都經得起考驗:
# Handoff: <task name> (<date>)
## Done this session
- Wired the payment webhook to the new queue
- Added retry with backoff, capped at 5 attempts
## Key decisions
- Idempotency key is the provider event id, not our order id.
The provider can resend an event, and order id is not unique per event.
- Chose an at-least-once queue over exactly-once. Handlers are already
idempotent, so duplicates are safe and the setup is far simpler.
## Open / next
- NEXT: the dead-letter path is not wired yet. Failed events vanish
after 5 retries. Start here.
- Trap: the staging webhook secret differs from prod. Do not copy the
prod value into staging config.
## Pointers
- Handler: internal/payments/webhook.go
- Queue setup: internal/queue/consumer.go:42
- Failing test: TestWebhookRetry (currently skipped)
- Related commit: a1b9f30
有兩個細節使其發揮作用。下一步驟被標示出來,並位於其區塊的頂部,因此接續的工作階段無需尋找即可對其採取行動。而且,陷阱會被明確標示為陷阱,而不是埋藏在長篇大論中,因為交接的全部價值就在於警告下一個工作階段,使其遠離無法預見的錯誤。
自動化交接
每次都用手寫筆記會產生阻力,而阻力意味著事情不會發生。更好的版本是自動化。注意會話即將結束的信號,並在那一刻產生或更新交接內容。
這些信號很容易捕捉。一個人輸入像是「我們今天就到這裡」、「今天就到此為止」或「開始一個新會話」之類的內容。工具可以偵測到 context window 即將達到其極限。這些任何一項都可以觸發筆記的產生。內容會根據工作的狀態進行調整。如果任務已完成,交接內容會很簡短,只需幾行字說明任務已完成及其成果所在之處。如果工作正在進行中,筆記會側重於未完成和下一步的部分,因為那是下一個會話最需要的資訊。
這樣做的好處是,無需自律,筆記也能保持在最新狀態。你不需要記得去寫它,也不需要記得去更新它。在你發出完成信號的那一刻,工作的狀態就已經儲存在磁碟上,而下一個會話也已經有內容可以讀取了。
何時不值得交接
交接是一種額外開銷,而只有在能換回某些價值時,這種開銷才值得付出。對於一個在單一工作階段內開始並完成的簡短、一次性任務,交接什麼也換不回來。重新命名一個變數、修正一個錯字、編寫一個小函式:這些都沒有需要彌補的間斷,所以寫筆記是純粹的成本。不要這麼做。
交接的價值體現在兩種工作型態上。第一種是任務規模太大,無法在一個工作階段內完成,此時筆記能將上下文帶過你自己的工作階段邊界。第二種是在數人或數個代理者之間傳遞的工作,此時筆記就是他們之間的介面。在這兩種情況下,與從頭重建一個下午的上下文所需付出的成本相比,寫下幾個精簡條列項目的成本是微不足道的。
老實說的規則是:當工作會超過一個工作階段時,就進行交接。如果不會,就跳過它。往成本低的方向猜錯,也就是寫了你不需要的筆記,只會花費你一分鐘。往成本高的方向猜錯,也就是在一個需要多個工作階段的任務上省略了筆記,則會讓下一個工作階段耗費一小時。
確保交接的真實性
交接有一種比沒有交接更糟的失敗模式:變得過時。如果筆記上說 dead-letter path 尚未連接,但你在上一個工作階段中悄悄地將它連接起來卻沒有更新筆記,那麼下一個工作階段讀到的就是謊言。它會基於錯誤的認知進行工作,並浪費時間去發現地圖與實際情況不符。錯誤的交接比遺失的交接更危險,因為遺失的筆記會讓你在明知自己毫無頭緒的情況下重建情境,而過時的筆記則會讓你相信一個已經錯誤的情境。
這就是為什麼自動化的重要性不僅止於方便。在每個工作階段結束時更新的筆記,是一份能追蹤現實情況的筆記,因為它每次都是根據當前狀態重寫,而不是經由手動編輯而慢慢偏離。保留一個標準的交接檔案並覆寫它。不要累積一堆標有日期的筆記,讓讀者必須去猜測哪一份才是最新的。
這個機制很古老,而這正是重點。自從有輪班制度以來,人們就一直用日誌來交班。唯一的新鮮事是,下一班的同事是同一個工具的另一個實例,它對上一班沒有任何記憶,除了你留下的筆記之外,沒有其他方法可以獲取這些資訊。寫下筆記。保持簡潔。保持真實。下一個工作階段,無論是由誰或什麼來執行,都將能從你離開的地方精確地接手。