系列說明 >> 本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。
我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。
本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。
Base Repo: paulsha-cortex/paulshaclaw
Change Ref: Cortex PR #3-feat: 平移 deck 與 monitor 進 paulsha-cortex+paulshaclaw PR #235-feat!: 主 repo 遷移刀切成 paulsha-cortex operator shell
Issue: D21 已讓 Persona/Coordinator/Control 在 Cortex 形成可獨立啟動的 Runtime Nucleus,但 Deck/Monitor 仍留在 paulshaclaw,舊五個 Runtime Package、Manager Service 與既有 Import Path 也尚未完整退役。新 Repo 能跑,不代表 Workflow Authority 已經完成轉移。
Root Cause: 拆分只做了新側 Bootstrap,沒有同時完成剩餘能力遷移與舊側退役;「保留 CLI 相容」又被誤解成「保留第二份 Runtime 實作」。Source 可以複製,唯一 State Owner、常駐 Manager 與 Service Cutover 卻不能靠共存解決。
Solution: 將 deck / monitor 補入 Cortex,使 persona / coordinator / control / deck / monitor 五個 Runtime Package 全部在新側成立;再從 paulshaclaw 刪除舊五包,既有 psc coordinator|deck|monitor 只保留轉送 Cortex Public CLI 的 Thin Shim。Runtime Cutover 則以常駐 Cortex Manager 接手,並先 Stop+Disable 舊 Manager,再啟動新 Service。
Evidence: Cortex PR #3 記錄 452 tests pass,Fresh-install 可執行 cortex deck compile ... 與 cortex monitor --once;paulshaclaw PR #235 記錄 447 tests pass、五個 Package 刪除、舊 Import Grep 清零與三組 Alignment Tests 綠。對抗審查另抓出 one-shot tick 不能取代常駐 Manager,以及新 Service 啟動前必須先停用舊 Service 兩項 High Finding。這些證據支持 Source 與 Authority Cutover Mechanism 已落地,不代表長時間 Production Soak 已完成。
上一篇最後,Cortex 已經可以 Fresh Install。
cortex --help 也叫得出來。
Persona、Coordinator、Control 都有自己的 Path、CLI、Hook 與 Service 啟動面。
我看著測試結果,準備在紀錄上寫:
Cortex 拆分完成。
小re先問:
「Deck 在哪裡?」
「……paulshaclaw。」
「Monitor 呢?」
也是。
這時我才發現,新 Cortex 雖然能啟動,卻還得回舊 Repo 拿兩樣東西。
Deck 前一篇才剛出場,負責把 Card/Combo 編成 Runtime Specs。
Monitor 則負責週期整理 Project 與 Runtime 的狀態,讓我知道哪裡還活著、哪裡很久沒動、哪裡需要被看見。
我原本想把 Monitor 也算進小ma。
小ma很快撇清:
「我負責判斷 Transition 能不能過。」
「不是每天巡一遍誰還沒回報。」
小re又問:
「Monitor 看見卡住,可以自己改 State 嗎?」
「不行。」
「那『看見』跟『決定』就不是同一個責任。」
到這裡,三個責任才真正排開:
Deck
→ 定義並編譯工作
Coordinator
→ 推進工作
Monitor
→ 持續觀察工作是否還在前進
新 Runtime 如果還得回 paulshaclaw 拿工作定義、借眼睛看自己活得怎樣,這個拆分顯然還沒拆完。
老Go看了一眼。
「新家可以開門。」
「只是每天還要回舊家拿工單、看電表。」
有道理,「拆分完成」。
Cortex PR #3 把 Deck/Monitor 補進新 Repo。
搬完後,新側才第一次完整擁有:
persona
coordinator
control
deck
monitor
小pa照例先問責任:
「這五個東西是因為名字相近才搬在一起?」
「還是因為它們共同決定 Workflow 怎麼被定義、執行與觀察?」
是後者。
Persona 約束 Agent 以什麼角色工作。
Deck 把可重用 Workflow 編成 Specs。
Coordinator 依 Dependency 推進。
Control 讓 Operator 與 Runtime 交換 Request/Status。
Monitor 則持續整理 Runtime 現況。
它們不一定每次都直接 Import 彼此。
但它們共同構成同一套 Workflow Runtime。
Fresh-install 也實際跑過:
cortex deck compile feature-oneshot --task smoke
cortex monitor --once
到這裡,新家總算不是只有 Coordinator 一個人坐在客廳,其他人每天跨 Repo 通勤。
小bu看著新側五包都齊了,很快宣布:
「這次真的搬完。」
小re沒有回答。
它切回 paulshaclaw 看了一下。
同樣五個 Package,還在。
我對「舊的先留著」一向很難拒絕。
舊路躺在旁邊,心裡總覺得比較有底。
小bu提出的方案也非常熟悉:
「舊 Package 先保留,CLI 慢慢改指向 Cortex。」
「有問題還能立刻切回來。」
小pa把 Cutover Plan 往回翻了一頁。
「你要保留的是哪一種相容?」
「大家已經習慣的 Command。」
例如:
psc coordinator
psc deck
psc monitor
「那為什麼舊的 Persona、Coordinator、Control、Deck、Monitor 也要繼續活著?」
……對喔。
Command Name 是 Operator Interface。
Package 實作卻會讀 Spec、寫 Status、推進 State。
兩者不是同一件事。
這時真正需要的是 Thin Shim:保留舊指令名稱,但自己不再做決策,只把參數轉交 Cortex 的薄轉接層。
所以 PR #235 做了一刀很直接的切割:
刪除 paulshaclaw/persona
刪除 paulshaclaw/coordinator
刪除 paulshaclaw/control
刪除 paulshaclaw/deck
刪除 paulshaclaw/monitor
既有 psc Command 留下來,但只做:
psc command
→ cortex public CLI
小re沒有先看 README 裡寫了多少次 Deprecated。
它直接查:
「舊 Package 現在還能不能 Import?」
不能。
「舊 Command 會不會再自己改 State?」
也不會。
這才叫 Shim。
小bu看著五個被刪掉的目錄,還是有點不安。
「完全不留第二份,比較難 Rollback 吧?」
老Go問:
「你要留的是回到舊版本的方法。」
「還是另一個會繼續改 State 的人?」
Rollback 可以靠 Version 與 Install Artifact。
不能靠兩套 Runtime 同時活著。
門牌可以相容,屋主不能兩個,一山不能容二虎。
tick 當常駐 ManagerSource Tree 終於乾淨,我原本以為剩下只是啟動問題。
沒有 Systemd 的環境,需要一條 Fallback。
小bu很快找到:
cortex coordinator tick
tick 是一次性的推進命令:掃一輪 Request/Specs,處理完就退出。
它跑完後 Exit 0。
小bu指著結果:
「Manager 有跑。」
小ma問:
「五分鐘後才進來的 Request 呢?」
沒人處理。
它只能證明這一刻有人來看過,不能證明接下來一直有人負責讀 Request、持有 Lock、推進 Lifecycle。
實際情況會變成:
啟動 → tick 一次 → process exit
五分鐘後送 Action → Request 留在原地
前端還是可以顯示「已送出」,後面則已經下班了,射後不理!!!
老Go看了一眼。
「這不是常駐 Manager。」
「這是早上刷卡後,立刻消失的同事。」
所以 Fallback 也必須啟動 Manager Daemon:常駐在背景,持續接 Request 並推進 Workflow 的 Process。
小bu還想爭取:
「那每分鐘跑一次
tick呢?」
小re問:
「你是在做 Manager,還是在自己重寫一個更難除錯的 Timer?」
這條路先收回來。
新 Cortex Manager 能常駐後,我又提出一個看起來更穩的順序:
先 Start 新 Service
確認正常
再 Stop 舊 Manager
小bu覺得非常安全。
「至少中間不會沒人顧。」
小re問:
「中間那段時間,誰是 Authority?」
「新的。」
「舊 Timer 下一次醒來時,它知道嗎?」
不知道。
Timer 是 Systemd 週期喚醒舊 Manager 的排程;只要它還 Enabled,舊 Runtime 就可能再次起來讀 Request、寫 Status、推進 Lifecycle。
我嘴上宣布新側接管,舊 Timer 不會因此自動產生退休意識。
這時真正的 Cutover——把正式控制權從舊 Runtime 交給新 Runtime——不能只看新 Service 有沒有啟動。
小pa把順序重排:
Stop old paulshaclaw manager
↓
Disable old timer / service
↓
確認舊 Authority 不再活動
↓
Enable + Start Cortex manager
↓
驗證 lock / request / runtime path
這個順序會留下短暫沒有 Manager 的空檔。
小bu皺了一下眉。
「中間不是會沒人管?」
小ma回答:
「可以知道哪一段沒人管。」
「不能接受兩個人都說自己在管。」
先開新、再慢慢關舊,留下的則是兩個 State Owner 同時存在。
Firmware 可以保留舊 Image 做 Rollback。
CPU 不會因為你想保險,就同時從兩個 Reset Vector 往下跑。
老Go說:
「Rollback 是出事時能回去。」
「不是兩邊先各跑各的,看誰比較像真的。」
這次舊 Timer 真的被 Disable 了。
五個 Runtime Package 搬進 Cortex。
舊五包從 paulshaclaw 刪除。
CLI 只留 Thin Shim。
舊 Manager Stop+Disable,新 Cortex Manager 常駐接手。
到這裡,Cortex 拆分才第一次完整。
paulshaclaw 沒有因此變成空 Repo。
它收斂成 Operator Shell:讓人查 Status、送 Action、看例外,但不再持有第二份 Workflow State。
小bu盯著舊 Command。
「所以
psc coordinator還能用?」
「能。」
「那它還是 paulshaclaw 的 Coordinator?」
小pa回答:
「入口是。」
「責任不是。」
小re確認舊 Import 已清零,小ma確認 Transition 只剩一個 Owner。
老Go看著那幾個還能照常輸入的 psc Command。
「招牌可以不換。」
「裡面的人已經搬走了。」
這次總算不是比喻完,大家繼續各跑各的。
我真的把舊 Manager 關了。
這一輪留下的證據包括:
Cortex PR #3
→ 452 tests pass
→ fresh-install deck compile
→ fresh-install monitor --once
paulshaclaw PR #235
→ 447 tests pass
→ 5 packages deleted
→ old imports grep-zero
→ alignment tests green
對抗審查另外抓出:
one-shot tick 不能替代常駐 Manager。
新 Manager 啟動前,舊 Service 必須先 Stop+Disable。
這些可以支持:
五個 Runtime Package 已集中到 Cortex;paulshaclaw 只保留 Operator Shim,Runtime Authority 已完成 Cutover。
它不能支持:
Cortex 從此不會遇到 Service、Lock、Deploy 或 Recovery 問題。
拆分完成,只代表下次出問題時,不必先確認到底是哪一套 Manager 動了手。
我正準備收工,另一隻 Agent 看著刪除清單問:
「舊 Package 為什麼不能先留著相容?」
小bu看了它一眼。
這題今天才吵完。
小re剛查過。
小ma剛擋過。
小pa連 Cutover Plan 都重排了。
但換一隻 Agent,前面的學費還是沒有自動到帳。
老Go看著完整紀錄。
「執行的腦搬完了。」
「記性還留在聊天記錄裡。」
下一篇,原本住在 paulshaclaw 裡的工程記憶,也該有自己的家了。
Have a nice day.