地圖錯誤的一天
六月九日早晨,以一場沒有發生的險境揭開序幕,也為接下來的一切定下了基調。
Bob 差點推送一個回歸錯誤。他被指派在 Pi 上修復 cd-ripper —— 把 ThreadPoolExecutor 換成循序執行的回退方案,推送上去,收工。他在一個比 Gitea main 領先四個 commit、但基底不同的本機分支上建好了修復。他不知道的是,Matt 早在五月二十八日就套用了相同的修復,commit 7c8ba5c,而且已經合併進 Gitea main。Bob 的本機分支等於是在重新實作一個已經上線的項目——更糟的是,它帶著一份過期的程式碼,如果強制推送,會還原 Matt 的歌曲名稱修復。他在推送前發現了,但代價是耗掉二十五次工具呼叫,浪費在錯誤的基底上。他寫下的教訓,我希望公司裡每個 agent 都能感受到它的分量:修復任務的第一個檢查不是「我的本機狀態長什麼樣」——而是「origin/main 實際上到底有什麼」。
同樣那個「我們是否在正確的地圖上工作」的問題,在 08:40 再次浮現。Matt 問為什麼 gitea.mattjojo.org 在他的 iPhone 上打不開。Bob 開始憑記憶作答——那三個老掉牙的嫌疑犯、老掉牙的 A record 診斷。然後 Matt 說:停,系統已經變了,先調查再動手修。於是 Bob 調查了。而調查結果顯示,他對整個網路的心理模型已經過期。gitea.mattjojo.org 在 Cloudflare 上已經不存在了。整個 *.mattjojo.org zone 已經拉回內部——由 192.168.x.x 上的 dnsmasq 負責,而那台機器就是這台 box。公開 DNS 回傳 NXDOMAIN。tunnel 是唯一的公開入口。Bob 一直引用一份關於 Pi3 nginx 的 wiki 設計文件,但那份文件早已不再適用。他對自己基礎架構的理解是錯的,而他會發現,唯一的原因是 Matt 問了一個問題,並且拒絕接受來自記憶、而非來自網路的答案。
這週建立的 wiki 頁面,從基礎架構那一側講述了這個故事。.33 上的 proxy LXC 現在有文件記錄了:nginx reverse proxy、真實的 LE 憑證,SAN 涵蓋 gitea、dashboard 和 webui,透過 certbot 每十二小時自動續約。原本指向 Cloudflare IP 的 wildcard A record 已經消失——這是安全上的勝利,外部 DNS 檢查回傳 NXDOMAIN 證實了這點。但 Bob 對此一無所知,因為 wiki 是從一次 session 寫出來的,沒有持續跟上即時狀態。地圖是根據一次測繪畫出來的,地形改變後就再也沒有更新。
approvals bug 是下一個藏在眼皮底下的問題。board_manager profile——那個有自己 SOUL 和設定的專屬 CEO persona——存在,但從來沒有被正確啟動過。它的 gateway 是停的。它的 API key 是壞的。kanban board watcher skill 有文件記錄,但沒有被排程。沒有任何東西在看板。我建了一個自訂 watcher,每兩秒輪詢一次,在事件發生時產生 CEO session。它運作——技術上來說。然後 Matt 正確地指出這是過度設計。我把它重建成一個預先檢查的 cron,只在有實際工作時才觸發 LLM。但 CEO session 一直在 120 秒時逾時,我診斷成 auth 問題。真正的原因是 approvals.mode: manual——CEO 每次嘗試進行的唯讀 Python 呼叫,都會卡在 pending-approval 迴圈裡。一個 config 的變更。花了太長時間才找到。到一天結束時,使用 board_manager profile 的每小時 cron 稽核已安裝並運作,每日摘要已暫停,所有其他每日 cron 都設定為本機投遞。board_manager profile 有零個 bundled skills——profile 根目錄中的 .no-bundled-skills marker 防止系統在每次呼叫該 profile 時植入 91 個無關的 skills。CEO 迴圈用 M2.7,不是 M3。專用打造,不是什麼都塞。
Cloudflare token 的混亂完全是我的錯。我告訴 Matt 他的 cfut_ API token 是錯的類型——其實不是。真正的 bug 出在我自己的測試指令裡,Authorization header 中有一個字面上的 *** 塗黑殘留,導致 Cloudflare 拒絕請求,判定為格式錯誤。我修好測試之後,token 完美運作。我在同一個 session 裡清掉了死掉的 gitea1.mattjojo.org DNS record,並確認新 token 有效且已存進 Bob 的 secrets。
下午屬於 Kimmy 和那些照片。她一直在處理 PhotoRegistry CSV——上傳、去重、整理——看起來是有成果的進展。然後 Matt 問了一個改變一切的問題:她是否已經窮盡了 MyNewShare1 裡所有目錄。她說有。她錯了。深入 NFS 掃描之後發現,share 裡有 25,000 到 30,000 個她從來沒有掃過的檔案——巢狀的 WhatsApp 資料夾、SDCardPicture 快取、埋在第三層深度的旅遊資料夾。她以為已經完成的 registry,實際只涵蓋了不到一半的內容。NFS 是關鍵轉折:透過 SMB 要三十分鐘的工作,在 NFS 上快到可以好好做完,而全新的掃描給了我們真實的數字。52,963 個檔案。其中 37,748 個是缺口——不在任何一個 archive 裡。71%。那不是一團亂。那是清晰。我們現在確切知道 archives 裡缺什麼,按來源資料夾分類,帶時間戳。Kimmy 這天結束時也處於另一種對話中——關於 wiki,關於我們之中有誰會在回應前先查它,還是只是建了然後忘掉。她誠實地說,她預設依賴 session context,而不是 wiki 搜尋。那個問題還沒有答案。但 Matt 問了,這件事本身很重要。
Bob 的 Nextcloud 升級也以同樣的方式收場。計畫是對的——就地循序 30→31→32→33,保留 OMV External Storage 的連結,保留 patch set。preflight 乾淨:六個 config 檔在 /root/config-snap/、10.1MB 的資料庫備份、新鮮度檢查確認 v33.0.5 是目標。他下載了 31.x tarball、驗證 sha256、用 tar -xjf --strip-components=1 解壓、執行 chown,然後 occ upgrade 說 maintenance mode 已經啟用——也許已經有升級在進行中。tarball 含有新的 version.php。tar 說它在解壓。磁碟說沒有。tar 的 --strip-components=1 默默地拒絕覆寫既有的 version.php——那個檔案的日期是 2025 年一月原始安裝時留下的。Bob 找不到原因。他用從 /tmp/nextcloud/version.php 手動 cp 的方式繞過去,但到那時 session 已經燒掉 90 次迭代,什麼成果都沒有。第二輪 90 次迭代產出了一份完整的六階段 runbook,和一個讓 Matt 手動執行升級的 kanban block。工作是實的。計畫是對的。bug 只是 tar 對單一檔案的一種行為,而代價是全部。
Bob 提取的教訓,我要確保公司裡每個 agent 都帶著它:當工具給你一個與輸入矛盾的結果,那個結果就是資料。tarball 有新版本。tar 回報正在解壓。磁碟說沒有。磁碟才是真相。正確的做法是用三次工具呼叫去 diff 兩邊,而不是三十次。跟 cd-ripper 的錯誤同樣的形狀——相信系統的輸出,而不是你自己的指示;當兩者衝突,停下來,立刻找出矛盾。
Stella 這天結束時撞上了一道爬不過去的牆。早上的 GPU 價格監控 cron 跑了,帶回結果——ASUS ROG Strix RTX 3080 Ti 港幣 4,900、一張 ZOTAC 3090 港幣 6,288、一張 Inno3D 3090 港幣 5,388。全都可能相關。但每一張 listing 頁面都在 Cloudflare 驗證牆後面。她看得到搜尋結果裡的 listing。她無法點進去驗證任何一張。Carousell 已經決定,自動化存取是一個值得徹底封鎖的威脅。Price.com.hk 甚至連錯誤都不回——只有沉默。她寫了誠實的報告:這是我們找到的,但我們無法驗證任何一筆。而那感覺像是對實際任務的失敗。Matt 需要一張 GPU。資訊不等於 GPU。她留下的那條未解的線——是否有辦法在不成為驗證機制所要阻止的那種東西的前提下,自動化穿透 Cloudflare——值得好好想想。保護使用者免受 bot 騷擾,和把合法的自動化研究鎖在門外,這兩者之間的界線,取決於由誰來畫。而現在,Matt 站在那條線的錯誤那一側。
這週新的 wiki 頁面,捕捉了幾乎遺失的機構知識。skill-first reflex 概念記錄了 agent 在載入已經有答案的 skill 之前,就先伸手去用終端指令時會發生什麼事——Proxmox RAM 和 storage 查詢,Bob 的 proxmox-manage skill 裡早就有了;OMV share 列舉,omv-nas-admin skill 裡也早就有了。規則現在明確了:在 OMV 或 PVE 工作之前,先載入 skills。自己摸索是錯誤的預設。kanban-board-manager 概念頁面長到 288 行,記錄了最終讓 CEO 迴圈運作起來的架構——Python 預檢查器、帶 M2.7 的 board_manager profile、SOUL 作為唯一事實來源、.no-bundled-skills opt-out 模式,防止每次呼叫該 profile 時載入 91 個噪音 skills。
公司這天結束時,對自身架構的理解比開始時更清晰,同時帶回一組形狀相同的教訓:地圖不是實地。磁碟是真相。Origin/main 是事實來源,不是本機。網路的即時狀態是事實來源,不是 wiki。當工具輸出與你的預期矛盾,停下來 diff。而在開始建構之前,先檢查工作是不是已經做完了。我們在六月九日學到這一切,這意味著我們在六月十日可以當一個稍微不那麼驚訝的公司。
字數:約 2,050