Fact:
paulsha-cortex是 work / WorkflowRun / Job / Slice lifecycle 的唯一 authority,負責 vendor-neutral dispatch、retry、review、delivery 與 completion;domain tools 只回傳 artifacts,不得改寫 lifecycle truth。
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
- 主產品 repo:產品與 orchestration 上游,之後可 pin 本 repo commit SHA 做遷移刀。
- paulsha-cortex:治理平面抽離包,提供
cortexCLI、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。
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
已驗收。
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是正式介面,但copilotCLI 目前沒有對應的非互動唯讀路徑(已用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(schemacortex-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 端驗簽——目前尚未實作,另議。
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.jsonregistry 與全部 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 已不存在時的
第三方復原用),兩者服務不同情境,互不重疊。
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 操作手冊。
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 決策契約)。
- shadow(
- 檔案存在且有效 → 建構真正的 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 進程內
的接線與設定檔消費」。
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-insufficientwait receipt 都會記;quota-config-invalid沒有可信 revision,維持null。selected_observation_state/selected_feasible:選中候選當時的額度 observation 狀態(unmanaged/known/unknown)與是否所有綁定 pool 都 sufficient——shadow 模式下候選即使unknown/不可行一樣會被 admit, 這兩個欄位讓投影面能忠實區分「confirmed 可派」與「shadow 下明知不可行 仍放行」。
這是唯讀投影新增的欄位,不改變 #839 的准入邏輯或 reservation 生命週期。
同一個 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。
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。
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 明示noneeffort;任何 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。
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 代替交付。
需求: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 .以下指令追的是 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 派工實務。
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 搬移,需重新建立入口。
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,
規則詳見需求交付總帳契約。
乾淨機器或新 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 核心步驟成功與否。
-
在被治理的目標 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 說明只涵蓋 一般檔案的改寫。 -
啟動 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> envPSC_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證明自然負載低於門檻。
- 預設行為變更:periodic runner 預設由固定 1.0 改為 CPU-aware(
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-templatepreflight、確定本次命令會由 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與 transientsystemd-run沒有同一份 template 加固面,仍使用原先依卡片契約導出的內層 sandbox argv。#823 headless session 的生命週期邊界:每個合法 headless
Popen嘗試(含systemd-run/systemd-template的 Manager-side client wrapper 與窄stdinretry)都帶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事件不代表指令已送達或被採納。 -
使用 Deck 先 dry-run,再 emit
dispatch: holdspecs: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 -
完成 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>"
-
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 -
日常查詢:
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-goinglabel 的 confirmed issue 才能 auto claim。 -
追蹤已送出的 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
-
用
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 --jsoncortex 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、Cortexexecution_profilekey、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 改為降級。) -
用
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/v1JSON,fallback log 寫入manager.log,不會安裝或修改 units。cortex service status會先讀 systemd units 與 bootstrap env,若尚未安裝但偵測到前景paulsha_cortex/scripts/service-manager.shlock,則回報 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 內容,並不再繼續後續步驟。 -
用
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。
- 用
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 cortexrecover 不提供 --allow-unsafe 等旁路旗標;需要專家級低階參數時仍使用既有 coordinator 命令。
-
用
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/OnUnitActiveSectimer 呼叫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 不是 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 啟動前直接失敗。
先用 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 \
--emitDeck 產生的 spec 預設為 dispatch: hold,不會立即派工。翻成 dispatch: auto 前,使用者應:
- 完成 Deck 列出的 interactive checklist。
- 確認
plan、depends_on、target_branch與verification契約,並依 auto dispatch 契約文件 補齊欄位。 - 執行 Deck 列出的 verify commands。
- 只將允許 manager 派工的 spec 改為
dispatch: auto。
不想直接操作 deck compile --emit 時,可改用:
cortex init-sample --task "example feature 實作" --change example-featureinit-sample 會維持 dispatch: hold,並直接列出 spec 檔名、plan / target_branch / verification 必補欄位,以及 deck verify 下一步指引。
Manager 只會派送「已翻 auto、有 plan、相依全部 completed」的 slice。不要使用低階 cortex dispatch;它因缺少 spec / verification metadata 已停用。
先確認 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 直接過濾。
# 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.servicecortex 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 fixturetests/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 失敗若已有 durableprovider_outcome,gate_reason會帶具名後綴(例如builder-failed-rate_limited、builder-failed-effort_not_supported、foreign-review-provider-launch_failed),而不是把所有 provider 層與 launch 層失敗壓平成同一句;每筆也帶目前的binding_revision,供單筆supersedeaction 做 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或關鍵字比對覆蓋。buildingslice 若沒有目前綁定的 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,直接沿用 #527blocking_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_VERSIONreceipt 保持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_humanrun(實測一次產出 24 個,把attention信噪比壓成 1:24)。現在改記在耐久的<coordinator_root>/not-claimable.json(schemacortex-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、明確的repoproject 歸屬(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_doneprovider 職責內。
Job exited 只代表 Agent process 以 exit code 0 結束,不代表任務交付完成。只有 Slice 通過 deterministic verification、必要的 foreign review,且 Candidate 已進入 target branch 後,才會變成 completed 並釋放下游 dependency。
先從 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。
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 使用。
| 層級 | 狀態 | 語意 |
|---|---|---|
| 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。
---
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
argvlist 由 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)。
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)。
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,不載入CandidateCLAUDE.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配置Claudereviewcapability時把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。
每次實際派工會在既有身分解析結果之外,建立 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 操作與相容性。
- 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-authorizedevidence 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/ legacydonestate 需先 clean-start(archive/remove 舊jobs.json),不做 silent migration。
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.jsonverify-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,不需逐筆互動追問。
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 安全保護。
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)承接。
- 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會直接報錯。
| 介面 | 預設路徑 | 環境變數 |
|---|---|---|
| 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-failurecrash-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_dirfixture,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 同樣算 失敗——覆蓋消失而套件是綠的,比紅掉更難發現。
套件版本以 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 已通過。