系列說明 >> 本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。
我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。
本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。
Change Ref: 主線 PR #43-allow agent handoff while human monitors console;後續收斂 PR #65-區分 Human 在場與正在操作、PR #86-多 Agent 重疊時仍能正確歸還控制權。
Issue: 工程師維持 minicom Console 時,session self-test 會回報 HUMAN_INTERACTIVE_ACTIVE;即使 UART Session 是 READY,Agent Controller 仍會要求 Human 等待或離線。
Root Cause: Self-test 只要看到 Human 持有操作權,就直接判定 Agent 不能介入;後續又發現「Console 還連著」與「Human 正在操作」也不能混為一談。
Solution: PR #43 先讓 Self-test 可以短暫接手再歸還;PR #65 再依 Human 是否真的在輸入決定誰先讓路;PR #86 補上多 Agent 重疊時的交接保護。
Evidence: PR #43 以 Fake Target 驗證預設與 Strict 兩條路徑;PR #65 補上 Human Active/Idle 的 co-work tests;PR #86 驗證多層交接後仍能把控制權還給 Human。這些是受控 Host-side Evidence,不等同所有真機 UART 情境;PR #43 的完整 Test Suite 仍有一項可在乾淨基線重現的既有失敗。
處理好 WAL 記錄與測試初始化後,我把 minicom 留著監看,再接上 Reboot Test Flow,問題馬上就來了。
為了觀察 DUT Reboot 前後的 UART 訊息,minicom 當然不能關;另一邊的 Agent Controller 則會先執行 Self-test,確認 COM0、COM1 能不能接手操作。
兩條 Session 明明都是:
READY
Self-test 卻回:
{
"classification": "HUMAN_INTERACTIVE_ACTIVE",
"recommended_action": "wait_or_detach_console"
}
翻成白話就是:
有人在 UART 前面。請等他走,或先把他的 Console 拔掉。
問題是,我留下 minicom,就是為了看 Agent 接下來怎麼 Reboot、怎麼 Recovery,以及板子到底吐了什麼東西。
現在為了讓 Agent 開始工作,第一步竟然是先把負責觀察 Agent 的人趕走。
我是要 Human-in-the-loop,不是 Human-quit-the-loop 耶~
乞丐趕廟公喔?
工程師 Attach Console 後,不只會看到 UART Output。為了必要時能直接輸入 Command,serialwrap 也會把互動控制權交給這個 Human Console,形成一個有 Owner、有生命週期的 Interactive Lease。
問題可以想成一條 Slider:
Human Control ◀─────────────── 綠色區 ───────────────▶ Agent Autonomy
往 Human 端走過頭,只要人還掛著 minicom,Agent 就永遠不能進來。
往 Agent 端走太近,Agent 一開始跑工作,Human 就連必要的介入機會都沒有,只能等自動化自己決定什麼時候結束。
真正可用的位置不是某個固定刻度,而是中間那段可以互相讓路的綠色區:
Human 正在操作:
Human Control ◀────────●──────────────────────────▶ Agent
Agent 先等一下
Agent 正在跑工作:
Human Control ◀────────────────────────●──────────▶ Agent
Human 保留監看,輸入先延後
Human 真的在用 UART 時,控制權微微往左,Agent 不要硬搶。
Agent 已經開始執行 Command 或持續跑 Test Flow 時,控制權微微往右,Human 繼續看 Log,但不要在 Command 中間插入新的 Byte。
這才是我想找的甜蜜點,就是要這麼絲滑。
我先說,我是偏右派的,因為偏左表示我要自己來,我不想。
原本的 Self-test 只要看到 Owner 是 human:*,就立刻回:
HUMAN_INTERACTIVE_ACTIVE
後面的設備連線、UART 通道與 Target 回應全部不再檢查。
也就是說,Slider 被固定在 Human 最左邊。
但這個問題感覺似曾相識?
Day 3 已經先決定一條 UART 只能有一個 Writer,Human 與 Agent 的輸入都必須經過 Broker 仲裁。底下其實也已經具備暫時保留 Human 輸入的能力,只是當時文章沒有展開這一層。
它的實際動作大致如下:
暫停 Human 輸入
→ Human 新輸入先保留下來
→ Agent 執行 Command
→ 恢復 Human 輸入
→ 再把剛才保留的內容送出去
暫停的是 Human 寫入 UART 的權利,不是把 Console 關掉。minicom 還是會持續顯示 Target Output;UART 底下則維持 Single-writer,不會把 Human 與 Agent 的兩條 Command 混成一條。
程式內部把這套機制叫作 Suspend/Resume,保留下來的輸入則放在 Deferred Buffer。
PR #43 做的事情,就是讓 Self-test 也走這條既有路徑。
現在 Human Console 還在時,Self-test 不會立刻拒絕,而是繼續確認設備與 UART 是否正常。真的需要送出一條測試指令時,才短暫讓 Agent 使用 TX;完成後,再把輸入權還給 Human。
就算中途發生錯誤,恢復 Human 控制權這件事也不能被跳過。
至於確定不允許任何交接的情境,仍可明確使用:
serialwrap session self-test \
--selector COM0 \
--strict-human-lock
這個 PR 解決了 Human Console 一存在,Agent 就永遠站在門外的問題。
但問題永遠都是一體兩面的:
Console 還開著,Human 就一定正在使用嗎?
minicom 留著監看,和工程師正在輸入 Command,表面上都叫作「Human 還連著」,實際需要的處理卻不同。
人在打字時,Agent 當然不應該突然搶走整段互動控制權。
但 Console 只是開著看 Log,Human 已經一段時間沒有輸入,繼續把它當成永久占用也不合理。
這一塊在後續的 PR #65 才補上。
serialwrap 開始記錄 Human 最後一次真正輸入的時間,把「Console 還連著」與「Human 最近仍在操作」分開。程式裡分別叫作 human_attached 與 human_active。
概念上變成:
Human 最近仍在操作
→ Agent 不搶互動控制權,先等一下
Human 只是監看,已經沒有繼續輸入
→ Agent 可以暫時接手
→ Console 不斷線
→ 完成後再把控制權還回去
Source Code 裡把後者稱為 Soft Preempt。
名字聽起來很厲害,實際意思只是:
不把人踢出去,也不要求人先關 minicom;只是請目前沒有在輸入的人,暫時讓一下位置。
這才補上 Slider 靠近 Human 那一側的判斷。
不是只要看到人就全面停工,而是先確認人到底有沒有真的在用。
Slider 的另一側,則是 Agent 已經開始執行工作。
這時 Human 不需要離場,仍然可以透過 minicom 看 UART Log;但如果正好在 Agent Command 執行到一半時輸入新的內容,那些 Byte 不能立刻送進 UART。
否則 Target 收到的可能不是兩條 Command,而是一條誰都看不懂的混合物。
所以 Agent 執行期間:
Human 繼續看 Log
→ Human 新輸入先保留
→ Agent 完成目前的 Command
→ Human 輸入再接著送出
這裡的「Agent 優先」也不是整個 Full Run 永久鎖住 UART。
serialwrap 是逐條 Command 仲裁;Agent 已經排入的工作會先往下執行,Human 的輸入不會從中間切進去,但在命令邊界之外,控制權仍然可以正常交還。
換句話說,Slider 不會跑到 Agent 最右端。
Human 仍然在場、仍然看得到現場,也保留後續介入能力;只是不要在自動化執行到一半時,把手伸進 UART 裡一起攪拌。
把前後兩邊合起來,現行模型大致如下:
Human 正在操作
→ Agent 先不要搶互動控制權
Human 只是監看
→ Agent 可以暫時接手
→ 完成後還回去
Agent 正在執行 Command
→ Human 持續監看
→ 新輸入先保留
→ Command 完成後再送出
它不是 Human 與 Agent 各拿一半。
也不是誰的權限比較高。
真正的甜蜜點是:
誰正在使用 UART,另一邊就先讓一下;但不需要因此離開現場。
PR #43 先解掉 Self-test 永遠偏向 Human 的問題。
後續的 PR #65 再補上 Human Activity 判斷,讓 Slider 真的能在中間的綠色區裡左右移動。
PR #43 的測試先確認三件事:
Human Console 留著時,Self-test 仍然可以完成檢查。
明確使用 Strict Mode 時,Agent 不會送出任何 Probe。
不論 Probe 成功或失敗,最後都必須把輸入權還給 Human。
後續 PR #65 再補上「Human 最近是否真的有輸入」與 Soft Preempt 的測試。
再往後,PR #86 又補上多 Agent 同時進入時的保護。否則第二條 Agent 路徑如果再次接手,很可能把前一層記住的 Human 控制權蓋掉,最後沒有人知道該把 UART 還給誰。
這些測試沒有辦法證明所有 UART 情境都已經被收服。
但至少把最重要的底線釘住:
Human 不應該因為 Agent 工作而被踢出現場,Agent 也不應該因為 Human 還在現場就永遠無法開始。
到這裡,Human 正在操作時 Agent 會讓路;Agent 正在執行時,Human 也可以留在旁邊監看。
但 serialwrap 並沒有停在這裡。
隨著遇到的問題愈來愈多,它的操作方式、目錄位置與內部架構也改了好幾次。等我回頭把文件全部攤開來檢查,才發現有些地方還寫著舊路徑、舊指令,甚至還在介紹早已移除的功能。
Code 已經往前走了。
文件卻留在原地。
更精彩的是,Policy Check 仍然是 21 pass / 0 fail,只有一項既有的 Advisory Warning。
下一篇,我們來看看:
Code 都已經活在新世界了,為什麼文件檢查還在舊世界全部 PASS?
Have a nice day.