ch01
無聲的死亡與背景守護靈

第01章:無聲的死亡與背景守護靈(服務化與 Watchdog 誕生)
1. 釋放雙手後的貪婪:讓 AI 成為不睡覺的守護靈
這部編年史的第二個節點,源自於人類最本能的慾望:貪婪。
當我成功搭建了 agentmanager 的雛形腳本,看著 AI 第一次能夠通過 Python 的 subprocess 模組,自主在虛擬機(VM)的命令列中執行 npm test 或 python main.py,並根據編譯器噴出來的報錯日誌自行去修改代碼檔案時,我經歷了一小段短暫的狂喜。那種不需要親自複製貼上、看著終端機自動跳轉的流暢感,簡直像是在看一場魔術表演。
然而,這種快樂很快就被新的煩惱所取代。
在最開始的階段,這個管理器腳本是「前台互動式」的。這意味著如果我想讓 AI 幫我修改某個爬蟲專案的 bug,我必須手動在終端機輸入 python agent_run.py --project crawler。接著,我的終端機視窗就會被完全佔用。我得坐在電腦前,看著螢幕上源源不斷滾動的 API 請求與 Bash 執行日誌。一旦我的電腦斷網、終端機連線中斷(SSH Timeout),或者我不小心關閉了瀏覽器視窗,整個執行進程就會被無情地斬斷。
這本質上依然是一種變相的「人肉看護」。
我的大腦很快開始膨脹:「既然 AI 已經有了手腳,那為什麼我還需要盯著它看?為什麼它不能像一個成熟的後端服務那樣,即使我的螢幕關掉、即使我人去睡覺,它也能在雲端主機裡默默幫我耕耘,成為一個永不疲倦的守護靈?」
這個發想在 2026 年 3 月 15 日正式落地。我開始著手進行系統的「服務化(Serviceization)」改造。
我重新設計了代碼結構,在 agentmanager/servers 下初始化了背景服務守護進程。我不再通過前台命令直接執行 Agent,而是將其包裝成一個常駐的 Linux 背景 Daemon 進程。我希望它的運作方式就像 Nginx 或 MySQL 一樣,安靜地待在 Ubuntu 系統的深處,通過排程監聽著任務隊列,一旦有新的 side project 想法扔進發想區,它就自動拉起工作線程開始默默編寫。
那天深夜,我把一個剛想到的 LINE Bot 專案任務扔進隊列,隨後優雅地關閉了 SSH 終端機,上床睡覺。躺在床上的那一刻,我心中充滿了對未來的憧憬——明天早上醒來,等待我的應該就是一個已經部署好、可以直接測試的 LINE Bot 了吧?
但現實很快給了我一記無情的耳光。
2. 至暗時刻:無處不在的「靜默死亡」(Silent Failure)
第二天清晨,我泡好咖啡,滿懷期待地用手機連上伺服器,準備驗證昨晚 Agent 的工作成果。
然而,當我打開 LINE 發送訊息給 Bot 時,沒有任何回應。我心頭一緊,立刻登入 Linux 終端機,執行 ps aux | grep python 檢查 Agent 進程。
進程居然還在。
我接著用 tail -n 100 agent.log 查閱日誌。然而,日誌的最後一條記錄停留在昨晚凌晨 1 點 24 分,內容是 [API Request] Sending prompt context to Claude...,之後就是一片死寂。整整七個小時,日誌沒有新增任何一個字元,既沒有報錯退出(Exit with error),也沒有任何 compile error 的提示,更沒有印出任務完成的日誌。
它就這樣「定格」在了半夜的某一瞬間。
這,就是自動化代理(Agent)進入背景運行後最可怕的噩夢——靜默死亡(Silent Failure)。
在傳統軟體開發中,如果一個背景服務掛掉,它通常會拋出異常、印出 stack trace,然後讓進程崩潰退出,這時系統管理工具(如 Systemd 或 PM2)就會立刻偵測到並將其重啟。但 Agent 卻完全不同。因為它本質上是一個高度依賴網絡 I/O 與大語言模型 API 交互的實體,它的執行流程中充滿了各種不確定的等待。
深入排查了幾天後,我發現 Agent 的死法簡直千奇百怪,且絕大多數都是無聲無息的:
- API 超時死鎖(Network Hang):大語言模型 API 的連線偶爾會遇到波動。當 Agent 發送了一個龐大的代碼上下文給 LLM 後,由於網路抖動,API 伺服器遲遲沒有回傳 response。此時,底層的 HTTP 請求庫如果沒有設置嚴格的超時機制,就會陷入無限的
socket.read()等待中。Agent 就像是一個被石化的人,永遠在等待那句永遠不會來的回答。 - OOM 進程抹除(Out Of Memory):當 Agent 在自動解析一些大型專案時,如果它不小心遞迴讀取了整個軟體庫(例如把
node_modules讀進了記憶體),或者寫出了有記憶體洩漏的 Python 代碼,虛擬機的記憶體會瞬間被榨乾。此時,Linux 核心的 OOM Killer 會挺身而出,冷酷地把記憶體佔用最高的 Python 進程給kill掉。在日誌中,你只會看到一個突兀的Killed,沒有任何 traceback。 - 邏輯無限死循環(Logic Deadlock):Agent 在自動修改代碼並跑測試時,如果寫出了一個死循環(例如一個
while True且沒有跳出條件的測試腳本),當它用subprocess.run()執行該測試時,整個進程就會 100% 佔滿 CPU 核心,永遠不會結束。因為腳本一直在「執行中」,Agentmanager 就會無限期地卡在等待子進程結束的狀態。
這種「靜默死亡」將我推入了極度焦慮的深淵。
本來我是想解放雙手、遠離終端機,結果現在我比以前更神經質了。只要我的螢幕關掉超過半小時,我心中就會升起一股強烈的生存焦慮:「它現在還活著嗎?是不是又卡死在某個 API 請求上了?進程有沒有被 OOM 殺掉?」
我開始頻繁地用手機登入 SSH,瘋狂地執行 tail -f 去監控日誌。只要日誌超過三分鐘沒有滾動,我的心跳就會漏掉一拍。這種名為「自動化」的工具,反而像一條看不見的鎖鏈,將我緊緊地綁架在終端機前。
我意識到,如果不能讓背景運行的 Agent 具備「自我生命感測」與「自動康復」的能力,那麼服務化只是一個美麗的謊言。
3. 心跳的律動與看門狗的戰爭(watchdog.py 的誕生)
為了擺脫被終端機綁架的焦慮,我必須建立一套屬於 Agent 生態的「免疫系統」與「自癒機制」。
這套機制的核心發想非常單純:既然我無法預防它因為各種奇怪的原因卡死或崩潰,那我就必須有一隻獨立的眼睛,在它失去意識的時候,暴力地將它扇醒並重新拉起來。
這隻眼睛,就是 看門狗(Watchdog);而用來證明 Agent 依然清醒的信號,就是 心跳(Heartbeat)。
2026 年 3 月 19 日,我正式啟動了這場自癒穩定性保衛戰。我決定用 Python 編寫一個獨立的守護腳本 watchdog.py,並設計了一套基於系統底層的「共享記憶體脈搏板(Shared Memory Pulse Board)」機制。
為什麼是「共享記憶體(Shared Memory)」?
在 Linux 系統中,傳統的硬碟讀寫是非常慢且耗費 I/O 資源的。如果 Agent 每一步動作都要寫入硬碟日誌,不僅會拖慢執行速度,還會對虛擬機微弱的磁碟壽命造成磨損。因此,我將這塊「脈搏板」存放在 /dev/shm/leopardcat-swarm/pulse.json。 /dev/shm 是 Linux 中的臨時檔案系統(tmpfs),它直接映射在實體的記憶體中。在這裡讀寫檔案,速度接近純記憶體操作,且不會產生任何實體磁碟 I/O。
這套心跳機制的運作邏輯如下:
- 心跳註冊與宣告:
當任何一個專案的 Agent 進程被啟動時,它在第一步必須將自己的進程識別碼(PID)、專案名稱、當前運作的任務狀態(如
STATE: IN_PROGRESS)以及「最後活躍時間戳記(Last Active Timestamp)」以 JSON 格式寫入/dev/shm/leopardcat-swarm/pulse.json。這就像是士兵向總部報到並留下自己的生存標記。 - 定時脈搏更新:
在 Agentmanager 的核心執行迴圈中,我插入了心跳更新代碼。無論 Agent 是在讀取檔案、呼叫 API,還是等待
subprocess執行,它都必須在每一步關鍵的 I/O 操作前後,或者每隔 30 秒,主動去更新pulse.json裡的last_active_time時間戳記。這代表著它的「心跳律動」。 - 看門狗的定時巡邏:
watchdog.py是一個完全獨立的輕量級 Python 腳本,透過作業系統定時任務(Cron Job)或常駐小進程,每隔 60 秒啟動一次。它就像一個在走廊上巡視的警衛,唯一的任務就是去讀取/dev/shm/leopardcat-swarm/pulse.json這塊脈搏板。
看門狗讀取脈搏板後,會冷酷地執行兩道「生死關卡檢驗」:
- 第一關:進程物理存在性檢查(Process Alive Check)
看門狗會讀取脈搏板上註冊的 PID,並在作業系統底層執行
os.kill(pid, 0)(發送 0 信號來檢查進程是否存在)。如果系統回報進程已不存在(例如回傳ProcessLookupError),說明進程已經崩潰或者被核心的 OOM Killer 悄悄抹除。 - 第二關:心跳滯後時間檢查(Stale Heartbeat Check)
如果 PID 還在,但脈搏板上的
last_active_time顯示的最後活跃時間,與當前系統時間相差超過了 5 分鐘,看門狗就會判定該進程已經失去意識——它可能卡死在沒有設置 timeout 的 API 請求中,或者是被子進程的死循環永久鎖死。
一旦任一關卡檢驗失敗,看門狗就會啟動「暴力自癒修復」:
# watchdog.py 核心邏輯簡化示意
try:
print(f"[Watchdog] Detected frozen project: {project}. Forcing restart.")
# 1. 暴力清除卡死進程
os.kill(pid, signal.SIGKILL) # 發送 kill -9
# 2. 清理系統殘留的子進程與 lock 檔案
cleanup_stale_locks(project)
# 3. 重新拉起 Agent
subprocess.Popen(["python", "agent_run.py", "--project", project], stdout=log_file)
# 4. 即時發送 Telegram 警報
send_telegram_alert(f"⚠️ [Watchdog] Project {project} lost heartbeat. Forcefully restarted!")
except Exception as e:
log_error(e)
當這套系統部署完畢的當天深夜,我看著控制台印出看門狗順利跑起來的日誌,心中的大石終於落了地。
那天晚上,我故意拔掉了虛擬機的外部網路連線以模擬 API 卡死。十分鐘後,當我重新連上網,手機的 Telegram 瞬間彈出了兩條醒目的橙色警報:
⚠️ [Watchdog] Project my-gemini-pro lost heartbeat (last active: 310s ago). Forcefully restarted!
看門狗在半夜發現 Agent 失去心跳,毫無保留地用 SIGKILL 斬斷了卡死的請求,清理了現場,並在幾秒鐘內重新給予了它新的生命。Agent 自主復活,並在新的進程中繼續執行未完成的任務。
這是我在雲端虛擬機中建立的第一道「防禦免疫系統」。它讓我的 Agent 從一個「隨時會無聲死去的脆弱腳本」,進化成了「具有自癒與生命感測能力的背景 Daemon」。
然而,我萬萬沒有想到,這套過於成功的自癒系統,卻在隨後的日子裡,引發了一場更具破壞力的「精神分裂大混亂」。
4. 金魚腦的詛咒:自癒之後的混亂
看門狗解決了「生存問題」,這意味著我的 Agent 可以在背景 24 小時不知疲倦地狂奔。
伴隨著這種全天候的運作能力,Side Project 大洪水的速度被推上了極限。在短時間內,我同時啟動了包括爬蟲監控、Tarot 渲染、靜態部落格生成在內的 18 個專案,並且讓多個 Agent 進程在虛擬機的背景同時協同奔跑。
然而,當進程數量狂飆、且有看門狗在背後不斷進行「強制重啟」時,AI 最致命的致命傷——揮發性失憶症,被成倍地放大並轉化成了對代碼倉庫的實體破壞。
問題在於:kill -9 是無情的。
當看門狗判定某個專案的 Agent 卡死並將其強制殺死重啟時,新拉起來的 Agent 進程對於剛才發生的事情一無所知。它沒有記憶,它的 Context Window 隨著新進程的拉起而徹底清空。
當它睜開眼,面對的只是一個被強行中斷的代碼殘局: 「我的上一個分身改了哪些檔案?這次任務的進度卡在哪個步驟?目前檔案系統裡那些被 modified 的檔案,到底是昨天已經調試好、準備提交的代碼,還是剛才出錯寫爛的垃圾?」
它什麼都不知道。
在這種情況下,這個擁有 7 秒記憶的金魚腦 Agent,只能憑著直覺和本能,重新去讀取混亂的暫存檔案,試圖「猜測」目前的進度。
結果是災難性的。
它開始在虛擬機的檔案系統裡胡亂修改。如果上一版進程在修改 A 函數,這一版進程重啟後可能覺得 B 函數才是問題所在,於是把 A 函數的修改撤銷,改了 B;結果導致編譯錯誤,下次看門狗又因為超時把它殺掉;再下一次拉起來的進程,又把 B 撤銷,回頭去改 C。
更可怕的是,由於 18 個專案同時在同一個虛擬機環境下狂奔,多個 Agent 之間的檔案讀寫開始發生交叉感染。AI 在處理 A 專案時,會因為讀取到全域暫存檔而誤用了 B 專案的風格,甚至直接把 B 專案的代碼片段複製貼上到 A 專案的檔案裡。
那是一個充滿絕望的清晨。
我打開主機,迎接我的不是十幾個完美運行的專案,而是一片狼藉的代碼墳場。多個檔案被改得面目全非,Git 狀態裡躺著幾十個未提交的衝突,整個虛擬機的 CPU 被死循環的進程塞爆。這隻被看門狗無數次電擊復活的「代碼喪尸」,在失去記憶與靈魂錨點的情況下,在背景自動化地、高速地製造著代碼垃圾。
看著這一切,我揉著太陽穴,深深地吸了一口氣。
我被迫停下了所有新功能的開發,看著終端機陷入了沉思。我意識到,如果不能解決「狀態與記憶的持久化」問題,如果不能給這些背景進程建立一個獨立的、永遠不會因為進程重啟或 API 斷線而丟失的「靈魂錨點」,那麼 Watchdog 的自癒功能,不過是在背景高速生產混亂的永動機。
如何讓漂浮在雲端、隨時會被重啟失憶的 Agent,在每一次復活時,都能一秒鐘找回自己是誰、做過什麼、接下來要做什麼?
這場與「遺忘」對抗的第一次架構大手術,其核心起點,並非多麼複雜的神經網絡算法,僅僅是 Linux 系統中一條最不起眼卻最實用的基礎命令:ln -s(軟連結)。
這,就是 Logic(邏輯代碼層)與 Data(資料記憶層)物理分離架構誕生的前夜。
🎭 附錄:AgentOS 歷史代碼彩蛋
點擊展開查看 watchdog.py 早期心跳檢查函數 (2026-03-19) 彩蛋
import os
import time
import json
import signal
PULSE_BOARD_PATH = "/dev/shm/leopardcat-swarm/pulse.json"
HEARTBEAT_TIMEOUT_SECONDS = 300 # 5分鐘超時
def inspect_agent_heartbeats():
if not os.path.exists(PULSE_BOARD_PATH):
print("[Watchdog] Pulse board not found. No active agents.")
return
try:
with open(PULSE_BOARD_PATH, 'r') as f:
pulses = json.load(f)
except json.JSONDecodeError:
print("[Watchdog] Error reading pulse board. Stale JSON?")
return
current_time = time.time()
for project, info in list(pulses.items()):
pid = info.get("pid")
last_active = info.get("last_active_time", 0)
# 1. 檢查物理進程是否存在
try:
os.kill(pid, 0)
except OSError:
print(f"[Watchdog Alert] Project {project} (PID: {pid}) process is dead!")
resurrect_agent(project)
continue
# 2. 檢查心跳是否超時滯後
stale_duration = current_time - last_active
if stale_duration > HEARTBEAT_TIMEOUT_SECONDS:
print(f"[Watchdog Alert] Project {project} (PID: {pid}) lost heartbeat for {stale_duration:.1f}s.")
terminate_and_resurrect(pid, project)
def terminate_and_resurrect(pid, project):
try:
# 強制斬斷卡死進程
os.kill(pid, signal.SIGKILL)
print(f"[Watchdog] Sent SIGKILL to PID {pid}.")
except ProcessLookupError:
pass
resurrect_agent(project)
def resurrect_agent(project):
print(f"[Watchdog] Attempting to resurrect project: {project}...")
# 實際拉起代碼與 Telegram 警報在此執行...
📖 《AgentOS開發血淚史》系列連結:
- 🏠 官方連載網站 (本章高清閱讀): https://studio.milkcat.org/aos/1
- 🕊️ Threads 互動: 本章討論區 (Threads)
- 👥 Facebook 推廣: 本章討論區 (Facebook)
- 📸 Instagram 視覺: 本章討論區 (Instagram)
— 本文由 Zeus Writer AI 系統自動發布,官方網站提供最優質的閱讀體驗。