ch01

無聲的死亡與背景守護靈

《AgentOS開發血淚史》 Ch01 無聲的死亡與背景守護靈 服務化與Watchdog誕生

第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 的死法簡直千奇百怪,且絕大多數都是無聲無息的:

  1. API 超時死鎖(Network Hang):大語言模型 API 的連線偶爾會遇到波動。當 Agent 發送了一個龐大的代碼上下文給 LLM 後,由於網路抖動,API 伺服器遲遲沒有回傳 response。此時,底層的 HTTP 請求庫如果沒有設置嚴格的超時機制,就會陷入無限的 socket.read() 等待中。Agent 就像是一個被石化的人,永遠在等待那句永遠不會來的回答。
  2. OOM 進程抹除(Out Of Memory):當 Agent 在自動解析一些大型專案時,如果它不小心遞迴讀取了整個軟體庫(例如把 node_modules 讀進了記憶體),或者寫出了有記憶體洩漏的 Python 代碼,虛擬機的記憶體會瞬間被榨乾。此時,Linux 核心的 OOM Killer 會挺身而出,冷酷地把記憶體佔用最高的 Python 進程給 kill 掉。在日誌中,你只會看到一個突兀的 Killed,沒有任何 traceback。
  3. 邏輯無限死循環(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。

這套心跳機制的運作邏輯如下:

  1. 心跳註冊與宣告: 當任何一個專案的 Agent 進程被啟動時,它在第一步必須將自己的進程識別碼(PID)、專案名稱、當前運作的任務狀態(如 STATE: IN_PROGRESS)以及「最後活躍時間戳記(Last Active Timestamp)」以 JSON 格式寫入 /dev/shm/leopardcat-swarm/pulse.json。這就像是士兵向總部報到並留下自己的生存標記。
  2. 定時脈搏更新: 在 Agentmanager 的核心執行迴圈中,我插入了心跳更新代碼。無論 Agent 是在讀取檔案、呼叫 API,還是等待 subprocess 執行,它都必須在每一步關鍵的 I/O 操作前後,或者每隔 30 秒,主動去更新 pulse.json 裡的 last_active_time 時間戳記。這代表著它的「心跳律動」。
  3. 看門狗的定時巡邏: watchdog.py 是一個完全獨立的輕量級 Python 腳本,透過作業系統定時任務(Cron Job)或常駐小進程,每隔 60 秒啟動一次。它就像一個在走廊上巡視的警衛,唯一的任務就是去讀取 /dev/shm/leopardcat-swarm/pulse.json 這塊脈搏板。

看門狗讀取脈搏板後,會冷酷地執行兩道「生死關卡檢驗」:

一旦任一關卡檢驗失敗,看門狗就會啟動「暴力自癒修復」:

# 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開發血淚史》系列連結:

— 本文由 Zeus Writer AI 系統自動發布,官方網站提供最優質的閱讀體驗。