omnilane 0.42.8 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh-TW.md CHANGED
@@ -43,35 +43,133 @@ Gemini CLI** 之類。每一個都只接一個模型家族,所以你交代的每
43
43
 
44
44
  ## ⚡ 60 秒上手
45
45
 
46
- **最快的方式——用 npm 裝:**
46
+ 你本人坐在終端機前,現在就可以派工。
47
+
48
+ **1. 安裝。**
47
49
 
48
50
  ```bash
49
- npm i -g omnilane # 裝 CLI
50
- export OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1 # 你是操作者本人,不是模型
51
- omnilane route hardest-coding "修掉會間歇失敗的 auth token 更新測試"
52
- omnilane doctor # 看你手上有哪些 AI CLI / 金鑰
53
- omnilane ui start # 選配:在瀏覽器即時看派工
51
+ npm i -g omnilane
54
52
  ```
55
53
 
56
- **或 clone 整包**(拿到路由表與可自訂的技能):
54
+ 或是把專案抓下來,順便拿到可以自訂的路由表與技能檔:
57
55
 
58
56
  ```bash
59
57
  git clone https://github.com/Seraphim0916/omnilane && cd omnilane
60
- ./install.sh # 偵測你的 CLI、接好技能、說你的語言
61
- export OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1 # 你是操作者本人,不是模型
62
- omnilane route hardest-coding "修掉會間歇失敗的 auth token 更新測試"
58
+ ./install.sh # finds your CLIs, links the skill, speaks your language
59
+ ```
60
+
61
+ **2. 看看手上有什麼。** `doctor` 會列出 omnilane 找得到哪些模型 CLI 與 API 金鑰,讓你知道實際會跑哪一個。它不會改任何東西。
62
+
63
+ ```bash
64
+ omnilane doctor
65
+ omnilane list # the routing table this machine resolves
66
+ ```
67
+
68
+ **3. 表明你是操作者,然後派工。**
69
+
70
+ ```bash
71
+ export OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1
72
+ omnilane route hardest-coding "fix the flaky auth token refresh"
73
+ omnilane ui start # optional: watch jobs live in your browser
74
+ ```
75
+
76
+ > **為什麼要那行 export?** omnilane 每次派工都會拿「發問者的能力分數」去比對,所以派工時得說清楚是誰在問。真人說一次就好:`OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1`(或每次帶 `--operator-asserted-human`)。模型不能替自己這樣宣稱,它的身分是從啟動它的 CLI 讀出來的。兩者都沒有的話,派工會在建立任何工作之前就被拒絕,代碼是 `missing-caller-context`。
77
+
78
+ 真人用到這裡就夠了。下面這一節講更實用的用法:讓你的 AI 助理自己派工。
79
+
80
+ ## 🤖 讓你的 AI 助理來開 omnilane
81
+
82
+ 助理(Claude Code、Codex、Grok Build 或 Antigravity)會讀一份技能檔,裡面教它怎麼選通道、怎麼派工。每台機器做一次,共四步。
83
+
84
+ ### 第 1 步:把技能交給助理
85
+
86
+ `./install.sh` 會替它找到的每個 CLI 建好連結。要手動的話:
87
+
88
+ | 助理 | 做法 |
89
+ |---|---|
90
+ | Claude Code | `claude plugin marketplace add <本專案路徑>`,再 `claude plugin install omnilane@omnilane`(同時附上 `/route`、`/route-jobs` 與完工收件匣);或把 `skills/omnilane` 連結進 `~/.claude/skills/` |
91
+ | Codex | 把 `skills/omnilane` 連結進 `~/.codex/skills/` |
92
+ | Grok Build | `grok plugin install <本專案路徑> --trust` |
93
+ | Antigravity | `agy plugin install <本專案路徑>`(先用 `agy plugin validate <本專案路徑>` 檢查) |
94
+
95
+ ### 第 2 步:證明一次「每個 CLI 真的會選到你指定的模型」
96
+
97
+ 模型來派工時,只能派給這台機器**證明過**的目標:例如 `codex -m gpt-5.6-sol` 真的跑的是 Sol。這份證明是一個本機檔案,叫**傳輸覆蓋檔(transport overlay)**。安裝包裡不會附。沒有它,每條通道都會用 `runtime-mapping-unverified` 拒絕模型呼叫者,`omnilane doctor` 也會警告 `no overlay configured`。
98
+
99
+ 請在一般的桌面終端機裡建立。(用 ssh 登入的工作階段讀不到 CLI 登入用的鑰匙圈,會把每一家都報成沒登入。)
100
+
101
+ ```bash
102
+ cd "$(npm root -g)/omnilane" # or your clone
103
+ ROOT=~/.omnilane/transport-evidence/first-sweep
104
+ python3 scripts/lib/probe_sweep.py --root "$ROOT" # one tiny prompt per selector, about 55 calls
105
+ python3 scripts/lib/build_overlay.py --root "$ROOT"
106
+ cp "$ROOT/transport-contracts.local.json" ~/.omnilane/transport-contracts.local.json
107
+ echo 'export OMNILANE_AA_TRANSPORT_OVERLAY="$HOME/.omnilane/transport-contracts.local.json"' >> ~/.omnilane/local.sh
108
+ omnilane doctor | grep transport-overlay # PASS, with a count per vendor
109
+ ```
110
+
111
+ 沒登入的那一家會被標成 `unprobeable`,就只是維持未驗證,其他家照常可用。
112
+
113
+ ### 第 3 步:讓這份證明自己保持最新,不用你動手
114
+
115
+ 覆蓋檔是用雜湊值釘住每個 CLI 執行檔的,而**這些 CLI 會自己更新**,常常一週一次。更新之後,那一家的通道就會被拒,直到覆蓋檔重簽為止。`omnilane resign` 一個指令做完全部:找出哪裡變了、只重新探測那一家、檢查結果、換上新檔、送一筆真實派工確認,失敗就自動還原舊檔。
116
+
117
+ 它不是什麼都簽。變動過的 CLI 只有在「簽署者跟紀錄上的相同、而且裝在同一類位置」時,才會**無人值守**地重簽。所以先告訴它一次你接受哪些簽署者:
118
+
119
+ ```bash
120
+ omnilane resign --record-signers # once, right after Step 2
121
+ ```
122
+
123
+ 如果你每次更新後都會自己修補某家 CLI、再用 adhoc 重新簽署,也要告訴它一次(每家各一次);之後同一個安裝目錄裡的 adhoc 新版也會無人值守地重簽:
124
+
125
+ ```bash
126
+ omnilane resign --trust-adhoc claude # 只有你自己會把 claude 簽成 adhoc 時才需要
127
+ ```
128
+
129
+ 接著讓它每天跑一次。用什麼排程器都行,但一定要跑在**你的桌面登入工作階段裡**(CLI 需要鑰匙圈)。macOS 可以用 LaunchAgent:
130
+
131
+ ```bash
132
+ cat > ~/Library/LaunchAgents/dev.omnilane.resign.plist <<'EOF'
133
+ <?xml version="1.0" encoding="UTF-8"?>
134
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
135
+ <plist version="1.0"><dict>
136
+ <key>Label</key><string>dev.omnilane.resign</string>
137
+ <key>ProgramArguments</key><array><string>/bin/zsh</string><string>-lc</string><string>omnilane resign</string></array>
138
+ <key>StartCalendarInterval</key><dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict>
139
+ <key>StandardOutPath</key><string>/tmp/omnilane-resign.log</string>
140
+ <key>StandardErrorPath</key><string>/tmp/omnilane-resign.log</string>
141
+ </dict></plist>
142
+ EOF
143
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.omnilane.resign.plist
63
144
  ```
64
145
 
65
- > **那個 export 是做什麼的?** omnilane 會用呼叫者自己的能力分數來把關每一次派工,
66
- > 所以派工必須表明「是誰在問」。人類在終端機前只要設一次
67
- > `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1`,或每次帶 `--operator-asserted-human`。
68
- > 模型驅動 omnilane 時**不能替自己主張**這個旗標。它的身分會從啟動它的 CLI 旗標
69
- > (模型與強度)自動讀取,一般 session 什麼都不用帶;`omnilane whoami` 會把這個身分
70
- > 印成 `--caller-context FILE`。既沒主張、又讀不到身分的話,派工會在建立工作前就被
71
- > `missing-caller-context` 拒絕。
146
+ 本版實測過從桌面終端機執行 `omnilane resign`,包含一次真實的、無人值守的 Codex 自動升級重簽;上面這個 LaunchAgent 包裝只是範例,本身沒有實測過。請在你的機器上用 `launchctl kickstart gui/$(id -u)/dev.omnilane.resign` 確認。
147
+
148
+ `omnilane resign` 的結束碼:
149
+
150
+ | 結束碼 | 意思 | 你要做的事 |
151
+ |---|---|---|
152
+ | 0 | 沒有東西變動,或變動的都已重簽 | 不用做 |
153
+ | 10 | 只有 `--check` 會出現:有東西變了 | 執行 `omnilane resign` |
154
+ | 20 | 有一家需要你:簽署者是新的或沒紀錄、執行檔沒簽章或在本機被改過、換了安裝目錄,或供應商這次拒絕了上次通過的探測 | 讀訊息。它會印「稍後重試」,或印出你看過之後該執行的那一行 `omnilane resign --vendor V --approve V` |
155
+ | 30 | 重簽後的覆蓋檔沒通過真實派工,已還原成前一份 | 沒有東西壞掉,讀紀錄即可 |
156
+ | 2 | 沒有設定覆蓋檔 | 做第 2 步 |
157
+
158
+ 兩個限制要知道。簽署者檢查用的是 macOS 的程式碼簽章,所以在 Linux 上,每次 CLI 變動都會停在結束碼 20 等你 `--approve`。另外,沒有真正簽章的執行檔(例如在本機修補過的 CLI)一定會停下來等核准:沒有任何東西能證明它來自原廠,而這正是這項檢查存在的理由。
159
+
160
+ ### 第 4 步:在助理裡面試試看
161
+
162
+ 請你的助理執行 `omnilane whoami`。它應該回報自己是哪個模型、哪個強度,以及一個分數。接著請它派一件小事:「用 omnilane 讓 triage 通道數一數這個專案裡有幾個 TODO 註解」。
72
163
 
73
- > 第一次用?先跑 `omnilane doctor`——它會告訴你 omnilane 現在能接到哪些模型 CLI 與
74
- > API 金鑰,你就知道實際會跑什麼。
164
+ 如果被拒絕,拒絕訊息會說是哪一關沒過、該怎麼辦:
165
+
166
+ | `failed_gate` | 白話 | 怎麼修 |
167
+ |---|---|---|
168
+ | `caller-identity` | omnilane 看不出是哪個模型在問 | 讓它把 `omnilane whoami` 當成那次工具呼叫裡的**唯一一條指令**。Codex 尤其如此:後面接了 `; echo $?`、`&&` 或管線就讀不到 |
169
+ | `target-transport` | 這台機器還沒證明過那個目標,或 CLI 之後更新過 | `omnilane resign`(第 3 步) |
170
+ | `downward-ceiling` | 目標模型的分數比發問的模型高;模型只能平派或往下派 | 從拒絕訊息列出的 `eligible_lanes` 挑一條,或用更高的強度啟動助理 |
171
+
172
+ Codex 的排程喚醒既有對話串時不會記錄強度。omnilane 這時會把該呼叫者限制在它那個模型的最低分,而不是直接拒絕:便宜的通道照常可用,貴的通道會告訴你要哪個強度才派得動。
75
173
 
76
174
  ## 🧭 運作方式
77
175
 
@@ -141,37 +239,33 @@ flowchart LR
141
239
  - 點標準模型別名(例如 Opus)時,會鎖定技能表裡的確切模型家族。明確目標
142
240
  不存在或 CLI 不可用時會清楚失敗,不會暗中換廠商或模型家族。
143
241
 
144
- ## 原生優先派工,保留終端相容性
242
+ ## 用助理自己的子代理
145
243
 
146
- 模型路由與執行器(executor)分開判定。`--executor auto` 是預設:只有呼叫端明示的
147
- 結構化能力全部相符,才選原生子代理(native agent);一般終端沒有能力脈絡,
148
- 就保留既有 CLI。`--executor cli` 強制原流程;`--executor native` 遇到能力缺漏或不符就報錯。
149
- 同廠商不代表同模型;明確指定的廠商、模型及推理強度都保留。原生不符時,
150
- 自動模式會說明 CLI 原因,只使用同一個已解析目標,不換廠商或模型。
244
+ omnilane 預設把工作交給廠商的命令列工具。當該做這件事的模型就是助理**自己那一家**的時候,再繞出去叫另一個 CLI 是多走一趟:多一次登入、多一個程序,那個 CLI 一更新又多一個會壞的地方。多數助理自己就能開子代理,omnilane 可以改成替它規劃這種工作。有兩種做法。
151
245
 
152
- ```sh
153
- # 一般終端的預覽:不建立工作,也不呼叫供應商。
154
- omnilane route --executor auto --dry-run hardest-coding "檢查這次變更"
246
+ **跟助理跑一模一樣東西的工人(`--inherit`)。** 助理開子代理時*不指定模型、也不指定強度*,工人就是呼叫者的分身。分身不可能比本尊強,而 omnilane 的分數檢查要防的就只有這件事,所以這條路不需要廠商 CLI、不需要傳輸覆蓋檔;呼叫者的強度讀不到、或覆蓋檔過期時,它照樣能用。
155
247
 
156
- # 呼叫端依工具契約準備共享繼承能力 JSON;完整格式見下方文件。
157
- omnilane route --executor native --native-context /absolute/capability.json --workdir /absolute/repo hardest-coding "檢查這次變更"
158
- # 接著由呼叫端啟動原生代理、等待結果,再登錄真實證據。
248
+ ```sh
249
+ omnilane native-context --workdir /absolute/repo --inherits-caller-runtime # prints a capability file
250
+ omnilane route --inherit --native-context /path/printed/above --workdir /absolute/repo triage "Count the TODO comments"
251
+ # -> a PENDING handoff (JSON). The assistant now starts its own sub-agent with no
252
+ # model argument, checks the result, and records it:
159
253
  omnilane jobs --json complete-native JOB_ID /absolute/completion.json
160
254
  omnilane jobs --json status JOB_ID
161
- omnilane jobs --json result JOB_ID
162
- omnilane jobs --json list --status pending
163
255
  ```
164
256
 
165
- 原生路由輸出的是「等待執行」交接 JSON,不會從 shell 啟動原生代理,也不代表任務成功。
166
- Codex `collaboration.spawn_agent` 沒有沙箱、工具或工作目錄限制參數,會繼承父代理的工具與檔案系統權限。要求與同一能力列都必須明示 `shared-inherited`,工具陣列留空;`advise`/`work` 與工作目錄只是任務意圖,不是作業系統隔離。要求硬隔離時,自動模式保留同模型 CLI,強制原生則失敗。
257
+ 老實的部分:交接單上標著 `satisfies_lane_target: false`。通道在這裡只是「這是哪一類工作」的標籤。這樣做出來的結果是「助理自己的子代理」做的,絕不是「hardest-coding 那個模型」做的;需要比呼叫者更強模型的通道,照樣會被拒。`--inherits-caller-runtime` 是助理自己聲明「我的子代理工具就是這樣運作」,omnilane 觀察不到。各家目前已知的狀況:
167
258
 
168
- 呼叫端以精確模型與推理強度啟動工作,最後登錄實際代理 ID、模型/推理強度/廠商/框架/後端、成敗、公開結果與證據。明示模型覆寫時使用 `fork_turns: "none"` 或有限的正整數歷史,不得搭配 `fork_turns: "all"`。路由已明示選中能力列中的精確模型時,可省略未知的呼叫端目前模型。重複登錄會被擋下;原生取消只改工作狀態,不發程序訊號,已啟動的代理由呼叫端另外停止。
259
+ | 助理 | 不帶模型參數的子代理 |
260
+ |---|---|
261
+ | Claude Code | 官方文件寫明會用主對話的模型,強度沿用工作階段(除非代理定義另有設定)。內建 general-purpose 代理、且沒設 `CLAUDE_CODE_SUBAGENT_MODEL` 時成立。本版完整跑通過 |
262
+ | Codex | `collaboration.spawn_agent` 不帶模型、不帶強度。本版完整跑通過 |
263
+ | Grok Build | 文件寫明沿用上層的模型(內建 `general-purpose` 代理是 `model: inherit`);強度沒有文件說明。本版沒跑過 |
264
+ | Antigravity | `agy` 1.2.7 找不到子代理工具。不適用 |
169
265
 
170
- 背景、持久、即時、具名 CLI 工作階段、sysops、不支援的隔離、投票/仲裁及多輪路徑
171
- 仍走 CLI。原生只整合清單、狀態、結果、取消與完成登錄,未接 CLI 等待、重試、
172
- 信箱或目標迴圈。原生協定需要 Python 3.9+;一般終端 CLI 保留相容。
173
- 測試替身不等於真實原生驗收;主機 AGENTS 管理區塊只由父代理審查後同步。
174
- 詳見[能力與完成格式、完整範例及限制](docs/native-executor.md)。
266
+ **助理的工具能指定的特定模型。** 把工具真正接受的東西寫進能力聲明檔(從 `omnilane native-context` 產生的檔開始,每組確切的模型與強度加一列),再用一般的 `omnilane route` 帶上 `--native-context FILE`。只有某一列完全相符時 omnilane 才會用子代理:模型、強度、模式、工作目錄、工具、隔離方式、生命週期都要對上。同一家廠商不等於同一個模型,也不會從你裝了哪些 CLI 去猜。`--executor native` 不符就失敗、不回退;`--executor cli` 強制走外部 CLI。如果同廠商的目標只因為沒給檔案而走了 CLI,派工現在會明說。
267
+
268
+ 不管哪一種,子代理都共用助理的工具與檔案系統:沒有作業系統層級的沙箱,`advise`/`work` 是意圖,不是強制。背景、常駐、具名對話串、多輪、投票與 `sysops` 工作仍走 CLI。協定處理需要 Python 3.9 以上。結構定義、完成檔、代理重用與取消:見 [docs/native-executor.md](docs/native-executor.md)。
175
269
 
176
270
  <details>
177
271
  <summary><b>模型角色指引:仍須派工</b></summary>
@@ -597,6 +691,39 @@ codex 記在 session rollout,agy 寫進 `cli.log`。這是 CLI 自己抄的訂
597
691
 
598
692
  ## 📜 版本歷程
599
693
 
694
+ ## v0.44.0 新功能
695
+
696
+ - **你自己修補過的 CLI 也能無人值守重簽。** 如果本機有個步驟會在每次更新後修補某家 CLI、再用 adhoc 重新簽署,以前簽署者檢查會把每一次這種更新都擋下來等 `--approve`。現在對該家跑一次 `omnilane resign --trust-adhoc VENDOR`,同一個安裝目錄裡的 adhoc 新版就會跟同簽署者的更新一樣,走每日 `omnilane resign` 自動重簽。未簽章的執行檔、換了目錄的 adhoc、其他廠商,仍然會停下來等你。信任記在覆蓋檔上,之後任何一家重簽都會保留,而且是操作者動作,模型不會執行。四家都適用。
697
+ - **登入過期會叫你「先登入」,不再說「稍後重試」。** `Failed to authenticate`、`OAuth session expired`、`Invalid API key`、`Unauthorized`、`401` 現在都會把該家標成無法探測,被擋下來的訊息會叫你先登入。
698
+ - 升級:`npm i -g omnilane@0.44.0`。若是從 0.42.x 升上來,另外跑一次 `omnilane resign --record-signers`(見 0.43.0 說明)。
699
+
700
+ ## v0.43.1 新功能
701
+
702
+ 請裝這一版,不要裝 0.43.0。0.43.0 的 `build_overlay.py` 與 `probe.py` 在 Python 3.9 上一匯入就會出錯,導致該版本上首次安裝的覆蓋檔建立步驟與 `omnilane resign` 無法執行。其餘沒有變動,下面 0.43.0 的說明全部適用。升級:`npm i -g omnilane@0.43.1`,然後執行一次 `omnilane resign --record-signers`。
703
+
704
+ ## v0.43.0 新功能
705
+
706
+ 十天之內,0.42.x 有四次把所有模型呼叫者全部拒絕,每一次都是因為 omnilane 管不到的事實:啟動器改了名、runner 腳本改了卻沒重簽、四家廠商 CLI 在同一週各自更新、Codex 排程不記錄強度。每一件都變成「什麼都派不出去」。這一版把它們各自縮小成講得清楚的結果,最常見的那一種還會自己修好。
707
+
708
+ - **廠商 CLI 更新了?`omnilane resign`。** 它會找出哪裡變了、只重探那一家、檢查結果、換上去、用一筆真實派工確認,失敗就還原舊的覆蓋檔。只有新執行檔的簽署者相同、位置也相同時,才會**無人值守**地重簽;其他情況會停下來,印出你該執行的那一行 `--approve` 指令。先跑一次 `omnilane resign --record-signers`,再把 `omnilane resign` 排成每天執行,之後 CLI 更新就不再是你的事。已用一次真實的 Codex 自動升級(0.155.0 → 0.155.1)驗證:不需核准、映射全數保留、結束碼 0。
709
+ - **拒絕訊息會告訴模型該怎麼辦。** 每一筆被拒的派工都帶著 `failed_gate`、`reason`、`next_command`、`required_caller_effort`,以及 `eligible_lanes`(這個呼叫者現在*派得動*的通道)。
710
+ - **沒記錄強度改成縮小範圍,不再全拒。** Codex 心跳排程會被限制在它那個模型的最低分,而不是每條通道都拒。便宜的通道照常可用,貴的會說需要哪個強度。
711
+ - **助理自己的子代理。** `omnilane native-context` 會寫出以前得手寫的能力聲明檔;`omnilane route --inherit` 規劃一個「呼叫者分身」工人:不經外部 CLI、不看覆蓋檔、連呼叫者身分讀不到時也能用,而且老實標明*不是*該通道的目標模型。已在 Claude Code 與 Codex 桌面版完整跑通。
712
+ - **Codex:每次工具呼叫只下一條 omnilane 指令。** `omnilane whoami; echo $?` 讀不到身分,單獨的 `omnilane whoami` 讀得到。拒絕訊息現在會直接這樣提示。
713
+ - **doctor 看得到搬了家的 CLI**(新版裝在舊檔旁邊),完全沒有覆蓋檔時會警告並附上步驟。
714
+ - **技能檔與教學重寫。** 技能檔現在是模型照著走的五個步驟;這份 README 一步步帶你讓助理來開 omnilane。
715
+ - **限制。** 無人值守重簽靠的是 macOS 程式碼簽章;在 Linux 上,以及任何沒簽章或在本機改過的 CLI,每次更新都會要你 `--approve`。`--inherit` 還沒在 Grok Build 裡跑過,Antigravity 沒有提供子代理工具。完整細節見 [CHANGELOG](CHANGELOG.md)。
716
+ - **升級。** `npm i -g omnilane@0.43.0`,然後執行一次:`omnilane resign --record-signers`。
717
+
718
+ ## v0.42.9 新功能
719
+
720
+ - **經啟動器帶起的 Codex 桌面版。** ChatGPT.app 若透過 codex-profile-switch 啟動 app-server,
721
+ 程序名稱是 `codex-modified`;0.42.8 只找名為 `codex` 的程序,會直接跳過它,該對話串的每一次派工
722
+ 都被拒為 `missing-caller-context`。現在認得這個名稱;同目錄的 `codex-code-mode-host` 仍不視為 CLI。
723
+ - **實機驗收。** 2026-09-13 在經啟動器帶起的 Codex 桌面版對話串執行 `whoami`,退出 0,
724
+ 讀出 `codex/gpt-5-6-sol-medium (score 46)`,來源是 `codex-modified` 程序。
725
+ 升級:`npm i -g omnilane@0.42.9`。
726
+
600
727
  ## v0.42.8 新功能
601
728
 
602
729
  - **Codex 本輪身分。** `app-server` 一律忽略啟動參數的模型與強度預設值。
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.42.8
1
+ 0.44.0
package/bin/omnilane CHANGED
@@ -60,6 +60,30 @@ case "$cmd" in
60
60
  }
61
61
  exec python3 "$REPO/scripts/lib/caller_identity.py" "$@"
62
62
  ;;
63
+ native-context)
64
+ command -v python3 >/dev/null 2>&1 || {
65
+ echo "omnilane: native-context requires Python 3.9 or newer" >&2
66
+ exit 1
67
+ }
68
+ exec python3 "$REPO/scripts/lib/native_context.py" "$@"
69
+ ;;
70
+ resign)
71
+ command -v python3 >/dev/null 2>&1 || {
72
+ echo "omnilane: resign requires Python 3.9 or newer" >&2
73
+ exit 1
74
+ }
75
+ if [[ -z "${OMNILANE_AA_TRANSPORT_OVERLAY:-}" ]]; then
76
+ # local.sh is where a host records its overlay; dispatch reads it the same way.
77
+ OMNILANE_AA_TRANSPORT_OVERLAY="$(
78
+ set +u
79
+ home="${OMNILANE_HOME:-$HOME/.omnilane}"
80
+ [[ -f "$home/local.sh" ]] && . "$home/local.sh" 2>/dev/null
81
+ printf '%s' "${OMNILANE_AA_TRANSPORT_OVERLAY:-}"
82
+ )"
83
+ export OMNILANE_AA_TRANSPORT_OVERLAY
84
+ fi
85
+ exec python3 "$REPO/scripts/lib/resign.py" "$@"
86
+ ;;
63
87
  benchmark)
64
88
  command -v python3 >/dev/null 2>&1 || {
65
89
  echo "omnilane: benchmark requires Python 3.9 or newer" >&2
@@ -97,6 +121,11 @@ omnilane — one routing table, every harness
97
121
  omnilane release-audit [flags] offline, read-only release gate
98
122
  omnilane doctor [--json] [--strict] [--probe V] local health / opt-in live probe
99
123
  omnilane whoami caller-context file for the CLI this runs under
124
+ omnilane native-context [--workdir DIR] [--mode M] [--inherits-caller-runtime]
125
+ capability file that lets dispatch use this harness's own sub-agents
126
+ (--vendor V --model M: the host's statement when whoami cannot read it)
127
+ omnilane resign [--check] [--vendor V] [--approve V] [--record-signers] [--trust-adhoc V]
128
+ re-probe and re-sign the transport overlay after a CLI update
100
129
  omnilane benchmark [--json] [--run] [--vendor V] fixed quality/cost comparison
101
130
  omnilane ui start|status|url|stop
102
131
  omnilane configure interactive lane menu
@@ -53,7 +53,7 @@ _omnilane() {
53
53
  local command="${words[2]:-}" sub sub_index=3
54
54
  local -a lanes ids
55
55
  if (( CURRENT == 2 )); then
56
- _values 'command' version list route dispatch jobs doctor whoami benchmark release-audit ui configure completion help
56
+ _values 'command' version list route dispatch jobs doctor whoami native-context resign benchmark release-audit ui configure completion help
57
57
  return
58
58
  fi
59
59
  case "$command" in
@@ -58,7 +58,7 @@ _omnilane() {
58
58
  prev="${COMP_WORDS[COMP_CWORD-1]:-}"
59
59
  command="${COMP_WORDS[1]:-}"
60
60
  if [[ "$COMP_CWORD" -eq 1 ]]; then
61
- words="version list route dispatch jobs doctor whoami benchmark release-audit ui configure completion help"
61
+ words="version list route dispatch jobs doctor whoami native-context resign benchmark release-audit ui configure completion help"
62
62
  else
63
63
  case "$command" in
64
64
  route|dispatch)
@@ -11,6 +11,8 @@ complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect ba
11
11
  complete -c omnilane -f -n __fish_use_subcommand -a mcp -d 'MCP stdio server'
12
12
  complete -c omnilane -f -n __fish_use_subcommand -a doctor -d 'read-only health report'
13
13
  complete -c omnilane -f -n __fish_use_subcommand -a whoami -d 'caller-context file for the launching CLI'
14
+ complete -c omnilane -f -n __fish_use_subcommand -a resign -d 're-probe and re-sign the transport overlay'
15
+ complete -c omnilane -f -n __fish_use_subcommand -a native-context -d 'capability file for this harness sub-agents'
14
16
  complete -c omnilane -f -n __fish_use_subcommand -a benchmark -d 'fixed quality/cost comparison'
15
17
  complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
16
18
  complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
@@ -88,6 +88,65 @@ workdir is task context, not an OS boundary. Requests for hard `read-only`,
88
88
  native jobs: `auto` stays on the same resolved model through CLI, while forced
89
89
  native fails closed.
90
90
 
91
+ ### Generating the capability file
92
+
93
+ `omnilane native-context [--workdir DIR]... [--mode advise|work]... [--harness NAME]
94
+ [--inherits-caller-runtime] [--out FILE]` writes a capability file for the
95
+ harness it runs under and prints the path. Vendor, current model and current
96
+ effort are read the way `omnilane whoami` reads them; nothing is inferred from
97
+ installed CLIs. It emits one capability row: the caller's own model at its own
98
+ effort. A caller whose effort is unrecorded gets `efforts: ["unverified"]`, which
99
+ matches no lane target, so that file serves `--inherit` only. Rows for other
100
+ models are the host's to add, from its real agent-tool contract.
101
+
102
+ `inherits_caller_runtime: true` is written only when the host passes
103
+ `--inherits-caller-runtime`. It is the host's statement that its sub-agent tool,
104
+ given no model override, runs the caller's model and effort.
105
+
106
+ What this release has and has not verified about each harness's sub-agent tool:
107
+
108
+ | Harness | Sub-agent tool | Status |
109
+ |---|---|---|
110
+ | Codex | `collaboration.spawn_agent`; a model override requires `fork_turns: "none"` or a bounded count | documented above from the tool contract; inheritance without an override is host-asserted |
111
+ | Claude Code | `Agent` tool; optional `model` override | documented (code.claude.com/docs/en/sub-agents, read 2026-09-20). Model resolves in this order: the per-invocation `model` parameter, the agent definition's `model` frontmatter, `CLAUDE_CODE_SUBAGENT_MODEL`, then the main conversation's model. Effort: the definition's `effort` frontmatter overrides the session level and its default is "inherits from session". So a host may pass `--inherits-caller-runtime` only when it spawns with no `model` argument, the agent type's definition sets neither `model` (other than `inherit`) nor `effort`, and `CLAUDE_CODE_SUBAGENT_MODEL` is unset; the built-in general-purpose agent meets the definition part. Two full `--inherit` cycles on 2026-09-20 under those conditions reported the parent's exact model. The worker cannot see its own effort, so completion reports `runtime.effort: "unknown"`; effort inheritance rests on the documentation, not on an observation |
112
+ | Grok Build | `spawn_subagent`; agent types `general-purpose`, `explore`, `plan` and user-defined ones | model: documented in the subagent guide embedded in grok 1.0.34 — "By default a subagent inherits the parent session's model"; only `[subagents.models].<agent>` in the config (highest priority) or the agent definition's `model` overrides that, and the bundled `general-purpose` definition says `model: inherit`. Effort: a definition may carry an `effort` override; no statement of the default was found, so effort inheritance is not verified. No `--inherit` cycle has been run in Grok |
113
+ | Antigravity | — | `agy` 1.2.7 exposes `--agent`, `--model` and `--effort` for the session and its help names no sub-agent spawning surface; not verified, and no `--inherit` cycle has been run |
114
+
115
+ ### Inherited worker (`--inherit`)
116
+
117
+ `dispatch.sh --inherit --native-context FILE <lane> "<task>"` plans a native
118
+ worker spawned with no model override. `aa_policy.decide_inherited` allows it
119
+ with code `native-inherited-allowed`: the worker runs the caller's runtime, so
120
+ its score equals the caller's and the downward rule holds by construction, with
121
+ or without a recorded effort. No target configuration is resolved
122
+ (`target_config_id: null`, `target: {"inherit": true, ...}`), the transport
123
+ overlay is not consulted, and there is no CLI fallback: a host that cannot take
124
+ it gets `native-inherit-unavailable` with `failed_gate: "native-capability"` and
125
+ the `omnilane native-context` command. The plan carries `inherit: true`,
126
+ `effort: "inherited"`, `worker_contract.model_override: false`,
127
+ `inherit_caller_runtime: true` and `satisfies_lane_target: false`. The capability
128
+ file must set `inherits_caller_runtime: true`, name the caller's vendor and
129
+ model, and hold a new-agent row for that model matching mode, workdir, tools,
130
+ `shared-inherited` and `single-shot`; effort is deliberately not matched.
131
+
132
+ When no caller identity reaches the gate, the file's `vendor` and `current_model`
133
+ stand as the host's statement and the decision is
134
+ `native-inherited-unverified-caller` (`caller_kind: "model-unverified"`,
135
+ `caller_identity_verified: false`, `caller_identity_source: "host-asserted"`). It
136
+ has no `effective_ceiling`, publishes neither `aa-authorizer.json` nor
137
+ `aa-child-context.json`, and `jobs status` shows `caller_identity_verified: false`
138
+ and `satisfies_lane_target: false`. A file with no `current_model` is refused as
139
+ `missing-caller-context`. `omnilane native-context --vendor V --model M` writes
140
+ such a file only when the identity cannot be read or resolved, marks it
141
+ `caller_identity_verified: false`, and exits 2 if the statement contradicts an
142
+ identity it can read. This exists for a caller that cannot be read, for example a
143
+ codex command wrapped in `;`, `&&` or a pipe, where the shell codex started stays
144
+ between codex and the command and carries no `CODEX_THREAD_ID` of its own; run
145
+ alone, the same command is read. Lane dispatch from an unread caller is refused.
146
+ Completion checks vendor, model and harness; `runtime.effort` is whatever the
147
+ host observed. A human operator, an unidentified caller, `--vendor`/`--model`/
148
+ `--effort`/`--target-config`, and every CLI-only lifecycle are refused.
149
+
91
150
  Native supports only a caller-supervised single task. `--background`, explicit
92
151
  `--live` / `--single-shot`, `--thread`, `sysops`, explicit/environment whole-job
93
152
  or idle watchdogs, vote/multi-round and `exec` arbitration paths stay CLI or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.42.8",
3
+ "version": "0.44.0",
4
4
  "description": "One routing table, every harness — classify subtasks into lanes and delegate each lane through a compatible caller-owned native agent or vendor CLI.",
5
5
  "bin": {
6
6
  "omnilane": "bin/omnilane"
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "omnilane",
4
- "version": "0.42.8",
4
+ "version": "0.44.0",
5
5
  "description": "One routing table, every harness: classify subtasks into lanes and delegate through compatible caller-owned native agents or vendor CLIs with exact-AA downward policy and supervised jobs."
6
6
  }
@@ -35,7 +35,7 @@ MODE="advise"; WORKDIR="$PWD"; BACKGROUND=0; DRY_RUN=0
35
35
  OVERRIDE_VENDOR=""; OVERRIDE_MODEL=""; OVERRIDE_EFFORT=""; OVERRIDE_TIMEOUT=""
36
36
  OVERRIDE_JOB_TIMEOUT=""; OVERRIDE_IDLE_TIMEOUT=""; SESSION_REQUEST="auto"
37
37
  THREAD_NAME=""; THREAD_MODE=""; THREAD_ID=""; THREAD_TURN=""; THREAD_CREATED=""
38
- EXECUTOR="auto"; NATIVE_CONTEXT=""; RESOLVE_WITH_CONTEXT=0
38
+ EXECUTOR="auto"; NATIVE_CONTEXT=""; RESOLVE_WITH_CONTEXT=0; INHERIT=0
39
39
  SELECTED_EXECUTOR="cli"; EXECUTOR_REASON="no-native-context"
40
40
  AA_POLICY_FILE="${OMNILANE_AA_POLICY_FILE:-$OMNILANE_REPO/config/aa-model-policy.json}"
41
41
  AA_CALLER_CONTEXT="${OMNILANE_AA_CALLER_CONTEXT:-}"
@@ -67,6 +67,8 @@ flags:
67
67
  before any provider call or job state
68
68
  --executor auto|native|cli caller-owned native handoff or legacy CLI
69
69
  --native-context FILE explicit JSON capabilities; native is not a binary
70
+ --inherit native worker on the caller's own model and effort; resolves no
71
+ lane target, needs --native-context with inherits_caller_runtime
70
72
  --caller-context FILE exact model caller identity plus inherited ceiling
71
73
  --operator-asserted-human explicit AA model-ceiling exemption; assertion only
72
74
  --aa-policy FILE frozen AA policy registry (default: repo config)
@@ -368,6 +370,18 @@ routing_candidate_available() {
368
370
  fi
369
371
  }
370
372
 
373
+ aa_print_refusal() {
374
+ # Refusal JSON on stderr, with the lanes this caller can still reach appended.
375
+ local helper="$OMNILANE_REPO/scripts/lib/aa_lanes.py"
376
+ if [[ -n "$AA_LAST_DECISION" && -r "$helper" ]] && command -v python3 >/dev/null 2>&1; then
377
+ printf '%s\n' "$AA_LAST_DECISION" | python3 "$helper" --registry "$AA_POLICY_FILE" --lane "${LANE:-}" \
378
+ --routing "$OMNILANE_HOME/routing.local.yaml" --routing "$OMNILANE_REPO/routing.yaml" >&2 \
379
+ || printf '%s\n' "$AA_LAST_DECISION" >&2
380
+ else
381
+ printf '%s\n' "$AA_LAST_DECISION" >&2
382
+ fi
383
+ }
384
+
371
385
  aa_policy_decide() {
372
386
  # vendor model effort [target-config] -> structured JSON in AA_LAST_DECISION
373
387
  local vendor="$1" model="$2" effort="$3" target_config="${4:-}" rc=0
@@ -712,6 +726,7 @@ while [[ $# -gt 0 ]]; do
712
726
  }
713
727
  SESSION_REQUEST="single-shot"; shift ;;
714
728
  --dry-run) DRY_RUN=1; shift ;;
729
+ --inherit) INHERIT=1; shift ;;
715
730
  --operator-asserted-human)
716
731
  AA_OPERATOR_ASSERTED_HUMAN=1; shift ;;
717
732
  --mode|--workdir|--vendor|--model|--effort|--timeout|--job-timeout|--idle-timeout|--thread|--executor|--native-context|--caller-context|--aa-policy|--target-config|--transport-overlay)
@@ -823,6 +838,34 @@ case "$EXECUTOR" in
823
838
  esac
824
839
 
825
840
  CHAIN="$(raw_lane_line "$LANE")" || { echo "omnilane: unknown lane '$LANE' (try --list)" >&2; exit 2; }
841
+ if [[ "$INHERIT" -eq 1 ]]; then
842
+ # A worker that inherits this caller's own model and effort runs inside the
843
+ # harness: no lane target is resolved, no vendor CLI or transport overlay is
844
+ # involved, and the lane is only a label for what the work is.
845
+ [[ -z "$OVERRIDE_VENDOR$OVERRIDE_MODEL$OVERRIDE_EFFORT$AA_TARGET_CONFIG" ]] || {
846
+ echo "omnilane: --inherit takes no --vendor, --model, --effort or --target-config; it overrides nothing" >&2
847
+ exit 2
848
+ }
849
+ [[ "$EXECUTOR" != "cli" ]] || { echo "omnilane: --inherit is native only" >&2; exit 2; }
850
+ command -v python3 >/dev/null 2>&1 || { echo "omnilane: native protocol requires Python 3" >&2; exit 2; }
851
+ INHERIT_TIMEOUT="${OVERRIDE_TIMEOUT:-${OMNILANE_TIMEOUT:-600}}"
852
+ [[ "$INHERIT_TIMEOUT" =~ ^[1-9][0-9]*$ ]] || {
853
+ echo "omnilane: invalid timeout (want a positive integer of seconds)" >&2; exit 2
854
+ }
855
+ INHERIT_ARGS=(route --inherit --home "$OMNILANE_HOME" --executor native --lane "$LANE"
856
+ --workdir "$WORKDIR" --mode "$MODE" --task="$TASK" --session "$SESSION_REQUEST"
857
+ --thread "$THREAD_NAME" --policy "$AA_POLICY_FILE" --timeout "$INHERIT_TIMEOUT"
858
+ --job-timeout "${OVERRIDE_JOB_TIMEOUT:-}" --idle-timeout "${OVERRIDE_IDLE_TIMEOUT:-}")
859
+ [[ -z "$NATIVE_CONTEXT" ]] || INHERIT_ARGS+=(--context "$NATIVE_CONTEXT")
860
+ if [[ "$AA_OPERATOR_ASSERTED_HUMAN" == "1" ]]; then
861
+ INHERIT_ARGS+=(--operator-asserted-human)
862
+ elif [[ -n "$AA_CALLER_CONTEXT" ]]; then
863
+ INHERIT_ARGS+=(--caller-context "$AA_CALLER_CONTEXT")
864
+ fi
865
+ [[ "$BACKGROUND" -eq 0 ]] || INHERIT_ARGS+=(--background)
866
+ [[ "$DRY_RUN" -eq 0 ]] || INHERIT_ARGS+=(--dry-run)
867
+ exec python3 "$OMNILANE_REPO/scripts/lib/native.py" "${INHERIT_ARGS[@]}"
868
+ fi
826
869
  if [[ -n "$OVERRIDE_VENDOR" ]]; then
827
870
  if resolve_chain "$CHAIN" "$OVERRIDE_VENDOR"; then
828
871
  :
@@ -835,7 +878,7 @@ if [[ -n "$OVERRIDE_VENDOR" ]]; then
835
878
  exit 4
836
879
  ;;
837
880
  6)
838
- printf '%s\n' "$AA_LAST_DECISION" >&2
881
+ aa_print_refusal
839
882
  exit 3
840
883
  ;;
841
884
  5)
@@ -852,11 +895,11 @@ else
852
895
  resolve_rc=0
853
896
  resolve_chain "$CHAIN" || resolve_rc=$?
854
897
  if [[ "$resolve_rc" -eq 6 ]]; then
855
- printf '%s\n' "$AA_LAST_DECISION" >&2
898
+ aa_print_refusal
856
899
  exit 3
857
900
  elif [[ "$resolve_rc" -ne 0 ]]; then
858
901
  echo "omnilane: no eligible available target for lane '$LANE' (chain:$CHAIN)." >&2
859
- [[ -z "$AA_LAST_DECISION" ]] || printf '%s\n' "$AA_LAST_DECISION" >&2
902
+ [[ -z "$AA_LAST_DECISION" ]] || aa_print_refusal
860
903
  exit 4
861
904
  fi
862
905
  fi
@@ -1110,6 +1153,13 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
1110
1153
  fi
1111
1154
 
1112
1155
  printf 'omnilane: executor=cli reason=%s\n' "$EXECUTOR_REASON" >&2
1156
+ if [[ "$EXECUTOR_REASON" == "no-native-context" && -n "$AA_CALLER_CONTEXT" && -r "$AA_CALLER_CONTEXT" ]]; then
1157
+ CALLER_VENDOR="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["caller"]["vendor"])' "$AA_CALLER_CONTEXT" 2>/dev/null || true)"
1158
+ if [[ -n "$CALLER_VENDOR" && "$CALLER_VENDOR" == "$VENDOR" ]]; then
1159
+ # Same vendor is not same model, so this is an offer, never a silent switch.
1160
+ echo "omnilane: the target is this harness's own vendor, yet it goes out through an external CLI because no capability file was given. To use your own sub-agent tool: omnilane native-context --workdir \"$WORKDIR\", then pass --native-context FILE (or --inherit for a worker on your own model and effort)" >&2
1161
+ fi
1162
+ fi
1113
1163
  mkdir -p "$OMNILANE_HOME"
1114
1164
  if [[ ! -d "$JOBS_ROOT" ]]; then
1115
1165
  mkdir -m 700 "$JOBS_ROOT"
package/scripts/doctor.sh CHANGED
@@ -419,7 +419,13 @@ overlay_path="$(
419
419
  printf '%s' "${OMNILANE_AA_TRANSPORT_OVERLAY:-}"
420
420
  )"
421
421
  if [[ -z "$overlay_path" ]]; then
422
- report PASS transport-overlay "no overlay configured; every runtime mapping stays unverified"
422
+ # Only a model caller needs the overlay; a host whose operator asserts the human
423
+ # exemption is complete without one, and --strict must not fail it.
424
+ if [[ "${OMNILANE_AA_OPERATOR_ASSERTED_HUMAN:-0}" == "1" ]]; then
425
+ report PASS transport-overlay "no overlay configured; fine for a human operator, but a model caller would be refused on every lane (README, 'Let your AI assistant drive omnilane')"
426
+ else
427
+ report WARN transport-overlay "no overlay configured, so a model caller is refused on every lane (runtime-mapping-unverified). First install: probe_sweep.py --root ROOT, build_overlay.py --root ROOT, then export OMNILANE_AA_TRANSPORT_OVERLAY in local.sh; see the README, 'Let your AI assistant drive omnilane'"
428
+ fi
423
429
  elif ! command -v python3 >/dev/null 2>&1; then
424
430
  report WARN transport-overlay "python3 is absent; cannot load the AA transport overlay"
425
431
  elif [[ ! -r "$OVERLAY_HEALTH" ]]; then