Skip to content

About

agent 治理平面:manager 派工決策 + persona scope 護欄 + control 檔案契約控制面

Resources

Contributing

Security policy

Stars

13 stars

Watchers

1 watching

Forks

Repository files navigation

Fact: paulsha-cortex 是 work / WorkflowRun / Job / Slice lifecycle 的唯一 authority,負責 vendor-neutral dispatch、retry、review、delivery 與 completion;domain tools 只回傳 artifacts,不得改寫 lifecycle truth。

paulsha-cortex

paulsha-cortex 是由 orchestrator 主導的 harness 治理平面三件套:persona 契約、coordinator 派工、control 檔案契約。它把可移植的治理邏輯拆成獨立套件,讓主 repo 專注在產品行為,並把 Stage 3/4 的 guardrail、manager control plane、deck / monitor 以最小 runtime 依賴方式出貨。

定位

flowchart LR
    claw[產品 / 編排主 repo] --> cortex[paulsha-cortex\npersona + coordinator + control]
    claw --> hippo[paulsha-hippo\n共用基礎能力]
    cortex --> deck[deck + monitor]
    cortex -. 不依賴 .-> hippo
Loading
  • 主產品 repo:產品與 orchestration 上游,之後可 pin 本 repo commit SHA 做遷移刀。
  • paulsha-cortex:治理平面抽離包,提供 cortex CLI、persona scope guardrail、manager runtime、deck / monitor 與檔案契約。
  • paulsha-hippo:既有共用基礎能力;本 repo 已剪除 runtime 依賴,僅保留檔案契約層級的整合。

persona 是 manager 與 guardrail 共同引用的角色契約資料(role profile + scope subject),不是執行中的 agent session;真正執行的是 AgentInstance,真正做安全判斷的是 guardrail / policy engine,它們只讀 persona 契約做 enforcement。

Quota observation shadow

paulsha_cortex.coordinator.quota_observation 是 #866 的純 stdlib wire-contract 模組:它解析 standalone UnitDefinition、PoolDescriptor、 ProfilePoolBinding、QuotaObservation,並計算 binding_status()、 freshness()、event_identity()。#836 沿用這個 schema-core,新增 quota_sources 的純 payload adapter、quota_ledger 的 Manager-owned append-only event store,以及 quota_shadow 的唯讀 reconciliation。shadow 會按 pool/window 分開顯示 observed、estimated、unknown、native usage 與 coverage gaps;不同 unit 沒有已驗證 mapping 時不會換算或扣抵。Shadow consumer API 接受 controller、 worker、reviewer 的既有 registry/#325 extract_usage() 結果,不另讀 log 或解析 token。

provider adapter 接收呼叫端已取得的 Codex account/rateLimits/read、Copilot SDK account.getQuota 或 Antigravity CLI /usage JSON(參考 Codex App Server protocol、 Copilot SDK usage and billing、 Antigravity CLI usage);adapter 不啟動命令、不讀 credentials。沒有已核實 machine-readable quota 介面的 executor 回傳 unknown 與 coverage gap。Ledger 位於 quota_observation_root(),以獨立事件檔 保存,已登記於 Trust Root;corrupt/future/無法讀取資料 fail closed。這些 API 只供 shadow producer/consumer 使用,不接 workflow chain、排序、reservation、admission 或派工, 亦可由 record_external_observation() 匯入呼叫端已唯讀取得的外部 host/session 事件;未覆蓋的外部來源仍標 gap。這些 API 不代表 provider live read 或安裝 runtime 已驗收。

Quota observation collector(cortex quota)

paulsha_cortex.coordinator.quota_collectors(#836)是唯一真的會啟動 provider CLI 的地方,讓 operator 能產生真實觀測供上面的 shadow ledger 使用:

  • codex:啟動 codex app-server,走 stdio JSON-RPC 讀 account/rateLimits/read(先 initialize/initialized,再送 request); 整體逾時(預設 20 秒)或協定失敗(逾時、子程序提前結束、壞 JSON、協定 錯誤、缺 id=2 回應)皆終止子程序(terminate→kill)並回精確 gap。
  • agy:執行 agy -p /usage --output-format json 一次性讀取;逾時、非零 exit、壞 JSON 各自回精確 gap。
  • copilot:Copilot SDK 的 account.getQuota 是正式介面,但 copilot CLI 目前沒有對應的非互動唯讀路徑(已用 copilot --help 逐條核對其子命令), 回報 copilot-quota-read-path-unavailable,不猜測、不爬網頁。
  • claude/cg:沿用 provider_read_contract() 既有的 unsupported/unknown 狀態與理由,不重複定義。

不讀任何 credential 檔,也不把 provider 原始 payload 或帳號識別(如 accountId)外洩到 ledger、stdout 或錯誤訊息——只有 capture_provider_quota() 產出的、已去識別的 observation 才會往下游走。

quota_collectors.load_collector_config(path) 讀取版本化的 cortex/quota-pools/v1 設定檔(config_revision/descriptors/ unit_catalog/bindings,選填 lease_ms),descriptor/binding 驗證完全 交給既有的 quota_observation.parse_*;額外解析選填的 collector_targets 區塊(每個 executor 的 resource_key/window_id/profile_key/pool_ref), 依 profile_key 分組後對應到 ProviderQuotaTarget。設定檔路徑一律由呼叫端 明確給定,本模組不讀任何預設路徑。

CLI 提供四個子命令:

  • cortex quota observe --config <path> [--executor codex|agy|copilot|claude|cg] [--dry-run] [--json] [--output <file>] [--timeout-s <秒數>]:對設定檔內每個 executor 執行唯讀讀取,預設以 Manager 帳號寫入 QuotaShadowService(QuotaEventLedger())。ledger 路徑不可寫時(例如多 UID 部署下執行者不是 Manager 帳號)不崩潰,回報 ledger-unwritable 並建議改用 --output。--dry-run 不寫任何檔,只印分類結果;--json 輸出機讀摘要 (每 executor 的 state/observations/gaps)。
  • cortex quota import --config <path> --file <file>:由 Manager 帳號匯入 --output 落地的 observation 檔。匯入前先以 quota_collectors.validate_import_payload() 核對這份檔案真的是 observe --output 產生的 export envelope(固定 schema/ collector_version/config_revision),且每筆 observation 的 source.method/source_schema/authority_ref/unit 的 semantics_ref 逐字等於 provider_read_contract(executor) 的正式值、config_revision 等於目前 --config、(pool_ref, window_id, profile_key) 屬於設定檔該 executor 的 collector_targets、observed_at_ms 不在未來也未超過自身 ttl_ms 過期;任一筆不符即整份拒絕(不部分匯入),錯誤訊息只帶機器可讀 reason code,不含原始內容。通過驗證後才走 record_external_observation()(仍要求 method 為 provider_status/structured_event)逐筆寫入 ledger。
  • cortex quota bindings --report --config <path> [--store <path>] [--json] (#1116):唯讀彙總 admission decision store 裡『曾經派工用到、但用目前 --config 重算仍然沒有任何 binding 涵蓋』的 resolved profile key,依 executor/model_id/profile_key 分組,附出現次數與最近一次時間戳。 只讀 decision store 與設定檔,不寫任何狀態,也不啟動任何 provider CLI; --store 缺省時沿用 AdmissionDecisionStore 既有預設路徑。
  • cortex quota reservations [--store <path>] [--json](#838):唯讀回報 目前 instance 解析到的 reservation store(authority.store/ store_source/exists)、各邏輯狀態筆數、lease 已過期但仍持有容量的 uncertain 筆數、每個 pool/window 的 committed,以及 quota_reservation.authority_coverage() 的跨 host/跨 UID/跨 coordinator root coverage gap(schema cortex-porcelain/quota-reservations-report/v1)。不建立任何檔案;store 不存在(shadow 從不建立)時計數為零,store 損毀 exit 1。見下方「Quota reservation authority」的協調範圍說明。

多 UID 部署的建議流程:以任意帳號執行 cortex quota observe --config <path> --output observation.json(唯讀,只落已去識別的 observation),再由 Manager 帳號執行 cortex quota import --config <path> --file observation.json 寫入 ledger。

信任邊界:cortex quota import 假設執行者是 Manager 帳號、--file 是 collector(cortex quota observe --output)產生的檔案。上述檢查能擋掉「檔案 被搬到別的 config/別的 executor 環境」或「隨手竄改單一欄位」這類意外或粗糙 偽造,但無法防止持有 operator 執行權限者刻意偽造整份檔案——只要偽造者能 在本機跑一次 cortex quota observe --config <path> --output x.json,就能取得 所有欄位的合法值再自行拼裝別的內容(例如竄改 amount 但保留其餘欄位全部合法)。 要防這一層,需要 collector 對輸出簽章、import 端驗簽——目前尚未實作,另議。

Quota reservation authority

paulsha_cortex.coordinator.quota_reservation(#838)提供跨 instance 共享的 原子 reserve/bind/settle/release/reconcile 生命週期,讓同帳號下 多個 Manager instance 對同一個共享 pool/window 的預留有單一權威來源:以檔案鎖 序列化同一份 append-only JSONL 事件檔,同池競爭最後一單位時恰好一個成功, 多 pool 需求 all-or-none(任一不足即整份拒絕,不留半張 grant,也不扣其他池)。

消費端協定固定為 reserve → 建立 job 記錄取得 job_id → bind(job_id) → 才 spawn:reserved 恆表示「尚未 spawn」,release 只用於 bind 前放棄;settle 只接受 bound(job 終局,含 spawn 失敗);crash 或 lease 過期由 reconcile 依可信 liveness 證據處理。如此 reserve 的冪等回放即使交回 owner_token,也無法釋放已 spawn 的 job 所占用的額度。 pool_ref/window_id 沿用上面 #836 shadow 的契約,刻意不含 model 維度。

每筆 reservation 綁定 run_id/card_id/decision_id/attempt_id 與 observation/demand 版本;bind()/settle()/release() 皆需驗證 owner token、attempt id 與 CAS expected_sequence,錯誤呼叫端一律拒絕而不是靜默 生效。crash/restart 之後,lease 到期本身永不證明可以釋放容量——只有 reconcile() 帶入耐久 job 存活證據並給出 confirmed-terminated 才會釋放, inconclusive 或未 reconcile 一律回報 uncertain 並持續佔用容量。同一個 decision 用相同組成重複呼叫 reserve()/terminal 事件重送皆冪等回放;組成 不同則回報 conflict,不會靜默選邊。

本模組刻意不做候選排序、fallback 或 quota dispatch threshold(由 #839 消費 #836 的 measured usage),也不接線 任何實際 spawn path,也沒有自己的開關:是否預留完全由呼叫端決定—— production 只有 #839 quota_admission 在 PSC_QUOTA_ADMISSION_ENFORCE=on 時呼叫(enforce ⇒ 選中的受管候選一律原子預留;shadow ⇒ 完全不碰本模組)。 先前文件描述的 reservation 專屬開關 PSC_QUOTA_RESERVATION_ENFORCE 從來 沒有任何 production 讀取點(設了也不會改變任何行為),已連同其 helper 一併 移除;rollback 只需把 PSC_QUOTA_ADMISSION_ENFORCE 改回非 on。

協調範圍與 coverage gap:以檔案鎖序列化的是同一台主機、同一個檔案。 store 固定在 coordinator_root()/quota-reservations/reservations.jsonl (paths.quota_reservation_root(),沒有獨立的覆寫變數),且沿用 owner-only 硬化(父目錄不得有 group/other 位元、檔案 0600、O_NOFOLLOW)。因此 跨主機、跨 UID、跨 coordinator root 的 Manager 都不共用 authority—— quota_reservation.authority_coverage() 以機讀形式列出這三個 gap (reservation-authority-host-local-flock/ reservation-store-owner-only-permissions/ reservation-authority-scoped-to-coordinator-root), cortex quota reservations [--store <path>] [--json] 連同實際解析到的 store 路徑一起輸出。要確認兩個 instance 是否真的共用同一個 authority,對 各自的 PSC_INSTANCE 各跑一次 cortex quota reservations --json,比對 authority.store。

部署拓撲(#838 G838-5,只描述現況,不改部署):

  • coordinator_root() 的解析順序:process 的 PSC_COORDINATOR_ROOT(絕對 路徑)→ process 的 PSC_AGENTS_ROOT + /coordinator → 依 PSC_INSTANCE (預設 cortex)讀 installer bootstrap ~/.agents/core/runtime/<instance>-manager.env 裡的 PSC_COORDINATOR_ROOT, 否則其 PSC_AGENTS_ROOT + /coordinator → $HOME/.agents/coordinator。 cortex install service 不管理 PSC_COORDINATOR_ROOT(見下方「Path 契約」);同一個 $HOME、都沒設時,多個 instance 會落在同一個 ~/.agents/coordinator,任何一個 instance 自設 PSC_COORDINATOR_ROOT 就 有自己的 authority。
  • 同帳號多 instance 要共用 authority,只能讓它們解析到同一個 coordinator root——這會一併共用 jobs.json registry 與全部 coordinator 狀態,不只 額度;而且 #836 ledger(quota-observations/)與 #839 decision receipt (quota-admission-decisions/)也掛在同一個 root 下,capacity 來自各自 ledger 的投影,只共用 reservation 而不共用 ledger 會讓各 instance 以不同 的 remaining 當 capacity。
  • user 級與 system 級 instance(不同 UID、不同 $HOME)目前無法共用: 即使把兩邊的 PSC_COORDINATOR_ROOT 指到同一個路徑,owner-only 權限檢查 也會讓非擁有者開檔失敗(reservation-store-open-failed/ ...-parent-permissions-invalid,enforce 下 fail closed)。要支援需要 程式變更(獨立的 reservation/ledger root 覆寫加上跨 UID 的權限模型), 屬待裁決的後續設計,不是部署設定可以達成的。

list_by_state(state, *, now_ms)(唯讀)列出目前邏輯狀態恰為 state (reserved/bound/settled/released)的所有 reservation,讓呼叫端 可以直接以本 authority 為真相來源做收斂掃描,不必經過任何下游 receipt/ 索引是否成功寫入;純查詢,不修改任何狀態、不新增事件種類,狀態機與寫入 協定不變。

renew(reservation_id, owner_token, attempt_id, lease_ms, expected_sequence, now_ms)(純加法,新事件種類 renew)讓合法持有者(知道自己 owner_token) 在 provisioning 期間主動延長 lease,只允許從 reserved 續租;與 reconcile(resolution="confirmed-alive", renew_lease_ms=...) 的差異是後者 刻意不驗證 owner_token/attempt_id(給 crash 之後、原 owner 已不存在時的 第三方復原用),兩者服務不同情境,互不重疊。

Quota-aware admission

paulsha_cortex.coordinator.quota_admission(#839)在既有候選分層排序/ runtime preflight/execution-profile 硬濾(pin、權限、角色、reviewer independence、#842 qualification)之後,疊上一層「這個已核可的候選現在 有沒有額度」:以上面 #836 的 QuotaShadowService.project() 唯讀投影算出每個 pool/window 是 sufficient/insufficient/unknown(同池換 model 不算恢復; 短窗夠、週窗不足一樣視為不可行),只替目前選中的那一個候選原子預留 (quota_reservation.reserve_for_candidate),成功才繼續 provisioning/ spawn;race 落敗、額度不可行,或 reserve() 回報這個 decision_id 的 grant 在這次呼叫之前就已經存在(duplicate——可能是另一個 Manager instance 剛贏得的 grant,這次呼叫從未替它 reserve 過,因此永遠不是它的擁有者)時, manager._dispatch_workflow_card 都會排除該候選、換下一個既有排序候選重試 (沿用 _runtime_preflight_gate/_select_workflow_identity 既有機制,不 重建候選池);duplicate 且那筆舊 reservation 目前仍是 reserved/bound 時精確等待(quota-admission-attempt-held-elsewhere),不建 job、也不對它 呼叫 release()/bind()——只有這次呼叫自己拿到 granted 的候選才是可信 擁有者,避免誤釋放別的 instance 仍在用的 grant。全部候選皆不可行才回精確 wait 理由,不建立任何 job、不留半額度。

duplicate 但那筆舊 reservation 已經是終局(settled/released,例如 上一次 retry 在 create_job() 之前失敗、已呼叫 release_reservation_before_spawn,或已被下方的 reconcile 掃描收斂)—— job 從未真正建立過,attempt_id 依 job 數推算出的計數因此不會前進,下一次 retry 只會用同一個 attempt_id/decision_id 撞到同一筆已終結的 duplicate。 reserve_for_candidate_with_generation_fallback 把『世代』疊在 attempt_id 之上(世代 0 就是原始 attempt_id,世代 ≥1 附加 :g{generation}),偵測到 duplicate-且終局時決定性地換算下一個世代重新 reserve,直到拿到 granted/denied/conflict,或撞到仍是 reserved/bound 的 duplicate (held-elsewhere,交由既有判斷)——同一次呼叫只會在探測到的第一個尚未 終結的世代真正改變狀態,不會一次 retry 產生兩個 grant。

Reservation 的 reserve → create_job → bind(job_id) → spawn 順序嚴格對應 #838 現行協定:job 記錄一旦建立即 bind(),之後只能經 settle()(含派工時 429/infra 失敗)或 reconcile() 結束;job 記錄建立前失敗才用 release(reason="fail-before-spawn")。settle 之後(不論成功或失敗)與 periodic reconcile 收斂 bound reservation 一樣,共用同一個 _quota_admission_record_terminal_usage helper 呼叫 QuotaShadowService.record_terminal_usage() 記終局 usage——spawn 時 429/ infra 失敗不必等到 restart 後的 reconcile 掃描才補記消耗;settle 本身結構性 被拒(例如 bind 前就已經被別的路徑處理過)時不記,交給既有 reconcile 依 job registry 事實判定。reconcile_bound_reservations() 另外提供 restart/crash 後的收斂掃描——job registry 查不到時一律 inconclusive (不因為查不到就假設已終止並釋放額度),只有確認終局才釋放。

reconcile_reserved_reservations() 收斂 create_job() 之後、bind() 之前 crash 留下的無 job_id reservation;掃描目標直接來自 QuotaReservationAuthority.list_by_state("reserved", ...)(唯讀新增 API, 不改 #838 狀態機/寫入協定),不依賴任何下游 AdmissionDecisionStore 是否 成功記錄這筆決策——合法持有者在 reserve() 成功後、寫入 admit receipt 之前 crash(或 receipt 寫入本身失敗)時,這筆 reservation 仍會被掃到並依 job registry 事實安全收斂或維持,不會因為 receipt 從未落地就對收斂掃描永久 隱形、卡死容量。

每個 decision 都有耐久、append-only 的 receipt(AdmissionDecisionStore, 已登記於 Trust Root),綁定 run_id/card_id/attempt_id/profile_key 與 qualification/observation/demand/policy 版本,以及被排除候選與理由; 同一個 attempt 重送冪等回放,不會二次扣款或搬活 job。admit receipt(含 shadow 與未受額度管理的候選)與 wait receipt 一樣先讀舊值、有紀錄就直接 沿用不重寫——generated_at_ms 是每次呼叫當下的時間戳,retry 必然與上一次 不同,若無條件 record() 會被判定成內容衝突而丟例外;store 讀寫任何錯誤 (含衝突、IO、損毀)一律靜默降級為 None,只影響這筆診斷投影,絕不讓 shadow(本該完全旁觀)也被這個純診斷寫入拖累而改變既有派工結果。全部候選 皆不可行(enforce 下的 quota-admission-insufficient)或 quota-pools 設定 檔本身無效(quota-config-invalid)時,同樣留一筆 outcome wait 的 receipt 並更新 WorkflowRun.quota_admission[persona] 投影——不再只標 needs_human;receipt 冪等(同一個 attempt 只留第一次觀察到的拒絕快照), store 讀寫失敗一律靜默降級,絕不會讓已經確定的 fail-closed 派工結果變成 派工。#837 operational usage forecast 由 owner 裁決為 not planned。新決策使用 DISPATCH_UNIT_DEMAND_VERSION 的一個原生計量單位門檻,不推算模型用量;已量測 的 Cortex 終局用量由 #836 ledger 記錄,並在 QuotaShadowService.project() 中 先從觀測 remaining 扣除再准入。歷史 DEMAND_FIXTURE_VERSION receipt 仍可讀, 投影會繼續標為舊版 estimated provenance。wait receipt 同時保留最早已知 reset_at_ms 與 retry_eligible;periodic tick 只在 fresh observation 顯示有 候選可行後自動續派,沿用同一 frozen card/pin 並建立安全的新 attempt。單有 reset 時間不會解除等待。

quota_admission_enabled()(PSC_QUOTA_ADMISSION_ENFORCE)是額度准入 唯一的 enforce 開關——#838 reservation authority 沒有另一道開關, enforce 下選中的受管候選一律原子預留。預設為 shadow——manager 只在有呼叫端明確傳入 quota_admission.DispatchContext 時才會呼叫任何一行本模組(dispatch_workflow_card/resume_workflow_run 新增的 quota_admission_context 參數,缺省 None),shadow 模式下只用 唯讀投影記一份 decision receipt 供觀察,不呼叫 reserve()、不改變既有派工 結果;rollback 只需把環境變數改回非 on(或整條不接線 context),不需要 刪除已經寫下的 decision receipt/reservation/consumption 證據。spawn 階段若 launcher 提供結構化 HTTP 429,Manager 保留 rate_limited/structured 分類並 寫入 durable executor backoff;不改 model qualification 或品質分。之後只在既有 retry/resume 的新 attempt 邊界重選候選,不搬動仍執行的 job。

quota admission 的啟用前提、fresh observation、wait 恢復、rollback 與 evidence 查核步驟,見Quota admission enforce 操作手冊。

Production 接線:paulsha_cortex/coordinator/manager_daemon.py 與 quota-pools 設定檔

paulsha_cortex/coordinator/manager_daemon.py 在 workflow start、operator resume(workflow-action/ work-action 兩條 resume/retry 路徑)與 periodic resume 全部五個 dispatch/resume 呼叫點,都會以下述設定檔建構同一份 DispatchContext 並 傳入;periodic tick 另外呼叫 manager.reconcile_quota_admission_reservations() 收斂 reserved(create_job() 之後、bind() 之前 crash 留下的無 job_id reservation)與 bound 兩種狀態,job 進終局時若 binding 可解析,同時透過 QuotaShadowService.record_terminal_usage() 記消耗(成功/失敗都記, infra/429 失敗不改寫品質分類)。

fanout 消費端 autonomy.dispatch_ready() 也使用同一份 DispatchContext 與 quota_admission.assess_candidate_quota()/reservation authority;daemon 的 手動 fanout 與 periodic run_tick() 都會傳入 context。shadow 只追加共用 AdmissionDecisionStore receipt,保留既有派工結果;enforce 只在受管候選 reserve 成功後建立 job、bind 到 job id 再 spawn。#381 SpawnAdmissionLimiter 先行,limiter 拒絕時不會 reserve。額度不足或 reservation 被其他 attempt 持有時,fanout 回傳 quota_waits 結構化等待;periodic tick 每輪以 fresh observation 重評,額度足夠後自動續派,reset 時間本身不會解除等待。

同一支 reconcile 的第三步 manager.harvest_quota_terminal_usage()(#836) 在 shadow 與 enforce 都會執行:shadow 沒有 reservation 可 settle,受管 workflow job 的終局 usage 改由這一步依 registry 與 admit receipt 的事實寫進 quota ledger,讓 shadow 投影與 decision receipt 看得到 Cortex 自家的消耗。 job 對 receipt 只認事實——enforce job 用 quota_decision_id 精確比對; shadow job 用 attempt ordinal(n{k} 即該 run/card 第 k 個 job)+ receipt selected 的 executor/model_id,對不上或同一個 ordinal 對到多個 resolved profile key 就不記(skipped 理由 no-admit-decision/ ambiguous-admit-decision)。idempotency key 與 enforce settle 路徑相同 (terminal-usage:v1:<job_id>:<metric>:<pool>:<window>),因此重複 harvest、 manager 重啟、enforce 已由 on_settled 記過,ledger 都只有一份;記到一半 crash 的缺口下一輪補齊。manager persona 與沒有 admit receipt 的 legacy job 不在收割範圍。要真的寫入 ledger,quota-pools 設定檔必須有涵蓋 job metric 的 usage_unit_refs,否則 tick summary 的 quota_admission_reconcile.terminal_usage.skipped 會回 usage-unit-mapping-missing,ledger 不變。

Manager 讀取 paulsha_cortex.config.paths.quota_pools_config_path() (預設 ~/.config/paulshaclaw/quota-pools.json,可用 PSC_QUOTA_POOLS_CONFIG 覆寫整個檔案路徑;比照既有 paulshaclaw.yaml,這是 operator-owned app 設定, 不是 Trust Root 治理的 durable-state 資產),schema 為 cortex/quota-pools/v1:

{
  "schema": "cortex/quota-pools/v1",
  "config_revision": "<operator 自訂版本字串,供稽核>",
  "descriptors": [ /* #836 PoolDescriptor 原始 payload,見 parse_pool_descriptor */ ],
  "unit_catalog": [ /* #836 UnitDefinition 原始 payload,可為空陣列 */ ],
  "bindings": [ /* #836 ProfilePoolBinding 原始 payload,見 parse_binding;
                   subject.kind 可為 "profile"/"group"/"identity"(#1116)*/ ],
  "lease_ms": 900000,
  "usage_unit_refs": { "input_tokens": ["token", "1"] }
}

descriptors/unit_catalog/bindings 逐字複用 #836 quota_observation.parse_pool_descriptor/parse_unit_definition/ parse_binding 的既有驗證,本模組不自寫第二套 schema;lease_ms(選填, 預設 900000ms)與 usage_unit_refs(選填,終局 usage metric → unit ref 對照,供 periodic tick 呼叫 record_terminal_usage 用)皆有明確預設,缺席 不視為錯誤。同一份檔案也可加上 #836 collector 用的選填 collector_targets(由 cortex quota observe 解析與驗證),admission 只接受其存在、不解讀內容,因此 admission 與 collector 可共用單一設定檔;其他未知鍵仍一律拒絕。

三態行為:

  • 檔案不存在 → 整條線沒接上,與 #839 落地前逐字相同,任何模式皆不 受影響。
  • 檔案存在但無效(結構錯誤、pool/unit/binding 驗證失敗等):
    • shadow(PSC_QUOTA_ADMISSION_ENFORCE 未開)→ 記一筆錯誤 log,降級為 「沒接上」,不擋派工。
    • enforce(PSC_QUOTA_ADMISSION_ENFORCE=on)→ fail closed:在建立任何 job/worktree 之前回精確等待理由 quota-config-invalid,零 job、零假 job_id(走 #830 非 Job 決策契約)。
  • 檔案存在且有效 → 建構真正的 file-backed DispatchContext (QuotaEventLedger/QuotaReservationAuthority/AdmissionDecisionStore 皆用各自模組預設路徑)。以檔案內容 sha256 digest 快取解析結果;operator 編輯過的檔案下一次讀到的 digest 改變即自動重新解析,不需要重啟 daemon。

Opt-in/rollback:預設(無設定檔)與 shadow(有設定檔但 PSC_QUOTA_ADMISSION_ENFORCE 未開)皆不改變既有派工結果,只多寫 decision receipt 供觀察。要讓額度不足真的擋派工,必須同時滿足「設定檔存在且有效」 與「PSC_QUOTA_ADMISSION_ENFORCE=on」。Rollback 只需移除設定檔或把環境 變數改回非 on,不需要刪除已經寫下的 decision receipt/reservation/ consumption 證據——那些是耐久稽核紀錄,rollback 只改變新決策的政策,不洗 掉歷史。

真正的 #836 provider 觀測來源(另一位同事在做的 collector)尚未接上時, descriptors 對應的 pool 一律回報 unknown;shadow 模式下這只影響 decision receipt 的 assessment 欄位,enforce 模式下 unknown 視為不可行 (AC2)。installed/live canary(在生產環境實際重啟 daemon、驗證真實 provider 讀取與長期運作)仍是獨立的部署 gate,本節只交付到「daemon 進程內 的接線與設定檔消費」。

決策 receipt 的投影欄位(#840)

AdmissionDecisionStore 的 decision receipt 以選填欄位補足觀測、設定與 wait provenance,供 paulsha_cortex.monitor.decision_projection(見上面 cortex status 的 quota_decision 欄位說明)投影使用,缺席(#840 之前寫的舊 receipt)時投影 一律視為 unknown,不臆測成 confirmed:

  • policy_config_revision:這筆決策當時使用的 operator quota-pools 設定檔 config_revision(來自 DispatchContext.config_revision,由 paulsha_cortex/coordinator/manager_daemon.py 從設定檔載入結果帶入)。admit 與 quota-admission-insufficient wait receipt 都會記;quota-config-invalid 沒有可信 revision,維持 null。
  • selected_observation_state/selected_feasible:選中候選當時的額度 observation 狀態(unmanaged/known/unknown)與是否所有綁定 pool 都 sufficient——shadow 模式下候選即使 unknown/不可行一樣會被 admit, 這兩個欄位讓投影面能忠實區分「confirmed 可派」與「shadow 下明知不可行 仍放行」。

這是唯讀投影新增的欄位,不改變 #839 的准入邏輯或 reservation 生命週期。

Binding subject:resolved profile key 精確綁定與 executor+model_id 穩定綁定(#1116)

同一個 builder 身分(例如 codex/gpt-6-luna)在不同卡片會依 launch contract/requirements 解析出不同的 execution profile resolved key (epk:v1:resolved:...,見 execution_profile.profile_key——canonical bytes 含 conditions/requirements 全部欄位)。bindings 過去只能以 subject.kind == "profile"/"group" 逐一列舉 resolved key,卡片 deck 一改、resolved key 一變,binding 就無聲失效(shadow 下變成 observation_state: unmanaged)。

quota_observation.parse_binding() 新增第三種 subject.kind:

{ "kind": "identity", "executor": "codex", "model_id": "gpt-6-luna" }

executor/model_id 是操作者手動填寫的設定事實(不是觀測值),比照既有 group kind 的 group_ref/revision,兩者皆視為必然已知,不套 known/unknown 包裝;未知的第三種 subject.kind 仍一律拒絕(fail closed, 不因為加了 identity 而放寬既有的封閉列舉)。

quota_admission.pools_for_profile()/assess_candidate_quota() 的比對 優先序:resolved profile key 精確綁定(profile/group)> executor+model_id 穩定 identity 綁定。找到精確綁定時只採用精確綁定的 pool/window 集合,不與 identity 綁定的結果合併——同一候選同時命中兩者時 不會被重複計算成兩份 pool 需求,也保留操作者用精確綁定刻意覆寫穩定綁定的 能力。CandidateAssessment.binding_kind("exact"/"identity"/ "none")與 AdmissionDecision.selected_binding_kind(比照 #840 既有的 選填欄位加法模式,缺席視為 unknown,不需要 schema bump)記錄這個候選 『目前是靠哪一種 binding 涵蓋』;decision_projection 的 classification.binding 據此投影成 bound-exact/bound-identity/ binding-missing/unknown 四態,讓 cortex work show/inspect status 能明確區分「這個候選完全沒有任何 binding(binding-missing)」與「有 binding 但目前餘量 unknown」(後者仍由既有 classification.observation 表達,兩者是獨立維度)。

quota_shadow.QuotaShadowService.record_terminal_usage() 同步支援 identity binding(否則 admission 用 identity binding 判定可行、job 終局時卻扣不到 消耗);quota_collectors.load_collector_config()/_find_binding() 刻意 不新增 identity 比對路徑——collector_targets 的 profile_key 是設定檔 逐筆手動填寫、在設定載入當下已經精確比對過一次的 resolved key, _binding_has_profile() 在讀取當下只是重放同一份判定,不是像 pools_for_profile() 那樣要替一個動態候選即時找出涵蓋它的 binding;要讓 collector_targets 也吃到 identity 綁定,需要替設定檔的 target item 另開一個 model_id 欄位(獨立的設定檔 schema 擴充),不是這次重放判定的自然延伸。 identity binding 仍可與既有 profile/group binding 並存於同一份設定檔 (collector 只是不使用它)。

Operator 可用 cortex quota bindings --report(見上面「Quota observation collector」一節)唯讀列出目前派工用到、但現有設定完全沒有 binding 涵蓋的 resolved profile key,取代逐卡手動比對 decision receipt。

對抗審查第四輪修法:mode 隔離、精確反查、provisioning 續租

decision_id_for() 新增必填 mode 參數("shadow"/"enforced"),一併 納入雜湊輸入——同一個 attempt/profile 先以 shadow 觀察、operator 隨後才 把 PSC_QUOTA_ADMISSION_ENFORCE 開成 on 重試時,兩者現在算出不同的 decision_id,各自冪等寫入、彼此不覆蓋;WorkflowRun.quota_admission[persona] 因此能正確反映「這次是 enforced」而不會卡在舊的 shadow 快照。 reconcile_bound_reservations() 也不再以 AdmissionDecisionStore.enforced_admitted() 反查候選——改與 reconcile_reserved_reservations() 一致,直接以 QuotaReservationAuthority.list_by_state("bound", ...) 為出發點;任何一筆 bound reservation 即使對應的 admit receipt 從未成功寫入(IO/衝突/ mode 隔離之前的舊碰撞),也不會對這支收斂掃描永久隱形。

reconcile_reserved_reservations() 的反查依據從「依 attempt_id 的 ordinal 猜測」改成「依 job 建立時記錄的 quota_decision_id 精確比對」——多個 Manager instance 交錯派工時(instance A 對候選 A 的某個 attempt reserve() 後 crash,instance B 改派候選 B 建出剛好同一個 ordinal 的 job),舊實作會 把 B 的 job 誤當成 A 的存活證據。registry.create_job() 新增選填欄位 quota_decision_id(純加法,缺席回 None,向後相容舊版 job/registry 讀取端),只有真的取得 reservation 的候選才會寫入這個欄位(shadow/未受 額度管理的候選維持 None,與完全沒接線時的 job 記錄逐字相同)。

Lease 過期本身仍然永不證明可以釋放(既有設計不變),但 provisioning (worktree/sandbox 建立)耗時可能超過單次 lease:QuotaReservationAuthority 新增 renew()(純加法事件種類,只允許從 reserved 續租,bind() 之後的 續租仍是既有 reconcile(confirmed-alive) 分支的責任,不重疊); _dispatch_workflow_card 拿到 grant 之後立刻續租一整個新的 lease_ms 窗口,同時把 reservation 標進 process-global 的 quota_admission.IN_FLIGHT_DISPATCHES(純記憶體、不耐久,process 重啟即 清空)。reconcile_reserved_reservations() 新增選填 grace_ms(額外寬限, 預設 0 逐字沿用舊行為;daemon 端傳入一整個 lease_ms)與 in_flight (給定時,即使 lease+寬限都已過期,只要本 process 自己知道這筆 reservation 還在 provisioning 就續租而不釋放);兩者皆為次要防線,主要 防線是上面的 provisioning 續租。quota-pools 設定檔的 lease_ms 新增下限 (1 分鐘,低於視為設定錯誤,quota-config-invalid fail closed)——太短的 lease 會讓上述安全網疲於奔命地追續租。

即使有這些安全網,bind() 仍可能在極端情況(例如續租呼叫本身失敗、clock skew)撞上 conflict/reservation-already-terminal(reservation 已被 結束):job 記錄這時已經建立,_dispatch_workflow_card 會把它標記失敗 (不記 provider_outcome,避免誤觸 #825/#826 的 executor backoff 分類—— 這不是 executor 的錯)、留一筆 outcome=wait 的 decision receipt 並更新 WorkflowRun.quota_admission[persona],再讓例外照既有「job 已建立、spawn 前失敗」的 fail-closed 路徑傳播——絕不假稱成功、絕不 spawn。

Execution profile schema/key core

paulsha_cortex.coordinator.execution_profile 是 #849 的純 stdlib execution-profile core。它以 caller 明示交入的 mapping 或 JSON text 解析 descriptor 與單一 requested/resolved/observed record,提供 parse_descriptor()、parse_profile()、canonical_profile_bytes()、 profile_key()、actual_condition_key() 與 actual_condition_missing_fields()。解析結果 immutable,to_dict() 回傳獨立 副本;模組不讀檔案、環境、clock、registry,不啟動 subprocess/network,亦不改 既有 resolver、launcher、workflow、registry 或 CLI。

這個 core 的完整 API、D4 canonical encoding、D5 resource bounds 與 owner/交付 邊界見 docs/execution-profile-schema-core.md。 重點是:

  • v1 嚴格要求 schema_version: 1;缺版本與未知未來版拒收,不猜測 migration。
  • effort 是 descriptor 定義的原生 string/integer/finite number/object/array grammar;新增 model、effort 值或 adapter protocol 欄位新值屬 descriptor-only 擴充,不建立全域產品清單。
  • 三層 record 分開保存;unknown 保留原因,不能補成 observed。not_applicable 只可用於 descriptor 明示 none effort;任何 schema/key 成功都不授予 approved/qualified/permission grant。
  • record key 與 actual-condition key 使用不同 domain;價格、時間戳、provenance、 report/approval reference 不進 capability key。core wire/projection/encoding 改變才需要新 core 版本;adapter protocol 欄位換值沿用 v1 且自然得到不同 key。
  • core 不新增 flags、commands、model/agent/effort 固定清單或 runtime probe;既有 CLI --help 仍是唯一 help contract。

Execution qualification lifecycle

cortex model qualification 將 PatchMUD report v2 綁定精確 execution profile、角色、完整 deck coverage 與來源 digest,建立不可變 candidate;只有技術證據完整且帶明示核可 receipt 的紀錄才能進 approved roster。CAS revision/冪等鍵保護核可與撤銷,legacy 資格維持 unknown。預設 sized-dispatch policy 為 disabled;只有 host overlay 設 qualification_policy.sized_dispatch: enforce 時,Manager 才查詢有效 roster 資格。測試 receipt 不會在 live policy 下生效。

CLI 用法、資料狀態、legacy migration 與 live 驗收界線見 docs/execution-qualification.md。cortex model profile --apply 僅套用封套 profile,不核可 execution qualification。

架構與工作流程驗收

開啟架構/工作流程 HTML · 架構事實與來源 · 呈現資料

這份 HTML 用同一頁呈現 Persona/Deck 規則、Manager 控制、WorkflowRun/Job/Slice 狀態、Monitor 投影,以及 feature-oneshot 七階段的 Card、輸入、產出、gate 與恢復路徑。它是文件驗收介面,不會派工或修改 runtime。GitHub 檔案頁顯示原始碼;clone 後直接用瀏覽器開啟 docs/architecture/architecture.html,不需要 server 或網路套件。

重新產生與驗證:先依 hamanpaul/custom-skills 的 architecture-fact-layer workflow-review profile 執行 semantic/exact HTML gate,再執行 python tests/architecture_browser_review.py --html docs/architecture/architecture.html --output /tmp/architecture-review(需 Playwright/Chromium)。CI 使用同一個 repo HTML 以 file:// 驗證;不以 PNG 代替交付。

Install

需求:Python 3.10+、Git,以及至少一個已安裝並登入的 headless executor CLI(copilot、claude 或 codex)。套件只安裝 Python runtime,不會代裝或登入 executor。

推薦:安裝已發版、可回滾、且已在 Python 3.10–3.13 完整 CI 驗證過的 release(目前 release batch v0.1.9):

pipx install "git+https://github.com/hamanpaul/[email protected]"
cortex --help
cortex --version   # 應印出 0.1.9

也可改用 GitHub Release 附帶的 wheel(.github/workflows/release.yml 會把 exact RC-qualified wheel 附在該 tag 的 Release):

pipx install ./paulsha_cortex-0.1.9-py3-none-any.whl

也可在專案內直接安裝:

python -m pip install .

Development / Edge:安裝 main 最新碼

以下指令追的是 mutable main,會拿到尚未發版、未經 release smoke 驗證的最新變更;除非你明確要測 edge/開發中版本,否則請改用上方的 release 安裝:

pipx install git+https://github.com/hamanpaul/paulsha-cortex.git

新手上手

第一次接觸 cortex,建議照這個順序走:

  • Quickstart:從 pipx install、cortex bootstrap 到第一個 workflow。
  • Upgrade:升級與 F1 pipx 快照過期處理。
  • Rollback:回退到上一個已知可用版本。
  • Troubleshooting:manager degraded、request timeout、systemd、executor、stale venv 快速對照。
  • Concepts:spec、job、slice、work 名詞速查。
  • Admin:cortex service、cortex inspect、cortex request 的日常維運操作。
  • Runbook:把常見事故整理成可執行 SOP。
  • driving-cortex skill:agent 編排 coordinator 視角,對應 issue #177 的 dogfood 派工實務。

安裝 driving-cortex skill

skills/driving-cortex 是此 skill 的唯一來源;Python 套件與 cortex install service 不會安裝它。支援 $HOME/.agents/skills 的 agent 可在此 repo checkout 建立 symlink,讓通用 skills root 發現該 skill:

repo_root="$(git rev-parse --show-toplevel)"
mkdir -p "$HOME/.agents/skills"
ln -s "$repo_root/skills/driving-cortex" "$HOME/.agents/skills/driving-cortex"

之後更新此 checkout 時,symlink 會讀到最新的 skills/driving-cortex/SKILL.md;若 repo 搬移,需重新建立入口。

Usage

需求交付總帳(#845)

refine R01–R14 的版本化需求、acceptance criteria、evidence policy 與 owner 對照在 docs/superpowers/specs/refine-requirements-v1.json; 驗證來源、CompletionRecord、remote closure、#841 loaded-runtime receipt、live receipt 及 crash/CAS 索引語意見需求交付總帳契約。

cortex delivery status
cortex delivery gaps --manifest docs/superpowers/specs/refine-requirements-v1.json \
  --snapshot "$PSC_COORDINATOR_ROOT/evidence/requirement-delivery/source-snapshot.json" \
  --source-root "$(git rev-parse --show-toplevel)" \
  --checkout "hamanpaul/paulsha-cortex=$(git rev-parse --show-toplevel)"

--snapshot 的 cortex/requirement-evidence-snapshot/v1 目前需由外部提供:本 repo 沒有程式 產生它,上例路徑只是建議放置位置,不會有程式寫入;外層 live receipt 同樣沒有 in-repo producer。 status 是最近一次 reconcile 的唯讀投影;gaps 即時重驗但不寫入;reconcile 才以 CAS 更新 Trust Root 登記的可重建索引。命令不派工、不呼叫模型、不 merge、不部署、不改 issue 或關票。installed 證據沿用 #841 receipt,並依 max_age_seconds.installed 以 loaded receipt 的 recorded_at 判定期限(過期為 stale);test 證據只採信 verification contract tests[*].acceptance_ids 綁定該 criterion 且通過的測試;production 不接受任何 waiver。live 證據已固定接上 paulsha_cortex/coordinator/live_receipt_validators.py 的封閉登記表 validator,只認得 deployment-canary qualification.json(沿用 qualification/validate.py)與 #857 cortex/task-memory-live-canary/v1 兩種 receipt kind,兩者都綁定該需求 claim 的 artifact/target;receipt 缺失、kind 不在登記表內或內容不符綁定規則時保留具體 gap, 規則詳見需求交付總帳契約。

10 分鐘上手:cortex bootstrap

乾淨機器或新 workspace 建議先跑一次 bootstrap,把 preflight、service install/start 與基本健檢串成單一入口:

cortex bootstrap --dry-run
cortex bootstrap --instance cortex --repo-root "$(git rev-parse --show-toplevel)"

--dry-run 只會檢查 Python/Git/repo/登入態並預覽後續命令,不會修改任何檔案或啟動服務;正式執行時預設會 install + start,最後附上 inspect status / inspect doctor 的下一步指引。若想順手產生第一個 sample workflow,可加 --sample feature-oneshot --task "demo feature";sample 失敗只會降級回報,不影響 bootstrap 核心步驟成功與否。

  1. 在被治理的目標 git repo 安裝 systemd --user 單元:

    cd /path/to/target-repo
    cortex install service \
      --instance cortex \
      --repo-root "$(git rev-parse --show-toplevel)"

    Installer 會 render/copy units、執行 daemon-reload,並 enable manager service、manager timer 與 monitor service;manager service 透過 WantedBy=default.target 隨 user systemd 啟動。deprecated timer 仍保留,--interval 只調整它的 OnUnitActiveSec;長駐 daemon 的 tick 週期由 PSC_MANAGER_INTERVAL_SECONDS 控制。安裝時不會 start service。兩個 service 都會設定 WorkingDirectory=<PSC_REPO_ROOT>,因此服務執行時不受 cwd 影響。

    若既有 $HOME/.agents/config/paulsha/project-cortex.yaml 已包含其他 workspace, installer 只會在末端追加目前目標,並在替換前建立 project-cortex.yaml.bak-*;既有 project-cortex.yaml 或 model-identities.yaml 無法載入時會拒絕覆寫。若舊 env 記錄的是另一個 HOME 下的 default agents root,請以 --agents-root PATH 明確指定後再安裝。 回寫會以 yaml.safe_dump 重排格式並移除註解,原始位元組保留於同目錄的 .bak-*。 每次 append 遷移會在 config root 留下 project-cortex.yaml.bak-<UTC timestamp>;這是刻意保留的復原素材(spec 要求), operator 可自行依需要清理舊備份。config root 會有一個 .cortex-migration.lock(0600) 用於序列化併發安裝,屬常駐鎖檔,不需清除。沒有任何程式會 glob config root,殘留檔 不影響 monitor/doctor 讀取;若 rollback restore 失敗,錯誤會逐一列出各檔案自己的 backup、遷移前不存在而 rollback 僅需移除,或沒有 backup 且原始內容只留在記憶體 中的 previous,並逐檔記錄 restore 結果。既有 project-cortex.yaml 若是 symlink, 只有在此次 install 需要 append/replace、確實會改寫 project config 時才會 fail-closed,明確指出該路徑並保留 symlink 與其 target 不變;若目標 workspace 已存在、 install 不需改寫 project config,則保留 symlink 正常完成。本段 append-only 說明只涵蓋 一般檔案的改寫。

  2. 啟動 manager 並分別檢查 service/runtime 狀態:

    systemctl --user start cortex-manager.service cortex-manager.timer
    systemctl --user status cortex-manager.service
    cortex status | jq

    可在 $HOME/.agents/core/runtime/cortex-manager.env 加入 operator 設定;installer 重跑時會保留非 managed keys:

    PSC_MANAGER_EXECUTOR=codex
    PSC_MANAGER_INTERVAL_SECONDS=300
    PSC_MANAGER_RECENT_DONE_WINDOW_SECONDS=86400
    PSC_MANAGER_MAX_LOAD=10.0
    PSC_MANAGER_REQUIRE_IDLE=1
    • PSC_MANAGER_MAX_LOAD/CLI --max-load(#819):控制 periodic tick runner 允許的最大 load1 門檻(正有限浮點數)。超過門檻時略過本輪 tick 並推進 last_tick_monotonic(避免 hot loop)。
      • 預設行為變更:periodic runner 預設由固定 1.0 改為 CPU-aware(max(1.0, (os.cpu_count() or 1) * 0.5))。若需保留既有 1.0 門檻,可顯式設定 PSC_MANAGER_MAX_LOAD=1.0。
      • 優先序:CLI --max-load > env PSC_MANAGER_MAX_LOAD > CPU-aware 預設。
      • 非法值處理差異:CLI 的顯式非法值(如 NaN、Inf、0、負數或非數值)直接由 argparse 報錯拒絕(exit 2);env 若為 unset、空字串、無法解析、NaN、正負 Inf、零或負數,則安全回落到 CPU-aware 預設,不得使非法值進入 probe。
      • 限制:os.cpu_count() 不反映容器 cgroup CPU 配額;容器環境應顯式配置合適門檻。manual tick request lane 預設仍維持 1.0(不受本項變更影響)。不能以 bypass 模式的 idle=True 證明自然負載低於門檻。
    • PSC_MANAGER_REQUIRE_IDLE/CLI --no-require-idle(#819):控制 periodic runner 是否探測主機負載。PSC_MANAGER_REQUIRE_IDLE 經 strip().lower() 為 0/false/off/no 時停用 idle 檢查,其餘值、unset 或空字串皆預設為 True(啟用檢查)。CLI --no-require-idle 優先停用(default_require_idle() and not args.no_require_idle)。

    PSC_JOB_RUNNER(trust-root Phase 2 降權啟動器):預設 direct=headless job 與 Manager 同帳號執行(現行行為)。設為 systemd-run 後,builder persona 的 job 改以 systemd-run --uid=cortex-builder 的 transient unit 執行,只帶白名單 env(不含任何 token)。這個開關要在 cortex-builder 帳號與 polkit 規則就位之後才可打開, 步驟見 docs/superpowers/runbooks/trust-root-transactional-install.md;歷史 Phase 2b 手工命令已標記為不可執行。相關選配變數 為 PSC_BUILDER_ACCOUNT/PSC_BUILDER_GROUP/PSC_BUILDER_HOME。

    systemd-template 是定案路徑(0816 第三輪裁決 B 案),涵蓋面比上一段廣: builder(#603/#584)、reviewer(#615)、gate(#629)、以及 define/brainstorm 的 planning(#672 票 A~F/#682-#687)四條執行路徑全部改經 root-owned 模板 unit。 最後一條是 #687 才接上的:在那之前 planning 走 planning_runtime 而非 SubprocessLauncher,PSC_JOB_RUNNER 對它完全沒有效果,模型 CLI 就在 Manager 行程內以 cortex-manager 執行(該帳號的 passwd 註記逐字寫著 no model code)。 systemd-run(A 案)對 planning fail-closed,不退回行程內執行。 PSC_BUILDER_PATH 不是選配(#679):它與 PSC_REVIEWER_PATH/PSC_GATE_PATH 同為必填,未宣告時 Manager 在派工前即 fail-closed。值由產生器導出,不要手打: python3 -m paulsha_cortex.trust_root unit four-way --job | grep '^Environment=PATH='。

    Codex 0.157 的 Trust Root 沙箱邊界(#716 選項 B):只有 launcher 成功完成 systemd-template preflight、確定本次命令會由 root-owned job template unit 執行時, Codex argv 才使用 --sandbox danger-full-access 並省略 legacy Landlock 旗標。 0.157 在該 unit 下啟用 legacy Landlock 會因 app-server socket 隔離需求而 panic;停用 legacy 則預設 bubblewrap 因 namespace 權限被拒。此路徑的唯一安全邊界是外層 systemd unit(含 RestrictNamespaces=yes 等既有加固及精確 ReadWritePaths)與既有 egress proxy;不因停用內層沙箱而放寬任何 unit 鍵值。direct 與 transient systemd-run 沒有同一份 template 加固面,仍使用原先依卡片契約導出的內層 sandbox argv。

    #823 headless session 的生命週期邊界:每個合法 headless Popen 嘗試(含 systemd-run/systemd-template 的 Manager-side client wrapper 與窄 stdin retry)都帶 start_new_session=True。direct child 因此有自己的 POSIX session/process group;這是 setsid 隔離,不是 systemd cgroup 移動,也不改變 unit 的 KillMode 或提供 cgroup restart survival 保證。Manager daemon restart、timeout/parser/CLI 值域(#824)與 AGY probe containment(#851)仍是獨立工作項;本 session 修正不新增 cancel/timeout/probe 語意,也不宣稱這些 issue 已完成或關閉。

    #498 Claude headless builder 的 steering 邊界:Claude builder 以 -p 執行時不附加 --remote-control;執行中的 headless job 不支援即時 steering。spool 保留的 steering 事件不代表指令已送達或被採納。

  3. 使用 Deck 先 dry-run,再 emit dispatch: hold specs:

    cortex deck compile feature-oneshot --task "example feature" --change example-feature
    cortex deck compile feature-oneshot --task "example feature" --change example-feature --emit

    若只是想先建立第一個 sample workflow,可直接走導引式包裝:

    cortex init-sample --task "example feature" --change example-feature
  4. 完成 interactive checklist,將 spec 的 glob plan 改為確切路徑,補齊 target_branch / verification 後才翻成 dispatch: auto,再執行一次完整 tick:

    cortex ready --specs-dir "$HOME/.agents/specs" | jq
    cortex tick \
      --specs-dir "$HOME/.agents/specs" \
      --executor codex \
      --model "<builder-model-id>" \
      --review-executor claude \
      --review-model "<reviewer-model-id>"
  5. Project Monitor 是選配功能。啟動前先建立 $HOME/.agents/config/paulsha/project-cortex.yaml;workspace path 應指向「包含各 repo 的父目錄」:

    workspaces:
      - name: projects
        path: /path/to/workspace
    monitor:
      poll_interval_seconds: 60
      rescan_interval_seconds: 300
      legacy_policy: hide
    cortex monitor --once | jq
    systemctl --user start cortex-monitor.service
    systemctl --user status cortex-monitor.service
  6. 日常查詢:

    cortex --version
    cortex status | jq
    cortex jobs
    cortex stat "$JOB_ID"
    cortex list --state todo --explain
    cortex doctor --probe-live --repo owner/repo --json

    新的 Work Item read model 使用 topic → todo → on-going → done 四態;日常操作、override、snapshot/registry migration 與 delivery gate 詳見 Unified Work Lifecycle 操作與遷移。所有工作預設 manual,只有 confirmed Todo 對應到帶 cortex:auto-on-going label 的 confirmed issue 才能 auto claim。

  7. 追蹤已送出的 control request(唯讀、不經 control queue):

    cortex request list --json
    cortex request show "$REQUEST_ID"
    cortex request wait "$REQUEST_ID" --timeout 30
    cortex request logs "$REQUEST_ID" --json
  8. 用 inspect 家族直接檢查既有 runtime / registry / monitor 快照(唯讀、不經 control queue):

    cortex inspect status --json
    cortex inspect job "$JOB_ID"
    cortex inspect ready --json
    cortex inspect work porcelain-inspect --repo hamanpaul/paulsha-cortex
    cortex inspect doctor --json
    cortex inspect service --json
    cortex inspect models --json

    cortex inspect models 顯示每個 model identity × persona 的能力封套投影: 四欄位值、逐欄來源(measured=patchmud 實測、default=保守預設窗口)與 實測 provenance(deck、deck content hash、patchmud 版本、評測時間)。

    模型能力封套的實測值由選配的評測巷道產生(沒有 patchmud 的機器一律走保守 預設,行為與未評測時相同):

    cortex model profile            # 偵測 patchmud、跑 deck、產 diff 預覽(不寫檔)
    cortex model profile --apply    # 人工複核後把實測封套寫入 packaged registry 檔
    cortex model profile --force    # 忽略評測指紋強制重評

    評測指紋為 (executor, model_id, persona, deck_id, deck content_sha256, patchmud version);指紋未變時重跑會回報 already-profiled 直接跳過。 patchmud 目前僅有 anthropic adapter,roster 內只有 claude/sonnet 可被驅動; 其餘身分會逐一回報 adapter-unavailable 並誠實維持預設封套。

    PatchMUD report consumer 僅將 schema v2 完整 cohort identity (role、benchmark_type、Cortex execution_profile key、deck_digest、 evaluator_revision) 對應到榜列;model/loadout 不再作為查詢鍵。Report v1 按上游遷移契約只可 opaque 保留、不可排名;未知版本拒絕。詳見 PatchMUD report v2 consumer。

    cortex inspect models 另顯示每列的 layer=,即該身分在三層解析鏈中的位置 (operator-overlay/evaluated-roster/packaged-fallback/parked)。

    升級遷移註記:packaged roster 已收編 copilot/gpt-5.4、 claude/sonnet、codex/gpt-5.3-codex-spark、cg/glm-5.2 四個身分。 host overlay($PSC_PROJECT_CONFIG_ROOT/model-identities.yaml)宣告其中 任一鍵時,以 overlay 為準(人工指定優先,見下方三層解析鏈);建議在 該列加上 override_packaged: true 明示覆寫意圖,否則 cortex doctor 會 提出警示。(此處先前為 fail-closed 中止載入,已於 #534/#509 改為降級。)

  9. 用 service 家族管理 installer、systemd runtime 與 fallback logs:

    cortex service install --instance cortex --repo-root "$(git rev-parse --show-toplevel)"
    cortex service start --instance cortex --json
    cortex service ensure-running --instance cortex
    cortex service status --instance cortex --json
    cortex service logs --instance cortex -n 50
    cortex service uninstall --instance cortex --purge --json

    自動化呼叫可用 cortex service ensure-running:若 manager.lock 已由 live manager 持有,就不重啟並回報 mode=already-running;systemd user units 齊備且可用時會啟動 manager service/timer 與 monitor service,並等待 manager lock 最多 10 秒;否則以目前執行中的 Cortex Python 啟動本地 manager 與 monitor。此命令固定輸出一行 cortex-porcelain/service/v1 JSON,fallback log 寫入 manager.log,不會安裝或修改 units。

    cortex service status 會先讀 systemd units 與 bootstrap env,若尚未安裝但偵測到前景 paulsha_cortex/scripts/service-manager.sh lock,則回報 fallback mode 與 log path;cortex service logs 會優先走 journalctl --user,否則回退讀 $HOME/.agents/log/manager.log。只有 systemd mode 支援 --follow 即時串流;fallback mode 會顯性拒絕並要求直接 tail log 檔。 cortex service status --json 另含 loaded_runtime:分開列出執行 status 命令的 operator_cli、磁碟上的 service_declaration,以及 Manager/Monitor 啟動時實際載入的 artifact/config receipt。只有 process PID、artifact 與可比對配置都能核對時才回報 match;磁碟更新會顯示 drift,缺 receipt、source override 或無法證實的欄位維持 unknown。cortex doctor --json 使用同一份安全投影。欄位與 live 驗收界線見已載入 runtime 身分證據。 cortex service install 寫入 unit 後,若 daemon-reload 或 enable 任一階段非零,會直接回報 mode=systemd、非零 exit code,訊息僅包含 systemd stderr、unit 落檔位置、重試 command(systemctl --user ...),並明確指出「unit 已寫入但僅 reload/enable 尚未完成」,不會輸出 traceback 或 stdout 內容,並不再繼續後續步驟。

  10. 用 run 家族提交高階 mutation;不加 --wait 時會回傳 request ID 並以 exit 3 表示 accepted-pending:

cortex run tick --specs-dir "$HOME/.agents/specs" --wait
cortex run fanout --executor codex --model "<builder-model-id>"
cortex run complete --review-executor claude --review-model "<reviewer-model-id>" --wait
cortex run work resume porcelain-run-recover --repo hamanpaul/paulsha-cortex --actor operator
cortex run work start per-work-model-chain --repo hamanpaul/paulsha-cortex \
  --builder-executor codex --builder-model "gpt-5.3-codex-spark"
cortex run work retry-card "$WORK_ID" --repo "$OWNER/$REPO" \
  --expected-run-id "$WORKFLOW_RUN_ID" --card "$CARD_ID" \
  --builder-executor copilot --builder-model "gpt-5.4"

--executor/--model 省略時由 daemon 採用部署設定;帶 --wait [--timeout N] 時成功為 exit 0、terminal failure 為 exit 1、逾時仍為 exit 3。所有 queue mutation 都可加 --json 取得 cortex-porcelain/run/v1 輸出。 若 spec frontmatter 成對宣告 optional executor/model_id,fanout/tick 會逐 slice 覆寫這裡的 builder 預設值;命令列明確指定與 spec frontmatter 宣告的 (executor, model_id) 都會先查 model-identities.yaml,unknown identity 直接 fail-closed 並列出可用 candidates。 run fanout/run tick 的 JSON 結果固定帶 dispatch_skipped_by_backoff;若 dispatch request 或 slice-action retry-build 因 durable executor backoff 沒有派出 job,也會回同名欄位而不是造假 job_id。元素若是已知 cooldown,形狀正好是 {"slice_id","executor","model_id","retry_after_epoch"};若 store 狀態未知,則改為 retry_after_epoch: null 並帶 reason(目前為 executor-backoff-store-unknown)。unknown 只代表 durable backoff state 無法可信判定,不代表 quota/額度已可用。 cortex run work start/resume/... 額外支援 --planner-executor/--planner-model/--builder-executor/--builder-model/--reviewer-executor/--reviewer-model:run-scoped 覆寫該 work item 這次 claim 的 planner/builder/reviewer 模型鏈,三段各自獨立、未指定的段落回退共享 model-identities.yaml。覆寫只影響這個 run,不改共享設定檔、不影響其他 active run 尚未派出的 card;於 claim(或首次 dispatch)時凍結,之後 resume/retry 沿用凍結值。指定的 identity 仍須通過既有 capability 與 builder/reviewer independence domain 檢查,違反時 CLI 直接回報錯誤原因並列出可用 identity,不會靜默退回共享預設。 run work 的 --card 用於 retry-card 與 regenerate-gates:前者需搭配 exact 活動 WorkflowRun ID 與該 run 的待派 card;後者選擇要重跑 gate 的 build 卡,若 run 有多張符合條件的 build 卡就必須指定,單卡時省略仍相容並取該卡最新 job。retry-card 帶 run-scoped identity override 時,Manager 會在 reset 前依既有 capability/independence 判準驗證;拒絕的 override 不會寫進 run。retry-verify 在同一個 work-action request reset 後直接派出新的 verification job;符合 #577 精準復原條件的舊 reviewer job 仍保留 exited 狀態。--payload 可用來傳遞各 action 原本支援的其他欄位,不是選擇 retry card 的必要輸入;retry-card 仍拒絕既有 allowlist 以外的 caller fields。 每張卡最多接受三次 retry-card 重派;超限會維持 needs_human 並在阻塞理由標示卡片,提示 abandon 或符合條件時使用 retry-build。

  1. 用 recover 家族執行受限復原;slice/work mutation 的 --actor 為必要審計欄位:
cortex recover slice "$SLICE_ID" retry-build --actor operator --wait
cortex recover work porcelain-run-recover resume --repo hamanpaul/paulsha-cortex --actor operator
cortex recover brokers reap --apply --cwd-root "$(git rev-parse --show-toplevel)"
cortex recover service restart --instance cortex

recover 不提供 --allow-unsafe 等旁路旗標;需要專家級低階參數時仍使用既有 coordinator 命令。

  1. 用 digest 家族把 attention/ready/held/degraded/recent_done 快照組裝成排程可推播的 digest(供外部 timer/cron 呼叫,取代純拉取式監控):

    cortex digest emit
    cortex digest emit --json

    預設投遞方式是無外部依賴的檔案 outbox:寫入 <coordinator_root>/digest/outbox/<timestamp>-<random>.json(--json 輸出的 delivery.path 即落檔位置)。設定 PSC_DIGEST_DELIVERY_CMD(typed argv,比照 PSC_PREFLIGHT_CMD 慣例)後,改把 digest JSON 從 stdin pipe 給該命令,兩者擇一、不 fallback——命令非零 exit 或逾時(預設 10 秒)會直接回報錯誤並以非零 exit code 結束,不會靜默改寫檔案 outbox。cortex digest 本身不建立 systemd timer;要排程執行請在既有 cortex-manager.timer 之外另建一個 OnCalendar/OnUnitActiveSec timer 呼叫 cortex digest emit(或由既有 cron 呼叫),部署層自行決定頻率。

cortex status 查 manager 的工作與 gate 狀態;systemctl --user status 只查 service 是否存活,兩者不可互相替代。mutation request 目前採分級 timeout:fanout / tick 為 60 秒,complete / work-action 為 30 秒,其餘為 5 秒;timeout 後 daemon 可能仍在工作,請回到 cortex status 或 cortex request show <request-id> 查證。

從使用者角度操作

Cortex 管的是什麼?

Cortex 不是 Jira / Notion 式的任務資料庫,而是以檔案為主的 Agent 派工與交付治理平面。使用者會接觸四種不同層次:

層次 代表什麼 主要觀察方式
Spec 任務意圖、plan、相依、驗證契約 ~/.agents/specs/*.md / cortex ready
Job 一次 Agent process 執行嘗試 cortex jobs / cortex stat
Slice 從 build、verification、review 到交付的生命週期 cortex status 的 slices / attention
Project Monitor 從 GitHub、Todo/spec/plan、active OpenSpec 與 workflow registry 投影跨 repo Work Item cortex list / cortex work show

Project Monitor 不會代替 coordinator 狀態:前者提供 topic/todo/on-going/done read model,後者仍是 workflow 的唯一 writer。可用下列 read-only CLI 查詢;同一 work-id 若跨 repo 重複,show 必須加 --repo:

cortex list --repo hamanpaul/paulsha-cortex --state on-going --explain
cortex work show unified-work-lifecycle --repo hamanpaul/paulsha-cortex --json
cortex work show task-memory-delivery-adapter --repo owner/repo --task-memory --json

--task-memory 以 cortex/task-memory-read-model/v1 回查 Work Item、WorkflowRun、Job routing、planning revisions、receipt sidecar 與 test/review gate evidence;它只讀取既有紀錄,不會啟動 memory retrieval。未帶旗標時仍輸出 cortex-work/v1。Cortex 透過可選 hippo task-memory CLI subprocess 讀取;不 import Hippo、不新增 package dependency,也不讀全域 memory root。Manager retrieval 預設關閉,只在 service 的 per-instance manager overlay($HOME/.agents/core/runtime/<instance>-manager.env)設 PSC_TASK_MEMORY_ENABLED=1 才會於派工時請求 bounded inline excerpt。啟用時必須以 PSC_TASK_MEMORY_HIPPO_CMD 明示 Hippo CLI 命令(JSON argv array 或 shlex command,第一個元素必須是絕對路徑);未設、空白字串或非絕對路徑一律視為 provider 缺席,不會對 PATH 做搜尋(issue #857 對抗審查修復:避免 PSC_TASK_MEMORY_ENABLED=1 意外執行 PATH 上第一個叫 hippo 的任意 binary)。此命令屬 operator 明示設定的可信輸入:Cortex 不替它做 PATH 搜尋,但若 operator 自己在 argv 中使用 /usr/bin/env 等間接啟動方式,即由 operator 承擔其解析結果。PSC_TASK_MEMORY_HIPPO_TIMEOUT_SECONDS 可設 0.05–60 秒,預設 10 秒。stdin 上限 64 KiB、stdout 上限 512 KiB。命令缺席時留下 provider-unavailable receipt;Hippo exit 10/11 分別為 permission-denied/provider-timeout。exit 12/13/14/15 分別記為 scope mismatch、content hash mismatch、unsupported schema park、manifest mismatch;16/17/18 統一為 provider-error,並在 receipt 只保留 bounded code。stderr 只檢查第一行 allowlisted code,其他內容不進 log 或 receipt。所有 failure 都不改 workflow lifecycle;receipt 只寫 Cortex sidecar,不寫 Hippo ledger。candidate 的 applicability/relevance_reason 屬 provider 回傳的自由文字,receipt 只保留其 SHA-256 摘要(applicability_sha256/relevance_reason_sha256),不持久化原文;source_time 只在可解析為受界長度 ISO8601 時原樣保留,否則記為 unknown;content_version 只接受 sha256:<64 hex> 或 ≤64 字元、無空白/控制字元的版本 token,否則整份 payload 以 manifest mismatch 拒收(issue #857 對抗審查修復:避免有缺陷或惡意的 provider 回應把 note 正文或內部診斷訊息帶進 Cortex sidecar 與 cortex work show --task-memory)。

「使用」層的回報管道(#1136):只有 build/verify/review 卡、且本 attempt 確實交付了 note 時,派工 prompt 的 task-memory 區塊才說明選填 terminal 欄位 task_memory_applied(至多 3 筆 {"note_id", "evidence_ref", "evidence_sha256"},只有依 note 內容實際改變產出時才回報);Claude/AGY reviewer 的工具 schema 同步開放此屬性並維持 Gemini 相容子集。Manager 在採信 terminal(canonical evidence 綁定成功)之後,以本 attempt 的正式 Work Item/Run/card/Job 邊界重建 task context,並從 receipt sidecar 重建本 attempt 已 offer/已交付的 note(不另存 prepared context、不含 note 內容),逐筆呼叫 record_applied(),再經 record_task_memory_receipt() 寫入 applied-with-evidence:evidence 必須是 repo 相對路徑且 sha256 相符——builder 同時比對本 attempt candidate commit(git cat-file blob <candidate>:<ref>)與工作區檔案,reviewer 以審查對象(workflow_repo_root)比對。欄位畸形、note 未交付或屬於別的 attempt、evidence 不在 candidate、hash 不符一律只留 class-only warning,不改卡片 lifecycle 與 gate 結果;欄位在所有既有 terminal 形狀判定之前就被拆出,未帶欄位時行為不變。applied receipt 的 counts_as_read 恆為 false,不進 Hippo legacy strict KPI 分母。三分帳號部署下 Manager 讀不到 builder 工作區,builder 的 applied evidence 目前會被拒收(只留診斷)。

正式 live 驗收可對至少兩個已登記 Hippo project 一次跑完三條 capability path、permission-denied 與 cross-project 負例:

cortex task-memory canary --repo owner/project-a --repo owner/project-b --runs 5 \
  --target-file acceptance-target.json \
  --evidence-path "$PSC_COORDINATOR_ROOT/evidence/requirement-delivery/live/task-memory-canary-857.json"

--evidence-path 是選填;省略時只輸出機讀 JSON,不在 repository 建立預設 evidence 檔。要把產物當 #845 live receipt 的 evidence 時,請寫到 Manager coordinator root(delivery evidence root)底下,並以 --target-file 提供該需求的 acceptance target(恰為 repo/candidate_sha/artifact_sha256/source_revision/service/instance/profile_key/config_revision 八欄的 JSON 物件,格式錯誤在量測前就以 exit 2 拒絕);未提供時輸出 target: null,validator 不會採信。回傳 0 要求每個 repo/path 至少五次 eligible authorized provide 且至少五次成功 delivery,provide 與 note 成功率都至少 95%;預設 --runs 5 即每個 repo/path 5/5。

輸出是 cortex/task-memory-live-canary/v1,形狀以需求交付總帳契約的 live receipt 登記表為正本,可原封不動放進 cortex/live-canary-receipt/v1 的 evidence:paths 的 key 是 delivery 結果 context-delivered/snapshot-ready/note-fetch(mode 欄標明對應的 inline/snapshot/note_fetch capability),每列有 attempts/successes/success_rate(eligible authorized candidate 層級)及 provide 次數、per-repo、per-task-kind 明細;negative_controls 列出 permission-denied 與 cross-scope-rejection 兩個案例,cross_project 逐 repo 列出是否通過,executor.id 是 canary 工具名加執行帳號 uid。合成卡片的 task kind 依序輪替 build/verify/review,每條 path 至少要在兩種 task kind 取得成功。canary 另以實際觀測判定 blocker:provider 覆寫 Cortex 轉交的 task id 或 delivery mode、或 Manager 正式 composer 組出的 executor prompt 沒有逐字保留原 prompt,記 relay-overwrite;provider 回應是 unsupported schema major、非 JSON 或結構不符,記 legacy-output-schema-break;receipt 的 counts_as_read 偏離 legacy strict Read 定義(只有 note-fetch 的 content-returned 是 Read),記 legacy-strict-kpi-mutation 並使 legacy_strict_kpi_mutated=true。任一 blocker 都使 passed=false。輸出不含 note 正文或本機絕對路徑。Manager production dispatch 目前只使用 inline capability(owner 2026-09-29 裁決此範圍可接受);canary 另以真 CLI 實際驗證 note-fetch 與 snapshot。若 service 以 cortex-manager Trust Root 帳號執行,Hippo memory root 權限仍由 Hippo registry 與 OS 判定;Cortex 不提升權限。

Monitor 只允許 override、frontmatter、GitHub closing reference 與通過typed refs驗證的workflow metadata提供 confirmed association;override exclusion 優先抑制所有同work的 confirmed edge。PR body、issue title、artifact/branch slug 等 fuzzy 訊號只顯示。未被 confirmed mapping 擁有的 archived OpenSpec 與 closed GitHub issue/PR 只提供終態證據,不會單獨建立 work item。done 的 Todo 證據只採遠端 default branch blob與 archived OpenSpec task checklist revision,不採本機 overlay;Monitor 仍要求可驗證的遠端 Todo evidence,但 workstream Todo 未勾 checkbox 只供診斷,不阻擋有效 CompletionRecord 的 done;archived OpenSpec tasks 的完成要求維持不變;所有 mapped PR 都必須是至少雙 parent且可證明已進 default branch 的 merge commit,且 CompletionRecord 保存的 source revisions、PR candidate與merge revision必須逐一符合目前remote truth;所有 mapped OpenSpec refs 也必須完成 archive。GitHub 或其他 authority provider degraded/超過 provider_stale_after_seconds 未成功更新時,會保留 last-good state並加上 degraded facet;cortex-work/v1.hard_gates只依查詢中的repo/work item authority關閉auto claim與merge,跨repo整體狀態另由fleet_health回報。

Monitor 採 last-good 語意:workspace 或 project subtree 暫時無法讀取時,既有項目會保留並帶 degraded scan signal,不會發布 removal;只有後續成功掃描父層、確認項目真的消失時才移除。poll_interval_seconds、rescan_interval_seconds 與 watch_debounce_ms 必須全部大於零,錯誤設定會在 service 啟動前直接失敗。

1. 建立任務

先用 Deck 把一個工作流編譯成 slice specs:

cortex deck list

# 先 dry-run 預覽
cortex deck compile feature-oneshot \
  --task "example feature 實作" \
  --change example-feature

# 確認後寫入 ~/.agents/specs/
cortex deck compile feature-oneshot \
  --task "example feature 實作" \
  --change example-feature \
  --emit

Deck 產生的 spec 預設為 dispatch: hold,不會立即派工。翻成 dispatch: auto 前,使用者應:

  1. 完成 Deck 列出的 interactive checklist。
  2. 確認 plan、depends_on、target_branch 與 verification 契約,並依 auto dispatch 契約文件 補齊欄位。
  3. 執行 Deck 列出的 verify commands。
  4. 只將允許 manager 派工的 spec 改為 dispatch: auto。

不想直接操作 deck compile --emit 時,可改用:

cortex init-sample --task "example feature 實作" --change example-feature

init-sample 會維持 dispatch: hold,並直接列出 spec 檔名、plan / target_branch / verification 必補欄位,以及 deck verify 下一步指引。

Manager 只會派送「已翻 auto、有 plan、相依全部 completed」的 slice。不要使用低階 cortex dispatch;它因缺少 spec / verification metadata 已停用。

2. 派工與執行 gate

先確認 ready set,再明確指定 builder 與 foreign reviewer:

cortex ready --specs-dir "$HOME/.agents/specs" | jq

cortex tick \
  --specs-dir "$HOME/.agents/specs" \
  --executor codex \
  --model "<builder-model-id>" \
  --review-executor claude \
  --review-model "<reviewer-model-id>"

tick 會依序處理 ready fanout、既有 Job 輪詢、deterministic verification、必要的 foreign review 與 completion 判斷。--executor/--model 是整批 builder 預設值;spec frontmatter 若成對宣告 executor/model_id,會逐 slice 覆寫且沿用同一套 commit-required/approval-safety 語意。命令列明確指定與 per-slice 覆寫的 (executor, model_id) 都必須存在於 model-identities.yaml,才會真正進 executor argv。不要在一般操作加入 --allow-unsafe;它會旁路 executor approval/sandbox,且只允許單一 ready slice canary。AGY 會將此旗標轉成 --dangerously-skip-permissions;其權限剖面尚未依 #716 逐 executor 量測,應視為 operator 明確承擔的高風險 opt-in。AGY builder prompt 也要求測試與長命令直接在前景同步執行並等待完成,不得交給背景任務。

若 fanout 發生 dispatch 例外,tick 回傳仍包含 completed 與 dispatched,並在 errors 按 slice 回報 slice_id、type、message,讓上層在部分派送成功時仍可見完整狀態。summary 也固定帶 dispatch_skipped_by_backoff:已知 cooldown 會給 retry_after_epoch,store unknown 只會給 reason 與 retry_after_epoch: null;unknown 一律視為「暫時不能可信派工」,不是 quota 已恢復的訊號。

Daemon log 的每筆輸出則會以 ISO-8601 時間戳作為欄位首位,便於 journalctl / manager.log 直接過濾。

3. 觀察任務狀態

# Manager 綜合狀態:ready / held / in-flight / slices / attention
cortex status | jq

# Job 執行歷史與單筆詳情
cortex jobs | jq
cortex stat "$JOB_ID" | jq

# 只列出目前可派送的 specs
cortex ready --specs-dir ~/.agents/specs | jq

# 從專案文件重新推導專案進度
cortex monitor --once | jq

# 確認長駐服務存活
systemctl --user status cortex-manager.service cortex-monitor.service

cortex status 是日常主入口:

  • ready:已滿足派工條件。
  • held:尚未可派工,並列出 no-plan、dispatch-hold 或未滿足的 dependency。
  • in_flight:正在執行的 Job,含 candidate_git_base(見下)以及由 registry job 實際綁定的 executor、model、job_id、card、identity_source 與 execution_state。workflow 的 attention/recent_done 也使用同一組身份欄位: identity_source 為 in-flight、last-execution、planned 或 unknown;沒有 registry 證據時不從 phase/persona 推測 executor 或 model。 若 job log 超過 30 分鐘未更新,條目會附 last_progress_at/progress_age_seconds;job 仍保持 in-flight,並在 attention 以 reason: stale-in-flight 提醒檢視。Cortex 不會自動中斷該 job。 下游可直接使用去識別化 producer snapshot fixture tests/fixtures/workflow-execution-identity-828-status.json;欄位與 selection 語意見 docs/superpowers/specs/workflow-execution-identity-producer-contract.md。
  • verify job 的 log 若含 status: ERROR 的 provider attempt 訊息,且 Manager 已綁定 成功 evidence、run 的 Candidate 與 verified_head 相同並將該卡標為 passed,in_flight/ attention 會附 accepted_workflow_results,cortex jobs 會附 workflow_result,顯示 verified 與 recovered_provider_errors。這是呈現註記,不覆寫 Job 的 process status、原始 log 或 usage;沒有 Manager 採信結果時不會投影成成功。
  • slices:交付生命週期、gate、Candidate 與 evidence 摘要。builder/reviewer 失敗若已有 durable provider_outcome,gate_reason 會帶具名後綴(例如 builder-failed-rate_limited、 builder-failed-effort_not_supported、foreign-review-provider-launch_failed),而不是把所有 provider 層與 launch 層失敗壓平成同一句;每筆也帶目前的 binding_revision,供單筆 supersede action 做 exact CAS。
  • attention:needs_human 項目,包含 reason、當下合法的 next_actions,以及 candidate_git_base。workflow job 的 provider 類失敗會另外投影 provider_outcome 與 provider_outcome_authority;runtime-contract-failed 仍與 provider 分類分層,不會被 exit 127 或關鍵字比對覆蓋。building slice 若沒有目前綁定的 in-flight builder job, complete/status reconcile 會將它轉成帶 DiagnosticReason 的 needs_human。另外,job log 超過 30 分鐘未更新時會出現 kind: job/reason: stale-in-flight,附最後進度時間及檢視提示。
  • Manager 正在處理 ship 控制請求(包含 preflight)時,status 會附 busy: true 與 activity, 顯示 request、action、repo/work item 與活動檔最後寫入時間。此狀態只表示 Manager 有近期活動, 不代表任何 preflight 或交付 gate 通過;活動檔超過 30 分鐘未更新仍顯示 stalled。
  • Manager daemon 預設每 3 秒輪詢;沒有 queued request、in-flight job 且 periodic tick 未到期時, 間隔有限遞增至最多 10 秒。發現 request、in-flight job 或執行 periodic tick 時,間隔回到 --poll-interval 設定值。
  • candidate_git_base(#731):這條 run/這張卡的候選 git base——真正那個 40-hex commit SHA,以及它落後 mirror 上 origin/main 幾個 commit。欄位含 sha、sha_source(frozen-readiness-base-sha 或 first-build-job-dispatch-head)、behind_origin_main、mirror_origin_main、threshold_commits、reason、measured_against、fetched。
    • 與 source_revision 是兩件事:source_revision 是 work item 來源材料的 sha256(authority digest,64-hex),與 git 無關、也不隨 origin/main 前進而改變。過去候選基底只存在於候選 worktree 的 .git 裡,operator 只能 git -C <候選 worktree> rev-parse HEAD 才問得到,於是把診斷掛在 source_revision 上而誤判。
    • 落後達 threshold_commits(預設 10,可用 PSC_CANDIDATE_BASE_STALE_THRESHOLD_COMMITS 覆寫)時,reason 為具名診斷 candidate-git-base-stale——代表「這條 run 的基底過舊、已 merge 的 test-only 修復進不去」。
    • 距離是相對 mirror 上次 fetch 到的 refs/remotes/origin/main 算的,不是相對 GitHub 此刻的 main:status 是唯讀路徑,fetched 恆為 false(fetch 是 claim 的職責)。讀不到 mirror/算不出距離時,behind_origin_main 落 <unresolved:MirrorRootUnset>/<unresolved:MirrorMainUnreadable>/<unresolved:BaseNotInMirror>,reason 為 candidate-git-base-distance-unresolved;run 還沒有基底(define/plan 階段)時 reason 為 candidate-git-base-absent。
    • 同一份資料也出現在 cortex work show <work_id>(文字模式與 --json 皆有)。
  • quota_decision(#840):quota-aware admission(#839)決策與額度等待來源的唯讀投影,只在這條 run 真的有 #839 證據時才出現。欄位含 wait(額度等待——非 Job 決策,例如 quota-admission-insufficient/quota-config-invalid,直接沿用 #527 blocking_reason 的 reason/detail/next_step_hint,不自行編造;context 只帶出 quota-wait producer 寫入的 run_id/work_id/card/attempted_candidates,其他 key(例如帳號、token、raw prompt)一律不轉發;另呈現 retry_eligible 與 reset_at_ms)與 personas(依 persona 分列:decision_id、mode(shadow/enforced)、outcome、policy_version、policy_config_revision(operator quota-pools 設定檔的 config_revision)、observation_version、demand_version、qualification_version、requested_profile_key/resolved_profile_key、selected/excluded(僅 executor/model_id/independence_domain/exclusion_reason——不含 credential、env、raw prompt)、reservation_id、card_id/attempt_id/reason(這筆決策所屬的卡片、attempt 與 wait 理由碼),以及 classification(demand:confirmed/not-applicable;舊 DEMAND_FIXTURE_VERSION receipt 保持 estimated;observation:confirmed/unknown/not-applicable,缺 provenance 或候選本身 unknown remaining 一律 unknown,不呈現成『現在可派』;binding(#1116):bound-exact/bound-identity/binding-missing/unknown,區分「這個候選有 binding 涵蓋(精確或穩定 identity)」與「完全沒有任何 binding(binding-missing,即 unmanaged 的具體原因)」,缺席(#1116 之前寫的舊 receipt)一律 unknown)。decision store 暫時讀不到時保留上一次成功讀到的內容,並附 stale: true、stale_reason、stale_since_ms;reader 重啟後 store 仍讀不到時,Monitor 以 durable work snapshot、Manager daemon 以上一份 status.json 裡的投影當 last-good 種子(stale_since_ms 是重啟前最後一次成功讀取的時間),仍依目前 attempt 事實判定——已被取代的舊決策回 quota-decision-attempt-superseded,不當成目前決策。與 cortex work show <work_id> 共用同一份投影,兩者對同一份 snapshot 保證一致;work show 的 quota_decision 與 blocking_reason 都以 exact (repo, work_id) 取值,同 work_id 的其他 repo 不會互相借用。
  • not_claimable(#669):claim 判定現在不可 claim、且刻意不建立 run 的 work item。最典型的是 docs/superpowers/workstreams/*——那類 work item 設計上就不對應單一 issue,missing_issue 是預期狀態而非異常,過去卻被物化成停在 current_phase: claim、永遠不會推進的 needs_human run(實測一次產出 24 個,把 attention 信噪比壓成 1:24)。現在改記在耐久的 <coordinator_root>/not-claimable.json(schema cortex-not-claimable/v1),欄位含 reason、detail、first_observed_at/last_observed_at/observations(卡多久了)與 next_step_hint(照抄即可執行的下一步)。work item 一旦變成可 claim,該筆紀錄於下一次判定時自動消失;work item 從 snapshot 移除時,claim scan 收尾也會一併清除其紀錄。attention 因此只留可行動的項目,被跳過的項目也不會變成盲區。
  • recent_done:最近退出的 job 或進入 terminal gate 的 slice 摘要,含 slice_id、gate_status、at、exited_at、gate_reason、job_id、branch、明確的 repo project 歸屬(manifest 缺該欄時為 null)。at 是 handoff manifest 完成時間;exited_at 來自 registry 中綁定 job 的實際退出時間。workflow job 另帶綁定 run 的 run_id、work_id、run_status(ongoing/superseded/done),缺少 registry 證據時欄位為 null。attention/slices 也只接受明確的 slice 或 workflow job repo;不從 branch、worktree 或 path 猜測 project。只回溯 --recent-done-window-seconds(預設 86400 秒/24 小時,可用 PSC_MANAGER_RECENT_DONE_WINDOW_SECONDS 覆寫)內完成的 handoff manifest;window 內沒有資料時回空陣列,不會回退撈更舊的紀錄,過期 manifest 檔案本身的清理屬於 #178 program teardown GC 的範圍,不在 recent_done provider 職責內。

Job exited 只代表 Agent process 以 exit code 0 結束,不代表任務交付完成。只有 Slice 通過 deterministic verification、必要的 foreign review,且 Candidate 已進入 target branch 後,才會變成 completed 並釋放下游 dependency。

4. 處理 needs_human

先從 cortex status 的 attention[].next_actions 選擇當下允許的動作,不要手動改 jobs.json: cortex list 與 cortex work show 的 needs_human work item 也會投影同一份目前可受理的 recovery actions,包含符合 owner-bound 前置條件時的 recover-pre-candidate。

Verify/review 卡的 stage-evidence reuse(#844):cortex work resume 與 periodic tick 遇到同 run/claim-era/exact candidate 且輸入完全相容的既有成功 job 時不再呼叫模型,改由 Manager 在採信點重驗後直接採信;resume 結果與 cortex work show 的 stage_reuse 會列出 reused(來源 run/job/evidence hash)、fresh 或 ineligible 與原因。支援 cohort、receipt 欄位、migration 與未支援範圍見 Stage evidence reuse。

Recovery action 的正式 work/slice 名稱、cortex recover 有界 alias、前置條件、CAS、派工時點與可觀測結果見契約矩陣。coordinator cortex work 的 --expected-candidate 支援 retry-build、retry-verify、retry-review 與 recover-repair-commit 的 exact SHA CAS;同時提供 JSON payload 時兩者必須一致。work 與 slice 同名 action 仍沿用各自的 authority 與資源契約。

cortex slice-action "$SLICE_ID" retry-build  --actor operator
cortex slice-action "$SLICE_ID" retry-verify --actor operator
cortex slice-action "$SLICE_ID" retry-review --actor operator
cortex slice-action "$SLICE_ID" abandon      --actor operator
cortex slice-action "$SLICE_ID" supersede    --actor operator \
  --reason "後續 retry 已接手此分析" --expected-binding-revision "$BINDING_REVISION"

supersede 是單筆終結動作,只接受沒有 in-flight job 的 needs_human/failed slice;必須帶 --actor、單行 --reason 與 status 所列的 --expected-binding-revision。它會保留舊 slice 與 action audit,將 state 標為 superseded,不建立 CompletionRecord,也不釋放 dependency;相同 actor/reason/CAS 重送會回報已完成且不新增 audit。

fanout、tick、complete、slice-action 與 work 都會寫入 control request queue,再由 daemon / manager 這個單一 writer 改變狀態;daemon 未啟動時會明確拒絕,不會由 CLI 直接競寫 registry。共享 coordinator root 內的 jobs.json 另以 exact durable-byte SHA-256 revision + canonical jobs.json.transaction.lock sidecar 做 compare-and-persist:stale request 不會被靜默重播,daemon 會先把 RegistryRevisionConflict(含 expected/actual revision 與 canonical path)持久化成 done error,再移除 request file。

Work lifecycle mutation 使用 cortex work <link|unlink|start|resume|retry-build|abandon|retire-delivered|close-delivered|auto|verify-attest|review-attest|review-disposition|ship> <work-id> --repo <owner/repo>。link / unlink 以 --kind <github_issue|github_pr|openspec|path> --ref <canonical-ref> 指定來源,--issue N 僅保留一個 release 的相容入口,兩者不得混用;一般 link/start/resume 由 installer/Monitor registry 解析 trusted repo root。retry-build 的 payload 只接受 expected_candidate CAS,CLI 會拒絕 expected_run_id;cortex run work retry-build 使用 --expected-candidate,不接受共用旗標 --expected-run-id。abandon 必須帶 exact --expected-run-id、bounded --actor 與單行 --reason,只會把無active Job、無PR/ship side effect的pre-delivery run設成superseded並留下immutable evidence,不會建立CompletionRecord;終態化後也會逐 run reconcile planning transaction、回收 build worktree,並退役 build branch。有超出 base 的 branch commit 會先保留在 archive/<work_id>-<shortsha> tag。review-attest 的 review 摘要、空 findings 與選填 evidence_refs,以及 ship 的 exact evidence refs,都由 --payload <json> 傳入;當 work item 沒有 mapped OpenSpec 時,即使尚未建立 PR,只要 verified HEAD 仍等於 candidate 也可先建立 maintainer attestation。CLI 只排隊,confirmed Todo/issue authority、GitHub label、official OpenSpec archive、preflight、current-HEAD review、merge 與 remote closure 都由 Manager 驗證及執行。

retire-delivered 只會移除同時 pinned 在該 run planning authority 與目前 mapped Todo、且已不存在的 path link;存在、symlink 或未經這兩者確認的連結會保留。

沒有 WorkflowRun 的管線外交付可用 cortex work close-delivered <work-id> --repo <owner/repo> --actor <actor> --reason <reason> 補建 immutable CompletionRecord。Manager 會重驗 issue、PR merge commit、OpenSpec archive、mapped Todo 與 archived OpenSpec tasks;所有既有 remote closure 條件通過才寫入 actor/reason 證據,命令不會建立 WorkflowRun。

Sizing 的五個維度仍各為 0–2 分,Green/Yellow/Red 仍是 0–3/4–6/7–10。spec_stability 使用可識別的 stability-risk-v2 風險映射:完整 accepted 三件組為 0、單一缺失 kind 為 1、至少兩個缺失 kind 或任何 blocker/未 accepted artifact 為 2;空白或不一致資料保守為 2。這不會取代 accepted/readiness gate。新 claim/reclaim/明示 retry 重算才採用新映射;舊 run、frozen planning、CompletionRecord 與 immutable evidence 不會因讀取或重啟被回填,缺算法來源的歷史分數標為 legacy/unversioned,需正式重新評估才取得新分數。

Manager periodic tick 會從 durable Monitor snapshot 執行 auto-claim scan;它會讀取 work item 的全部 mapped issues,任一張帶 cortex:auto-on-going 即符合 label 條件,但任一 GitHub API read 失敗會讓整個 claim fail-closed。cortex work auto ... --enable|--disable 未指定 legacy --issue 時會對全部 mapped issues 套用相同 label mutation;任一 mutation 失敗時整個 action 報錯。缺 issue 的 confirmed Todo 會持久化為 needs_human: missing_issue,待 operator link 且 Monitor snapshot 更新後才能 resume。Run 的 claim key 綁定該 work item 的 canonical semantic authority(provider/source revisions 與 confirmed refs),不綁 whole-fleet snapshot hash;只有 sequence、written-at 或其他 repo 噪音的 snapshot refresh 會原 run resume 並更新 provenance,語意來源變更才建立新 run。

ship 的 pr_number、change、todo_paths 必須與 current WorkAuthority 的 confirmed refs 完全相同,第一次 ship 後即成為 immutable delivery binding。V1 每個 run 只支援唯一一張 PR、零或一個 OpenSpec change,以及唯一一個 Todo path;PR/Todo 不是各一個,或 OpenSpec confirmed target 多於一個時,都會轉為 needs_human: multiple-delivery-targets-unsupported,不會以其中一個 target 寫 CompletionRecord 或投影 done。Manager 會用 authenticated gh api 更新既有 mapped PR 的 zh-TW conventional title、body 與 labels,再逐欄 reread;body 必須用 closing keyword涵蓋全部 mapped issues。Checks、statuses與reviews的REST pagination使用--paginate --jq '.'輸出一頁一行的compact JSON stream,不依賴gh api --slurp;任一頁無法解碼仍fail-closed。repo_root 必須恰好等於 canonical git rev-parse --show-toplevel realpath,且 origin 對應同一 owner/name。

第一次派出 Builder 前,Manager 會重新載入 WorkAuthority-backed run 的目前 authority,要求恰有一個 canonical workstream Todo,且 authority revision 與 WorkflowRun.source_revision 相符。Todo=0 時會停在 needs_human,提示發布 canonical Todo、link path、等 Monitor 更新後再 resume;只有多個 Todo mapping 才建議 unlink。authority 已變更時,舊 run 不會繼續派 Builder,須依既有正式重啟流程重新綁定。此 admission 發生在 Builder job、worktree 與 launcher 建立前,且不攔 plan、verify 或 review;ship cardinality gate 繼續作為 backstop。

Merge authorization 只雜湊 stable preflight 結果(argv、return code、HEAD、tree 與 gate outcome)及 immutable evidence hashes,不納入 stdout、stderr 或 duration。若 Manager 在 merge-authorized 後 crash,restart 會先以唯讀 authorization record 與 authenticated merge status reconcile;已合併時不重寫 PR metadata,也不重跑可能漂移的 preflight output。

5. 由 Manager 完成交付

verify/review steps 全部通過後,若 operator 才提交後續阻斷發現,仍可對尚未完成的 run 使用 exact-candidate retry-build;需附 --reason,Manager 會以既有 immutable operator-adjudication evidence 記錄裁決,再重開 builder 並重新驗證新候選。候選不符或 run 已完成時會拒絕。

Work Item workflow 通過 build、deterministic verification 與 foreign review 後,由 Manager 依序 archive OpenSpec(若 mapped_openspec == () 則跳過)、在自己的 ship clone 內用 bounded direct git subprocess probe origin/main、跑 policy/pinned preflight、建立或更新 PR,再要求恰好一種current-HEAD delivery review:authenticated Copilot review,或透過review-attest建立的immutable maintainer evidence。main probe 會依序 resolve/validate Candidate、fetch、resolve/validate FETCH_HEAD、算 merge-base,必要時再跑 merge-tree --write-tree --name-only --no-messages -z 與 NUL-safe path parser;只有 Candidate 已含最新 origin/main 時才可進 preflight。clean-behind 時 Manager 會在 ship clone 重新 fetch 並比對 main SHA,再由 Manager 將該 exact main 合入 Candidate;每個 run 預設最多自動同步 3 次,可用 PSC_MAIN_SYNC_AUTOSYNC_MAX 設為 0–10(0 停用)。同步產生新 Candidate 後只重跑 verify 與 ship probe,保留既有 foreign review,不重派 build 或 code-review。自動同步次數、main SHA、新 Candidate 與失敗原因會寫入 run evidence,cortex work show 可查看;達上限時以 main-moving-too-fast 停下。同步競態或 merge 失敗、conflict 與 probe failure 維持人工處理;若 exact Candidate、passed build、無 active job 與 ship/archive 前置條件都允許 registry reset,cortex status 會顯示帶 exact Candidate 的 retry-build 指令。Builder 指示會引用 stop evidence 與該次 probe 的 main SHA,reset 後須重跑 verify、review 與 ship probe。fetch/merge-base/merge-tree/path-parser failure 會把 candidate/stage/returncode(timeout 為 null)/error_kind/main_head 寫成 content-addressed main-sync-probe evidence;若真的要 push,preflight 後與 git push 前還會再 probe 一次。已進 merged/done closure 與 terminal refresh 則跳過 main probe,直接走既有 closure reconciliation。Manager接著重讀checks、threads、closing refs與mergeability;只有全部gate對同一HEAD成立時才執行merge commit。Maintainer evidence保留maintainer-review kind,絕不偽裝成Copilot。

Headless build card會在dispatch前解析declared inputs。異質brainstorm發布的新artifact會先由canonical brainstorm evidence的ref/kind/hash與不可變發證source revision原子併入WorkflowRun planning authority;legacy active run也只透過相同evidence reconcile,不從mutable檔案猜測。Monitor 後續納入同 run 已接受且 bytes 符合 baseline 的 planning artifact,或加入綁定同一 exact verified Candidate 的 Manager PR 時,claim/reconciliation 保留原 claim-era、gate、evidence 與 attempts;來源、hash、PR refs 或 Candidate 無法逐項對應時,仍走既有 restart。對已有 Candidate 與已對應 PR 的 run,其他 authority 欄位前進時,自動掃描不會重設;操作者明確執行 cortex work resume <work-id> --repo <owner/repo> 後,Manager 僅在該 PR 仍開啟、PR head 等於已驗證 Candidate、沒有執行中的工作,且 delivery journal 的 run/PR 綁定相符時,才讓同一 run 回 verify/review 重驗,再回到原 ship 路徑。建立 PR 只更新 run 的 pr_refs 與 delivery journal,不改寫 source_revision/claim key;brainstorm-required run 缺 evidence 一律停止。Yellow plan review 只有在結果明確 ready 後,才會以單一 registry transition 將被接受的逐檔 bytes/hash 與 immutable planning source revision 寫入 receipt 並同步 run baseline。若 verify dispatch 前發生 planning drift,Manager 會保存 exact candidate/card/hash stop;operator resume 只有在尚無 verify job、receipt 綁定仍有效且既有 candidate workspace 逐檔符合 review receipt 時,才會以 CAS 更新 baseline 後續跑同一 candidate,原有 checkbox-only 容忍維持不變。Accepted planning artifact只接受該authority的ref/hash;獨立builder worktree缺檔時由Manager原子seed。plan card 缺少宣告的 output 時,Manager 會以 planning authority 綁定的 durable journal 發佈;registry 未提交時回滾,已提交時由 reconcile 保留產物。Codex 的 sandbox mode 依卡片契約導出:只有 commit_policy=forbidden 且 declared_outputs 為空的 build card 使用 read-only;其他 builder card 不套用這個唯讀例外。workflow中commit_policy=required的builder,以及legacy fanout/dispatch/retry-build的builder persona,取得明確commit capability,linked worktree才額外開放Git驗證出的current worktree gitdir、shared objects、current branch ref與reflog parent directories。Launcher會清除inherited Git repository selectors;planner、verify與review不取得這些Git write directories,symlink、detached HEAD或invalid metadata一律拒絕required-commit launch。Job、versioned bounded prompt與canonical evidence保存同一份input snapshot,terminalize再驗hash。Build card可把Candidate單調推進到目前Candidate的exact descendant worktree HEAD;verify/review仍須完全等於凍結Candidate。Dead job,或plan/build workflow card以schema/binding正確的terminal明示failed|needs_human,轉needs_human後periodic runner都不會自動重派;explicit resume保留舊job/log並重試同一run/card。verify/review reviewer card若以形狀合法的 failed/needs_human terminal 誠實要求停止,Manager 會落 verification-terminal-explicit-stop/review-terminal-explicit-stop、保留模型原文與 job log evidence ref,periodic runner 同樣不自動重派;這條路的 explicit resume 只會重落同一個停止,不會重派新 reviewer job,實際重派出口仍是 retry-card/retry-build。若已完成的Candidate在delivery preflight才發現真實缺陷,operator可用retry-build加上exact expected_candidate;Manager通常只在ongoing needs_human verify/review run、無active job且舊build全部passed時,以窄化registry recovery原子重開最後一張builder card。若run尚未掛needs_human但停在review、verify/review steps全passed且verified Candidate精確相符,operator也可提交後續阻斷finding走同一入口;此時必須帶--reason,Manager先記錄既有immutable operator-adjudication evidence,再reset並重跑驗證;已done的run拒絕重開。若final builder已成功退出(exited/0)但因immutable input/evidence驗證失敗而未綁定,則只允許同一Candidate CAS在build phase重派,且前置build card必須全passed、最後card仍pending、最新同card job必須成功退出且無workflow evidence;真正的failed job不走此入口。所有retry-build recovery prompt都要求先檢查worktree是否已有repair commit,並允許builder提交或採用已測試的descendant Candidate;Manager仍會獨立驗證exact舊Candidate CAS與單調ancestry。terminalization recovery另要求保留declared input snapshot並先檢查未綁定commit。兩條路徑都清除失效的下游authority,並要求新HEAD為舊Candidate的descendant。Dispatch失敗會恢復stop facet,必須修正authority後再由operator重試。

Legacy headless builder 的 dispatch prompt 會帶入 Manager 解析後的 worktree 根目錄,限制 repository 讀寫與命令只在該樹內執行,並禁止存取 operator/base checkout;路徑遭拒時須從目前 cwd 重新解析成 worktree 內的相對路徑。

provider/launch 失敗詞彙維持分層,且只對有證據的 executable 問題 reroute(#826)。 結構化 terminal 與 controller interruption 仍優先於文字關鍵字;其後才接受可證實的 exit 127 空輸出,最後才走 provider text 的 rate limit → quota → auth → effort_not_supported → executable_not_found → content → transient 次序。not found 只有在已知 launcher/executor 的 shell command not found 上下文、同一行帶 exec/execvpe/execve/spawn/Popen 與 No such file or directory 的 launcher ENOENT(若同一行也附帶 : <target>,該 target 必須是已知 launcher executable,且不能只是缺失的 cwd/path;若沒有明示 target,則 bare launch-call ENOENT 仍可成立;未知 explicit target 一律維持 non-reroutable)、可信的空 127(不是缺 log/讀不到 log),或 launch 例外可證明缺的是 provider executable(如 copilot/codex/claude/agy/cg 或精確 executor 名)時,才會進 executable_not_found;HTTP 404、model not found、一般工作檔案 No such file or directory,以及 bash/sh/git/systemctl/systemd-run 這類 shared launch infrastructure 缺失,都不會借題發揮成 reroute。新的環境類詞彙是 effort_not_supported/executable_not_found/launch_failed:前兩者在 authority 不是 hint 時可有界 reroute,launch_failed 只保留原始 exception/缺 handle 診斷,三條 launch-failure producer path 都會同步保存 matching launch-failed runtime diagnostic,不自動 retry 或 reroute;真正的 runtime-contract-failed 與 reviewer candidate drift 仍比 provider routing 更強,持續 fail-closed。

quota 不會在同一個 identity 上重試;Builder roster 有其他合格 identity 時會改派下一位,沒有替代 identity 時則停止。這項改動不調整 rate-limit、transient 或其他 provider outcome 的 retry 政策。

#582 sandbox 工具中止分類:終局 subtype=error_during_execution 且 terminal_reason=aborted_tools 表示工具鏈被外部生命週期中斷,分類為 environment/tool_aborted,可進入 bounded retry;這不同於維持 unknown 的一般 controller interruption。

Merge 後 Manager 會從 canonical checkout fetch default branch,以本機 Git 驗證雙親 merge commit ancestry、OpenSpec tree 與 mapped Todo blob SHA/內容;issue closed 仍由 GitHub API 讀取,並照常驗證 CompletionRecord。shallow checkout 會 fail-closed 並提供診斷,不自動 unshallow。workstream Todo 未勾 checkbox 只作觀測,不阻擋 remote closure。若 work item 有 mapped OpenSpec,仍要求 active OpenSpec 消失且 archive 成立,archived tasks 仍須通過既有 archive gate;mapped_openspec == () 時不要求 archive。其餘 closure 證據不成立時不會提早標 done。

若舊版 authority-restart 已把 run reset 到 verify,但同一 run 的完整 merge authorization 與 delivery journal 仍確認 Candidate 已 merge,resume 會停止且不重派 verify,並提示 cortex work <work-id> retire-delivered。此出口保留退休/abandoned 語意,不代表 shipped completion。

Ship lane 自動 merge 後,Monitor 會把交付 PR 以 terminal 狀態關聯進 WorkAuthority,Closes #N 也會關閉 issue。只要 delivery journal 的完整 merge authorization 證明該 PR 是本 run 已 merge 的交付,且 authority 的 PR 集合恰好只有這一個,closure 就會綁回同一個 run,完成 CompletionRecord 並標記為 done(#1141)。若 authority 出現其他 PR,或 PR 不是由本 run merge,仍會 fail-closed。已交付的 run 若停在 needs_human,status/work list 會列出 retire-delivered,不再列出必定被拒絕的 abandon。 close-delivered 使用相同的 strict closure 條件,僅補足缺失的 operator CompletionRecord;遠端 issue、PR、OpenSpec 或 Todo 證據未全通過時不會結案。

目前邊界

  • 沒有 Web UI;任務意圖仍以 Markdown spec 維護。
  • Copilot finding 只允許兩輪 bounded fix/re-review;超過預算需由 operator recovery。Maintainer路徑仍要求ForeignReview、terminal-green checks與resolved/outdated threads。
  • verification 的 sanitized env 不等於 network / filesystem sandbox。
  • v1 自動 foreign review 限 tier: shareable;required review 的 builder slice 會在派工前檢查所選 project policy 的 tier。manifest 缺少或填入非法值時會指出檔案路徑與允許值;完全沒有 manifest 時維持 shareable 預設。
  • merge commit 是目前受支援路徑;auto/squash/rebase/cherry-pick 會 fail-closed。
  • installer/service 尚無 periodic builder/reviewer model pin;需要固定 model 時,使用帶 --model / --review-model 的手動 cortex tick。

尚未實作的 operator bootstrap、model pin、monitor init、instance/path isolation、state migration 與 async request UX 統一追蹤於 issue #12,供後續 OpenSpec / implementation plan 使用。

Coordinator dispatch discipline(v1)

Job / Slice / Gate 狀態語意

層級 狀態 語意
Job dispatched / running / exited / failed process 執行結果;exited 不等於交付完成
Slice pending / building / reviewing / verified / completed / needs_human / failed 交付生命週期與 release gate
Gate pending / passed / needs_human / failed verification + foreign review 的決策狀態

依賴釋放只接受 slice_state=completed 且 CompletionRecord / hash / target ancestry 全部一致;單純 Job exited 永遠不能滿足 DAG。

Verification frontmatter 與 trust boundary(shareable-only)

---
dispatch: auto
slice_id: auth-hardening
plan: docs/superpowers/plans/auth-hardening.md
target_branch: main
repo: hamanpaul/paulsha-cortex
verification:
  docs_class: code
  required_artifacts:
    - path: reports/policy.json
      must_change: true
    - path: script/deliver.py
      mode: "100755"
  checks:
    - kind: persona-scope
    - kind: command
      name: policy
      argv: [python3, -m, pytest, -q, tests/policy.py]
      cwd: .
      timeout_seconds: 30
  tests: []
  full_suite:
    argv: [python3, -m, pytest, -q]
    cwd: .
    timeout_seconds: 60
    baseline: no-regression
---
  • v1 只支援 tier: shareable;非 shareable 會 fail-closed 到 needs_human。
  • verification command 只接受 typed argv(shell=False);採 sanitized env,但這不是 sandbox,不保證隔離 untrusted code。
  • required_artifacts 可選擇以 mode: "100644" 或 mode: "100755" 宣告 Candidate Git tree 的預期檔案 mode;未宣告時不檢查 mode。
  • tests[*] 可選填 acceptance_ids(例如 acceptance_ids: [R01-AC1];非空、不重複的 acceptance criterion id),verification 執行該測試後把同一組 id 原樣寫進 evidence 的 details.tests[*],供 cortex delivery 的 test stage 逐 criterion 採信(#845);checks/full_suite 不接受此鍵,未宣告時 contract 形狀與 hash 不變。
  • verification frontmatter 的 inline argv list 由 zero-dependency YAML subset parser 解析;含逗號或 ] 的元素需使用單/雙引號,單/雙引號內的反斜線跳脫可保留引號等字面值,尾逗號可容忍,前導/中間空元素與未閉合引號會拒絕。
  • repo 為 optional 顯式歸屬宣告(owner/repo,#469):宣告後派工會寫進 builder/reviewer job 的 workflow_repo,recent_done/slices 的 repo 歸屬即投影此值;未宣告維持 null,不從本機路徑或 git remote 推斷。非法 shape(不是恰一個 / 或任一段為空)會 fail-closed 落 hold。
  • cortex deck compile 可用 --repo owner/name 將明確 repo 寫入輸出的 spec;省略時仍為 repo: null,不從本機路徑或 git remote 推斷(#473)。

Runtime preflight(dispatch 前的 capability 與 provider 新鮮度,#262)

Manager 在建立 worktree/sandbox/job row/model session 之前,會依 card 宣告在 「即將實際被使用的 executor 環境」執行低成本 preflight。card 未宣告任何 capability 時 runtime capability 檢查是 no-op;overlay 選出的 Copilot identity 仍會接受下述模型探測。

paulsha_cortex/deck/data/cards.yaml 的 card 可宣告 runtime_capabilities,形式為 <kind>:<name>,kind 為 module/executable/bridge/provider:

  - id: tdd-red
    # ...
    runtime_capabilities: ["module:pytest"]

宣告是資料而非程式碼分支——新增 card 只要寫這份宣告,不需修改 preflight 實作; 非法 kind 或空 name 會在 deck 載入時 fail-closed(DeckSchemaError),避免無聲漏檢。

檢查一律走 executor 環境,不看 host:

  • module: 以 executor 的 interpreter 開子行程 import;
  • executable: 只解析 executor 的 PATH;
  • bridge: 依內建對照表落到 module 或 executable 探測;
  • provider: 讀 provider snapshot 的新鮮度。

SubprocessLauncher.executor_environment() 用與 launch() 相同的 _git_scope_env()/_review_scope_env() 產生 env,因此 preflight 與正式 job 的 interpreter、PATH、HOME/sandbox policy 一致(reviewer 會走最小 env)。

Provider 健康採「快照 + TTL + 有界 probe」三層。snapshot 帶 observed_at、TTL、 source 與 reason;TTL 內直接採信,逾期的 degraded 不會被當成當前事實,而是對 必要 provider 執行有界 live probe。四種結果各自獨立,不折疊成布林:

結果 意義 是否 hard block
capability_missing executor 環境缺 module/executable/bridge 是
provider_unavailable 新鮮快照或 live probe 判定 provider 不可用 是
stale_snapshot 快照逾期且無法探測,需刷新 否
probe_inconclusive 探測 timeout/額度耗盡/無定論 否

degraded 的 Monitor provider snapshot 與 runtime preflight provider freshness 另帶 diagnostic_reason 物件,提供機器可讀 reason、人可讀 detail 與來源 source; cortex doctor --json 的非 pass probe 也以相同欄位呈現理由。這些診斷不改變原有 status、hard block 或 reroute 判定。

live probe 以 provider identity 為鍵共用 TTL 快取與 rate-limit 額度(沿用 claim_readiness 的 LiveProbeCache 模式),同批次多張 card 對同一 provider 只探測 一次。preflight 失敗時,Manager 會在既有 identity 順序與 independence domain 規則內 自動 re-route 至下一個合法 identity;沒有合法替代才進入帶具體 reason 的 needs_human(reason 形如 runtime-preflight-capability_missing,並保留 blocking finding 的診斷內容)。builder 候選會排除 zero-tool 的 cg executor。整個 gate 位於 model session 建立之前,被擋下時 model invocation 維持 0。

Manager 選到 operator overlay 中的 Copilot identity 時,會在建立 Cortex Job 前以 copilot -p、指定 --model 與 10 秒上限送出一次短 prompt。CLI 明確回報該模型不可用時, 會沿既有候選順序 reroute;若 CLI、認證或網路狀態無法判定,會記錄「模型可用性無法驗證」 診斷並繼續派工。這不是 Job,也不建立 remote session 或 log directory;短 prompt 仍可能 消耗一次 Copilot 請求。此探測只涵蓋 overlay Copilot 模型,不代表其他 executor 的可用性。

cortex inspect status 會顯示缺少的 capability、使用中的 executor environment (名稱/interpreter/PATH/HOME)與每個 provider 的 snapshot 新鮮度 (status、source、age、TTL、是否 fresh)。

Foreign reviewer identity(不同 independence domain)

PSC_PROJECT_CONFIG_ROOT/model-identities.yaml:

schema_version: 4
identities:
  - executor: agy
    model_id: gemini-3.1-pro-high
    independence_domain: google
    capabilities: [planning]
    live_probe: agy-plan-sandbox
  - executor: codex
    model_id: "<builder-model-id>"
    independence_domain: "<builder-domain>"
    capabilities: [planning, build]
  - executor: claude
    model_id: "<reviewer-model-id>"
    independence_domain: "<different-reviewer-domain>"
    capabilities: [planning, review]
    executable: /opt/cortex/bin/claude-compatible
  • schema v1–v3 仍可讀取;schema v2 起可設 capabilities / live_probe,schema v3 起可設封套欄位,schema v4 可為 Claude identity 選填 executable 絕對路徑。Claude job 與 cortex doctor 的 review-sandbox probe 共用該路徑;路徑必須指向一般可執行檔,無效時拒絕啟動且不回退 PATH,job 記錄保存解析後路徑。packaged registry 提供 canonical agy 候選;host overlay 宣告同鍵身分時以 overlay 為準(見下方「模型引擎三層解析鏈」)。cortex doctor 依解析政策確認至少有一個可用的 planning identity,不要求部署保留 canonical agy;agy discovery 與 smoke probe 只判定 agy 是否可用。
  • planner/builder/reviewer 必須是 explicit (executor, model_id) 且可解析;agy 只有在 doctor --probe-live 的 model discovery 與 plan/sandbox smoke 都吻合時才可用。
  • fanout/tick 明確指定的 builder (executor, model_id),以及 spec frontmatter 成對宣告的 executor/model_id,都會先查這份 registry;unknown identity 會在派工前 fail-closed 並列出可用 candidates。
  • workflow reviewer 只會選擇明示 capabilities: [review] 且與會產出 Candidate commit 的 build card 不同 independence domain 的 schema v2 以上 identity;commit_policy=forbidden 的隔離確認卡不計為 Builder;legacy v1 identity 只取得 planning capability,不能被猜成 reviewer。
  • Verify/Review 以 executor 的 enforced read-only mode在exact Candidate的remote-free disposable clone檢查;Claude reviewer固定使用dontAsk與safe-mode,只暴露OS-sandboxed Bash並要求structured JSON object,不載入Candidate CLAUDE.md/skills/plugins/MCP/remote session,也不進Plan Mode。Filesystem預設拒讀整個home、/run/user與Docker sockets,只重開Candidate與Python user-site工具鏈,並對Candidate clone設deny-write;review subprocess只保留PATH、HOME、locale、TMPDIR、VIRTUAL_ENV等非密鑰基礎環境,且不啟動login shell。Linux/WSL host必須安裝bubblewrap、socat與官方sandbox runtime(Ubuntu可用sudo apt-get install bubblewrap socat,再用npm install -g @anthropic-ai/sandbox-runtime);任一依賴缺失、Unix-socket seccomp失效或命令要求unsandboxed fallback都拒絕啟動。Manager在所有terminal/launch failure/operator retry路徑重驗原Candidate完整tree snapshot後清除clone。agent只回傳substantive result、findings與inline report body;report僅能發布至phase專屬的reports/verify/*.md或reports/review/*.md,並由durable publication journal把多檔CAS、canonical evidence與registry bind組成可rollback/roll-forward的transaction。Manager會從durable Job注入Candidate、builder/reviewer job ID與launch identity,agent不取得report或Candidate寫入權。
  • cortex doctor會在identity registry配置Claude review capability時把Claude Code 2.1.187+、必要CLI flags、bubblewrap、socat與srt執行能力列為required probe;--probe-live另跑native read-only與Unix-socket seccomp smoke。沒有Claude reviewer的部署只顯示非必要warn。Claude的protected bind targets位於deterministic disposable session root,exact Candidate則固定在其無污染的candidate/ checkout。
  • 同 domain、未知 identity、缺 model 都會得到 foreign-review-absent(fail-closed)。
  • foreign review 與 manager/tick 解析同一份合併 registry(host overlay + packaged 候選池):packaged 身分不需要被複製進 overlay 才能被 retry-review 解析到。

模型引擎三層解析鏈

planner/builder/reviewer 的身分解析依序走三層,層級是排序主鍵:

層 provenance 值 來源 語意
1 operator-overlay host overlay model-identities.yaml operator 人工指定;列序即優先序,壓過 packaged roster 的一切內建順序
2 evaluated-roster host model-eval-roster.yaml 經 patchmud 評估合格且人工複核通過的身分
3 packaged-fallback packaged roster 候選池:只供評估管線取材;解析落到這層一律 fail-loud

同層內維持既有偏好(primary_domain 偏好、patchmud 實測封套優先),因此這些偏好 不會再把 operator 的人工指定擠到後面。run-scoped 覆寫(--builder-model 等) 仍為最高優先,provenance 記 run-override。

WorkflowRun.resolved_model_chain 逐段記錄解析層與封套來源:

{"builder": {"executor": "codex", "model_id": "gpt-5.6-luna",
             "independence_domain": "openai",
             "source": "operator-overlay", "envelope_source": "default"}}

第 2 層契約——$PSC_PROJECT_CONFIG_ROOT/model-eval-roster.yaml(檔案不存在 即空清單;可手工維護):

schema_version: 2
entries:
  - executor: claude
    model_id: sonnet
    role: builder
    execution_profile_key: epk:v1:resolved:<64 lowercase hex digits>
    benchmark_type: issue-resolution
    deck_digest: sha256:<64 lowercase hex digits>
    evaluator_revision: sha256:<64 lowercase hex digits>
    verdict: pass
    evaluated_at: "2026-09-26"
    eval_source: patchmud-report-v2
    eval_ref: <report fingerprint 或 evidence 參照>
    review_status: approved
    reviewer: operator
    reviewed_at: "2026-09-26"

schema v2 每列只綁一個 report role、execution profile 與完整 cohort evidence; resolver 必須取得並比對五個 cohort 欄位才會將身分放入第 2 層。不同 deck 或 evaluator revision 可各有獨立核可列,重複完整 identity 會拒收。Schema v1 的 roles 清單仍可讀,沿用舊的 identity-level approval。所有版本都要求 verdict: pass、review_status: approved 及可稽核 reviewer/date;「評估過」不等於 「人工核可」。清單解析失敗時第 2 層視為空(保守方向,絕不因錯誤多授予資格), 並由 cortex doctor 的 model-resolution probe 回報,不會中止 periodic tick。

overlay 的解析指令(皆為選配,既有 overlay 檔案不改也照舊合法):

resolution_policy:
  packaged_fallback: warn        # allow / warn(有 overlay 時的預設)/ deny
identities:
  - executor: claude
    model_id: sonnet
    independence_domain: anthropic
    capabilities: [review]
    override_packaged: true      # 明示覆寫同鍵 packaged 身分
packaged_overrides:              # 對 packaged 身分的處置
  - executor: agy
    model_id: gemini-3.1-pro-high
    action: park                 # park=完全停用;demote=降到同層最後
    reason: operator 未核可此引擎

packaged_fallback: deny 時第 3 層完全不參與解析,某個 persona 因此無候選會 fail-closed 並列出補救路徑(列入 overlay,或評估合格後加入 eval roster)。

cortex doctor 的 model-resolution probe 走與 tick 相同的載入器與排序函式, 回報每個 persona 的生效解析與所在層、使用的 config root;overlay 宣告了某角色 卻不是生效解析(不變式被破壞)、或有 persona 無候選時 FAIL。

Execution profile(#835)

每次實際派工會在既有身分解析結果之外,建立 versioned execution profile: requested 記錄明示偏好/pin,resolved 記錄 Cortex 選定的 adapter、model、native effort、persona loadout、工具與 sandbox,observed 只接受可信來源對 exact resolved key 的完整觀測。無可信觀測時維持 unknown;不會把 descriptor、要求值或 launcher 設定冒充成 runtime 實測。

Profile 以 WorkflowRun.execution_profile_bindings 作為獨立 sibling 欄位持久化, 不改寫凍結的 resolved_model_chain、attempt 或歷史 evidence。舊 run 缺少該欄位時 仍按 legacy 載入;新版讀舊資料不補造 profile。格式版本未知、descriptor 不完整、 key 不一致或 launch 條件與 resolved profile 不符時拒絕派工。

Adapter descriptor 是資料,不會載入 descriptor 提供的程式碼。adapter 的 protocol/runtime version、原生 effort 合法值與預設(含 model 專屬預設)由 packaged paulsha_cortex/coordinator/data/execution-adapters.yaml 提供,operator 可在 $PSC_PROJECT_CONFIG_ROOT/execution-adapters.yaml 以同 schema 的 overlay 整筆取代同名條目;未知鍵、缺欄位或未知版本一律拒收。新增 model 或既有協定的 effort 只改 descriptor;新 runtime 則要以程式碼登記受信任的 adapter 實作, 並沿用現有 launcher 的 argv、terminal、usage、cancel/timeout、工具與 sandbox 邊界。 quota 能力預設為 unknown,usage 不代表剩餘額度。正式 dispatch 會重新驗證 role、pin、 reviewer independence 與既有 Trust Root(加固 runner 下的 launcher/toolchain/credential grant),拒絕時在 spawn 前持久化 needs_human。Sized run 的 #842 exact-profile qualification gate 預設未啟用,讓尚未部署 qualification receipt lifecycle 的環境可繼續派工;這類 dispatch 會在 resolved_model_chain 留下 qualification: not-enforced。只有 host overlay model-identities.yaml 明確宣告 以下政策時,缺少/撤銷/不匹配的 receipt 才會在 spawn 前轉為 needs_human:

qualification_policy:
  sized_dispatch: enforce # disabled(預設)或 enforce

此 overlay 位於目前生效的 project config root(PSC_PROJECT_CONFIG_ROOT)。只有 operator overlay 的這個明示值會啟用 gate;packaged identity roster 不會啟用。啟用前 需先部署 #842 qualification lifecycle 並提供 exact-profile receipts。

PatchMUD #37 的報告由 Cortex consumer 驗證 schema、source revision、digest 與 exact profile key;Cortex 不 runtime import PatchMUD。此 worktree 使用固定 fixture 驗證 consumer,真實 immutable producer fixture/revision 與安裝後 launcher 仍需外部驗收。 欄位與擴充契約見 Execution profile 操作與相容性。

Merge 限制與 completion/restart

  • v1 只支援 preserving-commit 路徑:Candidate 必須是 refs/remotes/<remote>/<target_branch> 的 ancestor;squash/cherry-pick 視為不支援(保持 blocked 或 needs_human)。
  • 同一 dependency chain 必須使用同一 target branch,否則 fail-closed。
  • completion ordering 固定為「先 atomic 寫 CompletionRecord,再 atomic 標 Slice completed」。
  • work delivery CompletionRecord 另綁定 repo/work/run ID、workflow step IDs、Monitor snapshot/provider/source revisions、mapped issues、PR/OpenSpec/Todo refs、merge commit,以及 trusted preflight/current-HEAD delivery review/ForeignReview/merge-authorization refs;cached done 每次仍會重新讀取 fresh authority 與 remote closure。若WorkflowRun已是done/ship但CompletionRecord綁定舊authority,operator可執行同一個cortex work resume;Manager只會唯一選取該terminal run並重跑ship validator,不重開builder或dispatch任何workflow card,且僅在完整closure再次通過後原子刷新completion binding。
  • merge 前 Manager 會先以 atomic no-clobber+fsync 寫入唯讀 merge-authorized evidence file(authority digest、HEAD/tree、實際review kind/ref/hash、ForeignReview/preflight/checks hashes),再把 path/hash 綁回 run state。Crash replay 只接受 exact record;未經 Manager authorization 的 external merge 會進入 needs_human,不會直接閉合。
  • crash window(record 已寫、slice 尚未 completed)在 restart 後只會補完符合當前 target ancestry 的紀錄;不符合則維持 blocked。
  • 舊版無 schema_version / legacy done state 需先 clean-start(archive/remove 舊 jobs.json),不做 silent migration。

Operator actions 與 status / attention

cortex slice-action "$SLICE_ID" retry-build  --actor "$ACTOR"
cortex slice-action "$SLICE_ID" retry-verify --actor "$ACTOR"
cortex slice-action "$SLICE_ID" retry-review --actor "$ACTOR"
cortex slice-action "$SLICE_ID" abandon      --actor "$ACTOR"
cortex slice-action "$SLICE_ID" supersede    --actor "$ACTOR" \
  --reason "$REASON" --expected-binding-revision "$BINDING_REVISION"

# 明確淘汰沒有delivery side effect的舊WorkflowRun:
cortex work abandon "$WORK_ID" --repo "$REPO" --actor "$ACTOR" \
  --expected-run-id "$RUN_ID" --reason "$REASON"

# verify 卡停在 needs_human 時,operator 以 exact Candidate 提交 full-suite 摘要:
cortex work verify-attest "$WORK_ID" --repo "$REPO" \
  --expected-candidate "$CANDIDATE" --actor "$ACTOR" --payload verify-attest.json

# PR已建立且foreign review綁定current HEAD後,建立typed maintainer evidence:
cortex work review-attest "$WORK_ID" --repo "$REPO" --actor "$ACTOR" \
  --payload review-attest.json

verify-attest.json 接受 {"full_suite_command":"python -m pytest -q","result_summary":{"passed":5987,"failed":0}}。這是 operator 提交的測試命令與結果聲明,Manager 不會代跑該命令。Manager 只在 ongoing、停於 verify/needs_human、exact Candidate 相符、build 已通過且沒有 active job 時,寫入綁定 repo/work/run/authority/candidate 的 immutable evidence 並推進 review;review 與後續 ship gate 照常執行,review-attest 仍只處理 review。

review-attest.json接受{"verdict":"approved","summary":"...","findings":[]},並可選填 evidence_refs(只接受 {"kind":"operator-reproduction","ref":"<absolute path>","sha256":"<64 hex>"} 陣列)。path/hash 仍由 Manager 生成,caller 不得注入。若 work item 尚無 mapped PR,Manager 會在 verified_head == candidate_head 時先建立 pr_number: null 的 immutable maintainer evidence;後續 ship 建 PR 時再把它綁進 delivery gate。若已有 PR,Manager 仍會重讀 authenticated PR HEAD 並將 evidence 綁定 repo/work/run/authority/PR/candidate/actor。

同 HEAD 的 Copilot finding 已完成討論時,operator 可用 cortex work review-disposition <work-id> --repo <owner/repo> --actor <operator> --reason <理由> 提交續行裁決。Manager 只在 run/PR/latest Copilot review 仍綁同一 exact HEAD、fresh GitHub snapshot 的所有 review threads 均 resolved,且 thread snapshot 與裁決時一致時寫入 immutable evidence 並清除 needs-human facet;ship 保留原 finding/disposition 歷史。之後明示 cortex work resume 會再次執行既有 delivery gates。HEAD、review 或 thread snapshot 改變、仍有未 resolved thread,或沒有 operator disposition 時都會繼續阻擋;單獨 resolve thread 不授權 merge。

cortex work review-disposition "$WORK_ID" --repo "$REPO" --actor "$ACTOR" \
  --reason "$REASON"
cortex work resume "$WORK_ID" --repo "$REPO"
  • slice-action 一律透過 control request queue,由 daemon/manager 單一 writer 消費。
  • slice-action retry-build 若在 agent 真正 launch 前失敗,現在會保留既有 candidate、verification/review refs、builder/reviewer 綁定與 slice state;operator 修正原因後可直接再次重試,不必先從被清空的恢復態重新補 proof。
  • status snapshot 會一次列出所有 needs_human 事項(attention),包含 reason、evidence refs、ancestry 摘要與 next_actions,不需逐筆互動追問。

Broker cleanup(best-effort)

  • cortex reap-brokers 預設 dry-run。
  • --apply 必須搭配 --cwd-root <realpath>,只允許 scoped project 下的 broker 候選。
  • signal 前會重驗 ppid/start-time/cmdline/cwd;僅送 SIGTERM,不做 escalation。PID reuse / race 只提供 best-effort 安全保護。

Skill usage ledger 與 park janitor(#204)

append-only skill 執行事件記錄、cold-skill 偵測與 proposal-first park/restore,落於 ~/.agents/registry/(skill_usage.jsonl / skill_park.json / skill_park_proposals/, PSC_AGENTS_ROOT 可整族覆寫)。treated 為 paulsha_cortex.coordinator.skill_ledger / skill_janitor 兩個模組;manager.run_tick 提供 ledger_recorder / skill_janitor 注入點(與既有 reaper 同款「預設不啟用、例外不破壞 tick」模式),production 排程是否 啟用由呼叫端決定。

Operator 流程:

# 唯讀:每個 skill 的樣本數/最後使用時間/目前 cold/park 判定
cortex skill inspect

# 唯讀:列出 janitor 已開的 park proposal(pending/approved)
cortex skill list-proposals

# 手動跑一次 janitor tick:偵測 cold skill、只開 proposal,不動 park 狀態
cortex skill propose

# 核准既有 pending proposal,套用 park(需 operator 明確帶 --approved-by)
cortex skill approve-proposal <proposal_id> --approved-by "$ACTOR"

# 不經 proposal,直接手動 park 一個 skill(同樣需 --approved-by)
cortex skill park <skill_id> --reason "..." --approved-by "$ACTOR"

# 撤銷 park(可逆:只改 park 狀態,不動 skill 檔案或 ledger)
cortex skill restore <skill_id> --approved-by "$ACTOR"
  • class: core / class: emergency(paulsha_cortex/deck/schema.py 的 Card.card_class)在 cold 判定與所有 park 入口(janitor proposal、approve-proposal、手動 park) 都強制豁免,不可被 park。
  • cold 判定閾值(最低樣本數 / 觀測窗天數)為初始預設值,未經人類最終核可; 細節與調整方式見 docs/superpowers/plans/2026-08-07-skill-cold-threshold-defaults.md。
  • park 目前只記錄狀態(cortex skill inspect 可讀出),尚未接上任何 skill 選牌/router 路徑——deck combo 選牌目前沒有消費 park 狀態的機制,這條「park → 排除於自動選牌之外」預期由後續 shared read model(issue #139)承接。

Monitor registry merge

  • manual config:~/.agents/config/paulsha/project-cortex.yaml
  • shared hippo registry:~/.agents/config/paulsha/project-hippo.yaml
  • merge 規則:兩份 registry 以 realpath 去重,manual entry 優先保留 metadata;兩者皆缺時 cortex monitor 會直接報錯。

Path 契約

介面 預設路徑 環境變數
preflight command (delivery) 無預設(delivery 時必填) PSC_PREFLIGHT_CMD
digest delivery command 無預設(未設定時改寫檔案 outbox) PSC_DIGEST_DELIVERY_CMD
digest 檔案 outbox <coordinator_root>/digest/outbox 隨 PSC_COORDINATOR_ROOT 覆寫
control root 依 PSC_INSTANCE 讀 installer env;未安裝時為 ~/.agents/control(未 instance 化,見下方說明) PSC_CONTROL_ROOT
coordinator root ~/.agents/coordinator(未 instance 化,operator 域,見下方說明) PSC_COORDINATOR_ROOT
specs root ~/.agents/specs(未 instance 化,operator 域,見下方說明) PSC_SPECS_ROOT
run root 依 PSC_INSTANCE(預設cortex)讀installer env;未安裝時為~/.agents/run/<instance> PSC_RUN_ROOT(最高優先)
monitor state root ~/.agents/monitor PSC_MONITOR_STATE_ROOT
config root ~/.config/paulshaclaw PSC_CONFIG_ROOT
project config root ~/.agents/config/paulsha PSC_PROJECT_CONFIG_ROOT
repo root 指定 install 時的 git repo top-level(--repo-root);無 cwd 預設,未宣告即 fail-closed(見下方說明) PSC_REPO_ROOT
worktree root <repo>-worktrees sibling(由 repo root 推導,因此同樣 fail-closed) PSC_WORKTREE_ROOT

Multi-issue workflow build 階段將以 issue 清單中最小號碼作為主 branch,並始終以 run repository 作為 ScriptWorktreeCreator 的 git來源,以確保 builder worktree 在對應 repo 池內建立。

同一個 shared coordinator root 內,jobs.json 的 durable identity 取 canonical state path:resolve parent directory(non-strict)後再接回原始檔名,因此 symlink-directory spellings 共用同一把 transaction lock;state file 本身若是 symlink,則保留該檔名自己的 replace / sidecar 語意,不會跟著 target path 重新命名。

repo root 是 fail-closed 的(issue #612):paths.repo_root() 舊實作在 PSC_REPO_ROOT 未宣告時退回 Path.cwd(),而 manager daemon 的 WorkingDirectory 正是 operator 的真實 checkout——於是任何解析不出目標 repo 的呼叫都不是失敗,而是靜默落在錯的樹上(實測形態:相對 spec 路徑使 complete_tick 對真實 repo 跑 git fetch --no-tags origin main;同一族還有 worktree_reclaim 的 git worktree remove --force/prune 這類寫入動作)。現行契約:

  • 未宣告 PSC_REPO_ROOT 時 paths.repo_root() 拋 RepoRootUnresolvedError,不猜 cwd;需要「以當下目錄為準」的 operator 手動 CLI(cortex work gc、cortex deck compile)在呼叫點顯式帶 allow_cwd=True,--repo-root 一律優先。
  • spec 路徑必須是絕對路徑才能推導 repo root(autonomy._infer_repo_root);相對路徑與「向上找不到 git repo 根又未宣告 PSC_REPO_ROOT」皆 fail-closed 並帶 DiagnosticReason(spec-path-not-absolute/repo-root-unresolved)。cortex install service 會把 PSC_REPO_ROOT 寫進 unit 的 EnvironmentFile,正常部署不受影響。
  • 解析時機是 lazy 的(issue #633):coordinator.seams.ScriptWorktreeCreator 不在 __init__ 解析 repo/worktree pool,改由第一次真正要用時解析並 memoize。理由是 manager_daemon.run_loop → ensure_dispatcher() 會在建 dispatcher 當下實體化它,舊實作因此讓「EnvironmentFile 少一個變數」變成 Manager 啟動即崩 + Restart=on-failure crash-loop,而不是「派不了工」。fail-closed 的性質未放寬,只改時機:第一次 create() 仍原樣拋 RepoRootUnresolvedError,訊息逐字不變,且拒絕發生在任何磁碟動作之前。副作用是 systemctl is-active 全綠不代表派得了工——部署驗收要另外確認 paths.repo_root() 解析得出目標樹(runbook 第 7b 的 F2b)。

cortex install service 會把 PSC_CONTROL_ROOT 寫成 <agents_root>/control/<instance>(比照 PSC_RUN_ROOT 的 run/<instance> 模式),讓 manager.lock 天生 per-instance;paulsha_cortex/scripts/service-manager.sh 透過 cortex control lock-path(與 daemon 同一套 paulsha_cortex/config/runtime.py 解析鏈)取得 lock 路徑,不再自行硬寫預設值(issue #375)。PSC_PROJECT_CONFIG_ROOT 與 PSC_CONTROL_ROOT 皆屬 installer 的 managed path:每次 cortex install service 都會依目前 PSC_AGENTS_ROOT/instance 重新推導並覆寫,不會被既有值鎖住(issue #371);cortex doctor 的 managed-path-drift probe 可在尚未重跑 install 前就偵測到殘留的舊值。PSC_MANAGER_SPECS_DIR/PSC_COORDINATOR_ROOT/PSC_SPECS_ROOT 目前仍未 instance 化、也不在 installer 的 managed_env 之列(evaluate 後決定留待後續 follow-up;多 instance 共用同一 PSC_AGENTS_ROOT 時這三者會共用同一份 specs/coordinator 狀態)。

PSC_PREFLIGHT_CMD 必須為 typed argv,不可使用 shell wrapper。delivery preflight 常見設定範例:

export PSC_PREFLIGHT_CMD='python3 -m project_preflight'

實際值請替換為 repo 實際提供的 module / executable,且禁止用 sh -c 形式包裝。

PSC_DIGEST_DELIVERY_CMD 語法與限制比照 PSC_PREFLIGHT_CMD(typed argv、shlex.split 解析),差別是這個變數本身是選填的——未設定時 cortex digest emit 改寫檔案 outbox,設定後才改採該命令並停用檔案 outbox(兩者互斥、不 fallback):

export PSC_DIGEST_DELIVERY_CMD='/path/to/relay-script --channel ops'

cortex digest emit 會把 digest JSON 從 stdin pipe 給該命令;命令需自行解析 stdin 並負責實際投遞(例如轉發到 Slack/Telegram webhook),cortex 本身不內建任何外部通知整合。

共同前綴 PSC_AGENTS_ROOT 可一次覆寫 mutable/runtime roots。systemd unit 依宣告順序讀取 ~/.agents/core/runtime/<instance>.env 與固定 bootstrap ~/.agents/core/runtime/<instance>-manager.env;installer在後者持久化PSC_INSTANCE與PSC_AGENTS_ROOT。Interactive CLI以同一PSC_INSTANCE選取bootstrap env,不掃描猜測其他instance;symlink、malformed或relative root會fail-closed。installer重跑時,PY/PSC_REPO_ROOT會與env中PSC_REPO_IDENTITY身分戳記(由git remote origin正規化而來,SSH/HTTPS視為同一身分;非git/無origin則退回路徑指紋)比對;同身分、或戳記缺席(首次安裝/既有舊env遷移)才會更新並保留既有operator roots,跨身分變更須帶--rebind明確放行,否則fail-closed並於錯誤訊息附上env檔實際路徑(cortex doctor可在潛伏期內偵測此類漂移)。Monitor socket預設為$PSC_RUN_ROOT/project-monitor.sock,也可由project-cortex.yaml的monitor.socket_path覆寫;production MonitorSocketClient與service使用相同config解析。

誠實狀態表

面向 現況
persona enforcement standalone PR workflow(.github/workflows/persona-scope.yml)由 paulsha_cortex/persona/personas.yaml 的 enforcement 驅動,現為 enforce(#135;切換前以 python -m paulsha_cortex.persona.replay 回放近期已合併 PR 驗證零誤殺,見 docs/persona-scope-enforcement.md);coordinator verification 的 persona-scope 為 fail-closed gate
manager service install cortex install service 會 render / copy / enable,但不會 start;systemd 不可用時只落檔
coordinator runtime jobs / stat / ready / status 為讀取路徑;fanout / complete / tick / slice-action / work 走 control queue;舊低階 dispatch 已停用
deck 驗證 compile 只產生 dispatch: hold 骨架;verify 只檢查 produces glob 存在性,不驗內容
monitor registry project-cortex.yaml ⊍ project-hippo.yaml,realpath 去重且 manual 優先
skill 治理(#204) ledger/park state/janitor proposal 已落地,cortex skill 可操作;manager.run_tick 的 ledger_recorder/skill_janitor 是可注入 hook,預設不啟用(比照既有 reaper 慣例);park 狀態尚未接上任何選牌/router 消費點
依賴模型 僅 PyYAML;runtime 不依賴 paulsha-hippo

開發備註

  • repo 宣告 tier: shareable,所有範例與測試都必須維持去識別化。
  • repo policy 由 canonical .project-policy.yml 宣告,並 pin paulsha-conventions v1.0.17。
  • agent 慣例檔採 symlink 模式:AGENTS.md、GEMINI.md、.github/copilot-instructions.md 都指向 CLAUDE.md。
  • 測試不得打真實網路(#610):tests/network_guard.py 是 session-scope、預設啟用的 守衛,攔 socket 層(白名單 AF_UNIX 與 loopback)與 subprocess 層(git 的 transport subcommand 以 git ls-remote --get-url 解出 insteadOf 改寫後的實際 URL 再判本機性; gh/curl/wget/pip 等純網路 client 一律違規)。違規當場失敗並指名測試 nodeid, 即使受測程式用 except Exception: 吞掉例外,per-test 帳本仍會在 teardown 讓它失敗。 需要 remote 的測試一律用本機 fixture(git_origin 造 tmp bare origin,或注入假 provider/runner)。本機除錯要放行整場用 PSC_TEST_ALLOW_NETWORK=1;本質上需要網路的 整合測試標 @pytest.mark.network(預設排除於全套,--run-network 才跑)。
  • AF_UNIX socket 路徑不得掛在 TMPDIR 下(#608):Linux 的 sun_path 只有 108 bytes(含結尾 NUL,可用 107),而 tmp_path/tempfile.mkdtemp() 的長度由環境給。 凡是要 bind()/connect() 的路徑一律取自 tests/socket_fixtures.py 的短固定根 (pytest 用 conftest 的 socket_dir fixture,unittest.TestCase 用 make_short_socket_dir());工作區、設定檔、快照沒有長度上限,照舊留在 tmp_path。 production 端由 paulsha_cortex/monitor/socket_path.py 統一判定,超限時 fail closed 在 SocketPathTooLongError 並附 byte 數,不退化成「服務沒起來」或「socket 沒在聽」。
  • ledger gate 環境健壯性(#565 / #586 / #608 / #610 同族,一律 P1):Manager 的 gate ledger 對 candidate 重跑全套 pytest 是採信的硬 gate(#540),因此凡是「全套 pytest 的 結果會隨 host 環境形狀改變」的測試,污染的都是採信判斷——環境噪音會被讀成「這次 交付真的沒過」,讓合格 candidate 撞上 GateContradictionError。發現這類測試一律比照 上述四例 hermetic 化:污染/環境條件由測試自備,且必須附負控制(同一條路徑上真正的 失敗仍然要被記成失敗),否則「修好了」與「什麼都判成通過」無法區分。靜默 skip 同樣算 失敗——覆蓋消失而套件是綠的,比紅掉更難發現。

Version

套件版本以 repo 根目錄 VERSION 為單一真相源;bootstrap 期間維持 0.0.0,待後續 feature batch 合併後再依 flat profile 做 patch/minor bump。

原生架構圖驗收

架構圖使用原版 Archify 的 SVG 節點與方向箭頭,不再以 workflow-review 說明頁代替。Cortex 的七階段是同一 WorkflowRun 的展開,不是七個服務;主要 Candidate 修正回路維持 Manager/CAS 條件。標籤使用繁體中文,固定 Viewer UI 回退為英文。

開啟架構與工作流程 HTML;直接以瀏覽器開啟本機檔案。完整角色、gate、恢復契約及部署 unknown 保留在 facts.json;本圖不宣稱 runtime E2E 已通過。

About

agent 治理平面:manager 派工決策 + persona scope 護欄 + control 檔案契約控制面

Resources

Contributing

Security policy

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages