omnilane 0.42.6 → 0.42.7

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "One routing table, native-first or CLI delegation, supervised completion workflows.",
9
- "version": "0.42.6"
9
+ "version": "0.42.7"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "omnilane",
14
14
  "source": "./",
15
15
  "description": "Route each subtask to the model that fits, then delegate through a compatible caller-owned native agent or vendor CLI.",
16
- "version": "0.42.6",
16
+ "version": "0.42.7",
17
17
  "category": "development",
18
18
  "keywords": [
19
19
  "routing",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.42.6",
3
+ "version": "0.42.7",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Seraphim0916"
package/CHANGELOG.md CHANGED
@@ -6,6 +6,41 @@ semantic version tags.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.42.7] - 2026-09-10
10
+
11
+ ### Added
12
+
13
+ - `omnilane whoami` prints a caller-context file for the CLI it runs under. It
14
+ walks up the process tree to the nearest vendor CLI, reads the model and
15
+ effort that CLI was launched with, and maps them onto the one scored
16
+ configuration they select, or exits 3 with the reason: a missing `--effort`,
17
+ a model alias, or a Claude effort whose only scored row is non-reasoning. It
18
+ never guesses.
19
+
20
+ ### Changed
21
+
22
+ - A dispatch that carries no `--caller-context` and no human assertion reads
23
+ the caller's identity from its launching CLI. Model sessions outside the
24
+ omnilane checkout used to stop on `missing-caller-context` and hand the
25
+ question back to the operator — three did on 2026-09-08 and 2026-09-10 —
26
+ because the refusal offered a model two options it could not take, and the
27
+ instructions for a third lived where those sessions never looked.
28
+ - Launch flags are set by the harness rather than the model, so an identity
29
+ read from them is harder to overstate than a hand-written file, which the
30
+ gate checks for shape but not against what is running. Each session is held
31
+ to its own flags: the same model at `high` and at `max` gets 52 and 54.
32
+ - An explicit `--caller-context`, the context a worker inherits, and
33
+ `--operator-asserted-human` all take precedence.
34
+ `OMNILANE_AA_CALLER_FROM_PROCESS=0` restores the file-only contract.
35
+ - The `missing-caller-context` and retry refusals name `omnilane whoami`.
36
+ Retries still require an explicit current identity; they are not read
37
+ automatically.
38
+
39
+ ### Fixed
40
+
41
+ - `omnilane --version` reported 0.42.5 in the 0.42.6 release, whose `VERSION`
42
+ file was not bumped with the package manifests.
43
+
9
44
  ## [0.42.6] - 2026-09-10
10
45
 
11
46
  ### Added
@@ -981,7 +1016,9 @@ work to the wrong model, and records the evidence behind the shipped defaults.
981
1016
  - Initial shared routing table, cross-vendor dispatcher, runners, installer,
982
1017
  and baseline lint fixes.
983
1018
 
984
- [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.42.5...HEAD
1019
+ [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.42.7...HEAD
1020
+ [0.42.7]: https://github.com/Seraphim0916/omnilane/compare/v0.42.6...v0.42.7
1021
+ [0.42.6]: https://github.com/Seraphim0916/omnilane/compare/v0.42.5...v0.42.6
985
1022
  [0.42.5]: https://github.com/Seraphim0916/omnilane/compare/v0.42.4...v0.42.5
986
1023
  [0.42.4]: https://github.com/Seraphim0916/omnilane/compare/v0.42.3...v0.42.4
987
1024
  [0.42.3]: https://github.com/Seraphim0916/omnilane/compare/v0.42.2...v0.42.3
package/README.ja.md CHANGED
@@ -71,8 +71,10 @@ omnilane route hardest-coding "auth トークン更新テストの不安定さ
71
71
  > ゲートするため、「誰が依頼しているか」を必ず示す必要があります。端末の前にいる人間は
72
72
  > `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` を一度設定するか、呼び出しごとに
73
73
  > `--operator-asserted-human` を付けます。omnilane を動かすモデルは**自分でこれを主張
74
- > できません**。代わりに正確なベンダー・モデル・effort を持つ `--caller-context FILE` を
75
- > 渡します。どちらも無い場合、ジョブ生成前に `missing-caller-context` で拒否されます。
74
+ > できません**。モデルの識別情報は、それを起動した CLI のモデル・effort フラグから自動で
75
+ > 読み取られるため、通常のセッションは何も渡す必要がありません。`omnilane whoami` はその
76
+ > 識別情報を `--caller-context FILE` として出力します。主張も読み取れる識別情報も無い場合、
77
+ > ジョブ生成前に `missing-caller-context` で拒否されます。
76
78
 
77
79
  > はじめての方は、まず `omnilane doctor` を実行してください。omnilane が今どのモデル CLI と
78
80
  > API キーに接続できるかがわかり、実際に何が動くか把握できます。
@@ -530,10 +532,12 @@ work の別名ではありません。サービス管理など、work の境界
530
532
  ください。その `transport-overlay` チェックが、問題はこのマシンの設定なのか
531
533
  リクエストなのかをすぐに示します。
532
534
 
533
- `missing-caller-context` — 呼び出し元の識別情報がありません。人間は
535
+ `missing-caller-context` — 識別情報がゲートに届いていません。人間は
534
536
  `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` または `--operator-asserted-human` を
535
- 使います。omnilane を動かすモデルは、正確なベンダー・モデル・effort を含む
536
- `--caller-context FILE` を渡し、人間向けの免除を自称してはいけません。
537
+ 使います。モデルは通常何もしなくてよく、dispatch が起動元の CLI から識別情報を
538
+ 読み取ります。読み取れない場合は `omnilane whoami` を実行してください。渡すべき
539
+ `--caller-context FILE` を出力するか、読み取れない理由(`--effort` 未指定、モデル
540
+ エイリアス、スコア行なし)を示します。モデルは人間向けの免除を自称してはいけません。
537
541
 
538
542
  `runtime-mapping-unverified` — 識別情報は正しく、**ターゲット**にホストローカルの
539
543
  リクエストセレクタの証明がありません。未プローブか、プローブが失敗しています。
@@ -573,6 +577,16 @@ work の別名ではありません。サービス管理など、work の境界
573
577
 
574
578
  ## 📜 リリース履歴
575
579
 
580
+ ## v0.42.7 の新機能
581
+
582
+ - **モデルのセッションは識別ファイルなしでディスパッチできます。** `--caller-context` が無い場合、dispatch はプロセスツリーを遡って最も近いベンダー CLI を見つけ、その起動時のモデルと effort を読み取ります。これまで omnilane のチェックアウト外のセッションは `missing-caller-context` で止まり、判断をオペレーターに差し戻していました(2026-09-08 と 2026-09-10 に 3 件)。
583
+ - **`omnilane whoami`** はその識別情報を caller-context ファイルとして出力し、読み取れない場合は理由(`--effort` 未指定、モデルエイリアス、Claude のその effort に non-reasoning 行しか無い)を示します。推測はしません。
584
+ - **手書きファイルより偽りにくい。** ゲートは caller-context ファイルの形式しか確認せず、実際に動いているモデルと一致するかは見ていません。起動フラグはモデルではなくハーネスが設定し、セッションごとに判定されます。同じモデルでも `high` と `max` なら上限は 52 と 54 です。
585
+ - **明示指定が優先。** `--caller-context` ファイル、ワーカーが継承する環境、`--operator-asserted-human` はいずれも自動読み取りより優先します。`OMNILANE_AA_CALLER_FROM_PROCESS=0` でファイルのみの契約に戻せます。
586
+ - **拒否メッセージが出口を示します。** `missing-caller-context` と再試行の拒否は `omnilane whoami` を案内します。
587
+ - **`omnilane --version` が正しくなりました。** 0.42.6 では `VERSION` が 0.42.5 のままでした。
588
+ - **アップグレード。** npm 公開後は `npm i -g omnilane@0.42.7` を実行してください。
589
+
576
590
  ## v0.42.6 の新機能
577
591
 
578
592
  - **「検証済み」が、どう検証されたかを示すようになりました。** 各 overlay マッピングは `evidence_tier` を持ちます。`billed-model` はプロバイダー自身が課金対象のモデル名を返した場合(claude、grok)、`client-echo` は CLI が自ら送信したモデルを記録した場合(codex、agy)、`selector-only` は CLI がセレクターを受け付けただけの場合です。`client-echo` は CLI が控えた注文書、`billed-model` はプロバイダーが発行した領収書です。
package/README.ko.md CHANGED
@@ -70,8 +70,10 @@ omnilane route hardest-coding "간헐적으로 실패하는 auth 토큰 갱신
70
70
  > 게이트하므로, 디스패치는 «누가 요청하는지»를 반드시 밝혀야 합니다. 터미널 앞의 사람은
71
71
  > `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 을 한 번 설정하거나 호출마다
72
72
  > `--operator-asserted-human` 을 붙입니다. omnilane 을 구동하는 모델은 **스스로 이를 주장할
73
- > 수 없으며**, 대신 정확한 벤더·모델·effort 를 담은 `--caller-context FILE` 을 전달합니다.
74
- > 둘 다 없으면 잡 생성 전에 `missing-caller-context` 로 거부됩니다.
73
+ > 수 없습니다**. 모델의 신원은 그것을 실행한 CLI 의 모델·effort 플래그에서 자동으로 읽히므로,
74
+ > 일반 세션은 아무것도 전달할 필요가 없습니다. `omnilane whoami` 는 그 신원을
75
+ > `--caller-context FILE` 로 출력합니다. 주장도 읽을 수 있는 신원도 없으면 잡 생성 전에
76
+ > `missing-caller-context` 로 거부됩니다.
75
77
 
76
78
  > 처음이신가요? 먼저 `omnilane doctor` 를 실행하세요. omnilane 이 지금 어떤 모델 CLI 와
77
79
  > API 키에 접근할 수 있는지 알려 주어, 실제로 무엇이 실행될지 파악할 수 있습니다.
@@ -516,10 +518,12 @@ work 는 지정한 디렉터리 안의 변경만 허용하며 모델 연결은
516
518
  `transport-overlay` 검사가 문제의 원인이 이 머신의 설정인지 요청인지 바로
517
519
  알려줍니다.
518
520
 
519
- `missing-caller-context` — 호출자 신원을 전달하지 않았습니다. 사람은
521
+ `missing-caller-context` — 게이트에 신원이 전달되지 않았습니다. 사람은
520
522
  `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 또는 `--operator-asserted-human`을
521
- 사용합니다. omnilane을 구동하는 모델은 정확한 벤더·모델·effort가 담긴
522
- `--caller-context FILE`을 전달해야 하며, 사람용 면제를 스스로 주장해서는 안 됩니다.
523
+ 사용합니다. 모델은 보통 아무것도 할 필요가 없으며, dispatch 가 실행한 CLI 에서 신원을
524
+ 읽습니다. 읽지 못하면 `omnilane whoami` 를 실행하세요. 전달할 `--caller-context FILE`
525
+ 을 출력하거나, 읽지 못한 정확한 이유(`--effort` 누락, 모델 별칭, 점수 행 없음)를 알려줍니다.
526
+ 모델은 사람용 면제를 스스로 주장해서는 안 됩니다.
523
527
 
524
528
  `runtime-mapping-unverified` — 신원은 정상이지만 **대상**에 검증된 호스트 로컬
525
529
  요청 셀렉터가 없습니다. 프로브를 한 적이 없거나 프로브가 실패한 경우입니다.
@@ -558,6 +562,16 @@ doctor가 파일과 벤더를 지목하며, 재서명 절차는 디스패치 스
558
562
 
559
563
  ## 📜 릴리스 기록
560
564
 
565
+ ## v0.42.7 새 기능
566
+
567
+ - **모델 세션은 신원 파일 없이 디스패치할 수 있습니다.** `--caller-context` 가 없으면 dispatch 가 프로세스 트리를 거슬러 올라가 가장 가까운 벤더 CLI 를 찾고, 그 CLI 가 실행될 때의 모델과 effort 를 읽습니다. 지금까지 omnilane 체크아웃 밖의 세션은 `missing-caller-context` 에서 멈추고 판단을 운영자에게 되돌렸습니다(2026-09-08, 2026-09-10 에 3건).
568
+ - **`omnilane whoami`** 는 그 신원을 caller-context 파일로 출력하고, 읽지 못하면 정확한 이유(`--effort` 누락, 모델 별칭, 해당 effort 에 Claude 의 non-reasoning 행만 있음)를 알려줍니다. 추측하지 않습니다.
569
+ - **손으로 쓴 파일보다 속이기 어렵습니다.** 게이트는 caller-context 파일의 형식만 확인하고 실제로 실행 중인 모델과 일치하는지는 보지 않습니다. 실행 플래그는 모델이 아니라 하네스가 설정하며, 세션마다 따로 판정됩니다. 같은 모델이라도 `high` 와 `max` 면 상한은 52 와 54 입니다.
570
+ - **명시 지정이 우선합니다.** `--caller-context` 파일, 워커가 물려받는 환경, `--operator-asserted-human` 은 모두 자동 읽기보다 우선합니다. `OMNILANE_AA_CALLER_FROM_PROCESS=0` 으로 파일 전용 계약으로 되돌릴 수 있습니다.
571
+ - **거부 메시지가 해결책을 알려줍니다.** `missing-caller-context` 와 재시도 거부는 `omnilane whoami` 를 안내합니다.
572
+ - **`omnilane --version` 이 다시 정확해졌습니다.** 0.42.6 에서는 `VERSION` 이 0.42.5 로 남아 있었습니다.
573
+ - **업그레이드.** npm 게시 후 `npm i -g omnilane@0.42.7`를 실행하세요.
574
+
561
575
  ## v0.42.6 새 기능
562
576
 
563
577
  - **"검증됨"이 어떻게 검증했는지도 알려줍니다.** 각 overlay 매핑은 `evidence_tier`를 가집니다. `billed-model`은 공급자가 과금한 모델을 직접 밝힌 경우(claude, grok), `client-echo`는 CLI가 자신이 보낸 모델을 기록한 경우(codex, agy), `selector-only`는 CLI가 셀렉터를 받아들이기만 한 경우입니다. `client-echo`는 CLI가 적어 둔 주문서이고, `billed-model`은 공급자가 발행한 영수증입니다.
package/README.md CHANGED
@@ -71,8 +71,10 @@ omnilane route hardest-coding "fix the flaky auth token refresh"
71
71
  > capability score, so a dispatch has to say who is asking. A human at a terminal
72
72
  > asserts that once with `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1`, or per call with
73
73
  > `--operator-asserted-human`. A model driving omnilane cannot assert it for
74
- > itself — it passes `--caller-context FILE` carrying its exact vendor, model and
75
- > effort instead. With neither, the dispatch is refused with
74
+ > itself. Its identity is read from the CLI that launched it — the model and
75
+ > effort flags that CLI was started with — so an ordinary session passes nothing,
76
+ > and `omnilane whoami` prints that identity as a `--caller-context FILE`. With
77
+ > neither an assertion nor a readable identity, the dispatch is refused with
76
78
  > `missing-caller-context` before any job is created.
77
79
 
78
80
  > New to this? Run `omnilane doctor` first — it tells you which model CLIs and
@@ -625,10 +627,13 @@ Three codes, three different fixes. Run `omnilane doctor` first — its
625
627
  `transport-overlay` check tells you immediately whether the problem is your
626
628
  machine's configuration or your request.
627
629
 
628
- `missing-caller-context` — you passed no identity. A human adds
629
- `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` or `--operator-asserted-human`; a model
630
- driving omnilane writes a `--caller-context FILE` with its exact vendor, model,
631
- and effort, and must not assert the human exemption for itself.
630
+ `missing-caller-context` — no identity reached the gate. A human adds
631
+ `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` or `--operator-asserted-human`. A model
632
+ normally needs nothing: dispatch reads its identity from the CLI that launched
633
+ it. When that fails, run `omnilane whoami` — it either prints a
634
+ `--caller-context FILE` to pass, or says exactly why it cannot (a missing
635
+ `--effort`, a model alias, no scored row). A model must not assert the human
636
+ exemption for itself.
632
637
 
633
638
  `runtime-mapping-unverified` — your identity is fine, but the *target* has no
634
639
  proven host-local request selector. Either it was never probed, or its probe
@@ -699,6 +704,16 @@ working notes, including per-benchmark caveats, live in
699
704
 
700
705
  ## 📜 Release history
701
706
 
707
+ ## What's new in v0.42.7
708
+
709
+ - **A model session no longer needs an identity file to dispatch.** When no `--caller-context` is given, dispatch walks up the process tree to the nearest vendor CLI and reads the model and effort it was launched with. Sessions outside the omnilane checkout used to stop on `missing-caller-context` and hand the question back to the operator; three did so on 2026-09-08 and 2026-09-10.
710
+ - **`omnilane whoami`** prints that identity as a caller-context file, or says exactly why it cannot — a missing `--effort`, a model alias, or a Claude effort whose only scored row is non-reasoning. It never guesses.
711
+ - **Harder to overstate than a hand-written file.** The gate checks a caller-context file's shape, not whether it matches the model actually running. Launch flags are set by the harness, not the model, and each session is held to its own: the same model at `high` and at `max` gets ceilings 52 and 54.
712
+ - **Explicit still wins.** A `--caller-context` file, the context a worker inherits, and `--operator-asserted-human` all take precedence. `OMNILANE_AA_CALLER_FROM_PROCESS=0` restores the file-only contract.
713
+ - **The refusal names the way out.** `missing-caller-context` and the retry refusal now point to `omnilane whoami` instead of offering a model two options it cannot take.
714
+ - **`omnilane --version` is right again.** The 0.42.6 release left `VERSION` at 0.42.5.
715
+ - **Upgrade.** After npm publication, run `npm i -g omnilane@0.42.7`. Existing repo-symlink installations can update their checkout and verify `omnilane --version` without rerunning installation.
716
+
702
717
  ## What's new in v0.42.6
703
718
 
704
719
  - **A verified mapping now says how it was verified.** Each overlay mapping carries an `evidence_tier`: `billed-model` when the provider named the model it charged for (Claude, grok), `client-echo` when the CLI recorded the model it asked for (codex, agy), `selector-only` when the CLI accepted the selector and said nothing more. `client-echo` is the CLI's copy of your order; `billed-model` is the provider's receipt.
package/README.zh-CN.md CHANGED
@@ -65,8 +65,9 @@ omnilane route hardest-coding "修掉会间歇失败的 auth token 更新测试"
65
65
  > **那个 export 是做什么的?** omnilane 会用调用者自己的能力分数来把关每一次派工,
66
66
  > 所以派工必须表明「是谁在问」。人类在终端前只要设一次
67
67
  > `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1`,或每次带 `--operator-asserted-human`。
68
- > 模型驱动 omnilane 时**不能替自己主张**这个标志,它要改用 `--caller-context FILE`
69
- > 提供确切的厂商、模型与强度。两者都没有的话,派工会在创建作业前就被
68
+ > 模型驱动 omnilane 时**不能替自己主张**这个标志。它的身份会从启动它的 CLI 标志
69
+ > (模型与强度)自动读取,一般 session 什么都不用带;`omnilane whoami` 会把这个身份
70
+ > 打印成 `--caller-context FILE`。既没主张、又读不到身份的话,派工会在创建作业前就被
70
71
  > `missing-caller-context` 拒绝。
71
72
 
72
73
  > 第一次用?先跑 `omnilane doctor`——它会告诉你 omnilane 现在能接到哪些模型 CLI 与
@@ -489,10 +490,11 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 完整解析后的计划,
489
490
  三个代码,三种不同的修法。先运行 `omnilane doctor`——它的 `transport-overlay`
490
491
  检查会直接告诉你问题出在本机配置还是你的请求。
491
492
 
492
- `missing-caller-context`——你没带身份。真人加上
493
- `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 或 `--operator-asserted-human`;
494
- 模型驱动 omnilane 时要写一份 `--caller-context FILE`,包含它精确的厂商、模型与
495
- 强度,且不得替自己主张真人豁免。
493
+ `missing-caller-context`——没有身份送到闸门。真人加上
494
+ `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 或 `--operator-asserted-human`。
495
+ 模型通常什么都不用做:dispatch 会从启动它的 CLI 读取身份。读不到时运行
496
+ `omnilane whoami`,它会打印可以带的 `--caller-context FILE`,或说明读不到的确切原因
497
+ (缺 `--effort`、模型别名、查无评分行)。模型不得替自己主张真人豁免。
496
498
 
497
499
  `runtime-mapping-unverified`——你的身份没问题,但**目标**没有已验证的本机请求
498
500
  选择器。可能从未探测过,也可能探测失败;`omnilane doctor` 会报告这类配置的数量,
@@ -554,6 +556,16 @@ codex 记在 session rollout,agy 写进 `cli.log`。这是 CLI 自己抄的订
554
556
 
555
557
  ## 📜 版本历程
556
558
 
559
+ ## v0.42.7 新功能
560
+
561
+ - **模型 session 派工不再需要身份文件。** 没给 `--caller-context` 时,dispatch 会沿进程树往上找到最近的厂商 CLI,读取它启动时带的模型与强度。以前不在 omnilane 目录下的 session 会卡在 `missing-caller-context`,把问题丢回给操作者——2026-09-08 与 2026-09-10 就有三个 session 这样停下来。
562
+ - **`omnilane whoami`** 会把这个身份打印成 caller-context 文件,读不到时说明确切原因(缺 `--effort`、模型别名、claude 该强度只剩 non-reasoning 行),绝不猜测。
563
+ - **比手写的文件更难作假。** 闸门只检查身份文件的格式,不核对它与实际运行的模型是否一致。启动标志由 harness 设置,模型改不了,而且每个 session 各算各的:同一模型分别开 `high` 和 `max`,上限就是 52 和 54。
564
+ - **明确指定仍然优先。** `--caller-context` 文件、worker 继承的环境、`--operator-asserted-human` 都优先于自动读取。`OMNILANE_AA_CALLER_FROM_PROCESS=0` 可恢复「只认文件」的规则。
565
+ - **被拒时会告诉你出路。** `missing-caller-context` 和重试被拒的消息都改为指向 `omnilane whoami`。
566
+ - **`omnilane --version` 恢复正确。** 0.42.6 发版时漏改了 `VERSION`,会报告 0.42.5。
567
+ - **升级。** npm 发布后运行 `npm i -g omnilane@0.42.7`。既有的 repo symlink 安装更新检出后确认 `omnilane --version` 即可。
568
+
557
569
  ## v0.42.6 新功能
558
570
 
559
571
  - **「已验证」现在会说明是怎么验的。** 每条 overlay 映射带一个 `evidence_tier`:`billed-model` 是供应商自己说出计费的模型(claude、grok),`client-echo` 是 CLI 记下自己送出的模型(codex、agy),`selector-only` 是 CLI 收下选择器、其余不表态。`client-echo` 是 CLI 自己抄的订单,`billed-model` 是供应商开的收据。
package/README.zh-TW.md CHANGED
@@ -65,8 +65,9 @@ omnilane route hardest-coding "修掉會間歇失敗的 auth token 更新測試"
65
65
  > **那個 export 是做什麼的?** omnilane 會用呼叫者自己的能力分數來把關每一次派工,
66
66
  > 所以派工必須表明「是誰在問」。人類在終端機前只要設一次
67
67
  > `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1`,或每次帶 `--operator-asserted-human`。
68
- > 模型驅動 omnilane 時**不能替自己主張**這個旗標,它要改用 `--caller-context FILE`
69
- > 提供確切的廠商、模型與強度。兩者都沒有的話,派工會在建立工作前就被
68
+ > 模型驅動 omnilane 時**不能替自己主張**這個旗標。它的身分會從啟動它的 CLI 旗標
69
+ > (模型與強度)自動讀取,一般 session 什麼都不用帶;`omnilane whoami` 會把這個身分
70
+ > 印成 `--caller-context FILE`。既沒主張、又讀不到身分的話,派工會在建立工作前就被
70
71
  > `missing-caller-context` 拒絕。
71
72
 
72
73
  > 第一次用?先跑 `omnilane doctor`——它會告訴你 omnilane 現在能接到哪些模型 CLI 與
@@ -533,10 +534,11 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 完整解析後的計畫,
533
534
  三個代碼,三種不同的修法。先跑 `omnilane doctor`——它的 `transport-overlay`
534
535
  檢查會直接告訴你問題出在本機設定還是你的請求。
535
536
 
536
- `missing-caller-context`——你沒帶身分。真人加上
537
- `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 或 `--operator-asserted-human`;
538
- 模型驅動 omnilane 時要寫一份 `--caller-context FILE`,內含它精確的廠商、模型與
539
- 強度,且不得替自己主張真人豁免。
537
+ `missing-caller-context`——沒有身分送到閘門。真人加上
538
+ `OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1` 或 `--operator-asserted-human`。
539
+ 模型通常什麼都不用做:dispatch 會從啟動它的 CLI 讀取身分。讀不到時跑
540
+ `omnilane whoami`,它會印出可以帶的 `--caller-context FILE`,或說明讀不到的確切原因
541
+ (缺 `--effort`、模型別名、查無評分列)。模型不得替自己主張真人豁免。
540
542
 
541
543
  `runtime-mapping-unverified`——你的身分沒問題,但**目標**沒有已驗證的本機請求
542
544
  選擇器。可能從未探測過,也可能探測失敗;`omnilane doctor` 會回報這類設定的數量,
@@ -595,6 +597,16 @@ codex 記在 session rollout,agy 寫進 `cli.log`。這是 CLI 自己抄的訂
595
597
 
596
598
  ## 📜 版本歷程
597
599
 
600
+ ## v0.42.7 新功能
601
+
602
+ - **模型 session 派工不再需要身分檔。** 沒給 `--caller-context` 時,dispatch 會沿行程樹往上找到最近的廠商 CLI,讀它啟動時帶的模型與強度。以前不在 omnilane 目錄下的 session 會卡在 `missing-caller-context`,把問題丟回給操作者——2026-09-08 與 2026-09-10 就有三個 session 這樣停下來。
603
+ - **`omnilane whoami`** 會把這個身分印成 caller-context 檔,讀不到時說明確切原因(缺 `--effort`、模型別名、claude 那個強度只剩 non-reasoning 列),絕不猜。
604
+ - **比手寫的檔案更難作假。** 閘門只檢查身分檔的格式,不核對它和實際在跑的模型是否一致。啟動旗標是 harness 設的,模型改不了,而且每個 session 各算各的:同一個模型分別開 `high` 和 `max`,上限就是 52 和 54。
605
+ - **明確指定仍然優先。** `--caller-context` 檔、worker 繼承的環境、`--operator-asserted-human` 都優先於自動讀取。`OMNILANE_AA_CALLER_FROM_PROCESS=0` 可以恢復「只認檔案」的規則。
606
+ - **被拒時會告訴你出路。** `missing-caller-context` 和重試被拒的訊息都改成指向 `omnilane whoami`,不再只給模型兩條走不通的路。
607
+ - **`omnilane --version` 恢復正確。** 0.42.6 發版時漏改了 `VERSION`,會回報 0.42.5。
608
+ - **升級。** npm 發布後執行 `npm i -g omnilane@0.42.7`。既有的 repo symlink 安裝更新檢出後確認 `omnilane --version` 即可,不需重跑安裝。
609
+
598
610
  ## v0.42.6 新功能
599
611
 
600
612
  - **「已驗證」現在會說明是怎麼驗的。** 每條 overlay 映射帶一個 `evidence_tier`:`billed-model` 是供應商自己說出計費的模型(claude、grok),`client-echo` 是 CLI 記下自己送出的模型(codex、agy),`selector-only` 是 CLI 收下選擇器、其餘不表態。白話說:`client-echo` 是 CLI 自己抄的訂單,`billed-model` 是供應商開的收據。
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.42.5
1
+ 0.42.7
package/bin/omnilane CHANGED
@@ -53,6 +53,13 @@ case "$cmd" in
53
53
  ;;
54
54
  release-audit) exec bash "$REPO/scripts/release-audit.sh" "$@" ;;
55
55
  doctor) exec bash "$REPO/scripts/doctor.sh" "$@" ;;
56
+ whoami)
57
+ command -v python3 >/dev/null 2>&1 || {
58
+ echo "omnilane: whoami requires Python 3.9 or newer" >&2
59
+ exit 1
60
+ }
61
+ exec python3 "$REPO/scripts/lib/caller_identity.py" "$@"
62
+ ;;
56
63
  benchmark)
57
64
  command -v python3 >/dev/null 2>&1 || {
58
65
  echo "omnilane: benchmark requires Python 3.9 or newer" >&2
@@ -89,6 +96,7 @@ omnilane — one routing table, every harness
89
96
  omnilane completion bash|zsh|fish print a shell completion script
90
97
  omnilane release-audit [flags] offline, read-only release gate
91
98
  omnilane doctor [--json] [--strict] [--probe V] local health / opt-in live probe
99
+ omnilane whoami caller-context file for the CLI this runs under
92
100
  omnilane benchmark [--json] [--run] [--vendor V] fixed quality/cost comparison
93
101
  omnilane ui start|status|url|stop
94
102
  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 benchmark release-audit ui configure completion help
56
+ _values 'command' version list route dispatch jobs doctor whoami 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 benchmark release-audit ui configure completion help"
61
+ words="version list route dispatch jobs doctor whoami benchmark release-audit ui configure completion help"
62
62
  else
63
63
  case "$command" in
64
64
  route|dispatch)
@@ -10,6 +10,7 @@ complete -c omnilane -f -n __fish_use_subcommand -a dispatch -d 'dispatch o
10
10
  complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect background jobs'
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
+ complete -c omnilane -f -n __fish_use_subcommand -a whoami -d 'caller-context file for the launching CLI'
13
14
  complete -c omnilane -f -n __fish_use_subcommand -a benchmark -d 'fixed quality/cost comparison'
14
15
  complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
15
16
  complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.42.6",
3
+ "version": "0.42.7",
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.6",
4
+ "version": "0.42.7",
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
  }
@@ -796,6 +796,17 @@ if [[ -n "${OMNILANE_AA_TRANSPORT_OVERLAY:-}" ]]; then
796
796
  OMNILANE_AA_OVERLAY_SHA256="$(file_sha256 "$OMNILANE_AA_TRANSPORT_OVERLAY")"
797
797
  export OMNILANE_AA_OVERLAY_SHA256
798
798
  fi
799
+ # A model caller that passed no identity is read from the CLI that launched it:
800
+ # the harness sets those flags and the model cannot. An explicit file or the
801
+ # human assertion still wins; OMNILANE_AA_CALLER_FROM_PROCESS=0 keeps the
802
+ # file-only contract.
803
+ if [[ "$AA_OPERATOR_ASSERTED_HUMAN" == "0" && -z "$AA_CALLER_CONTEXT" \
804
+ && "${OMNILANE_AA_CALLER_FROM_PROCESS:-1}" != "0" ]]; then
805
+ if derived_caller="$(python3 "$OMNILANE_REPO/scripts/lib/caller_identity.py" \
806
+ --registry "$AA_POLICY_FILE")"; then
807
+ AA_CALLER_CONTEXT="$derived_caller"
808
+ fi
809
+ fi
799
810
  AA_POLICY_ACTIVE=1
800
811
  if [[ -n "$OVERRIDE_VENDOR" || -n "$OVERRIDE_MODEL" || -n "$OVERRIDE_EFFORT" || -n "$AA_TARGET_CONFIG" ]]; then
801
812
  AA_EXPLICIT_TARGET=1
@@ -341,7 +341,10 @@ def decide(registry: dict[str, Any], registry_sha256: str, *,
341
341
  if caller is None:
342
342
  base.update(
343
343
  code="missing-caller-context",
344
- message="provide --caller-context FILE or explicitly assert --operator-asserted-human",
344
+ message=("no caller identity: a model caller runs `omnilane whoami` and passes the "
345
+ "file it prints as --caller-context, which dispatch does itself when the "
346
+ "launching CLI names its model and effort; a human operator passes "
347
+ "--operator-asserted-human"),
345
348
  )
346
349
  return base
347
350
  base["caller_context_sha256"] = caller_sha256
@@ -27,7 +27,9 @@ def retry_args(directory):
27
27
  original_limit = min(rows[0]["score"], original["inherited_ceiling"])
28
28
  current_path = os.environ.get("OMNILANE_AA_CALLER_CONTEXT")
29
29
  current_human = os.environ.get("OMNILANE_AA_OPERATOR_ASSERTED_HUMAN") == "1"
30
- aa_policy._check(bool(current_path) != current_human, "retry requires one current caller context or explicit current human assertion")
30
+ aa_policy._check(bool(current_path) != current_human,
31
+ "retry requires one current caller context or explicit current human "
32
+ "assertion; a model caller gets one from `omnilane whoami`")
31
33
  if current_path:
32
34
  current, _ = aa_policy.load_caller(current_path, registry)
33
35
  rows = aa_policy._matching_rows(registry, current["caller"])
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env python3
2
+ """Read the exact AA caller identity of the CLI this process runs under.
3
+
4
+ Walks up the process tree to the nearest vendor CLI, reads the model and effort
5
+ it was launched with, and maps them onto the one scored configuration they
6
+ select. Launch flags are the harness's request selector: the same class of
7
+ evidence the transport overlay carries, and unlike a hand-written caller-context
8
+ file, not something the model can edit.
9
+
10
+ Prints the path of a caller-context file for that identity. Exits 3 with the
11
+ reason on stderr when it cannot decide. It never guesses: a missing flag, an
12
+ alias, or an identity the frozen registry does not score exactly once is a
13
+ refusal, not a fallback.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import os
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Callable, Optional
24
+
25
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
26
+ import aa_policy # noqa: E402
27
+
28
+ REPO = Path(__file__).resolve().parents[2]
29
+ MAX_DEPTH = 64
30
+ ENCODED_EFFORTS = ("xhigh", "high", "medium", "low")
31
+ Selector = tuple[str, Optional[str], Optional[str]]
32
+ Lookup = Callable[[int], Optional[tuple[int, list[str]]]]
33
+
34
+
35
+ def _vendor(executable: str) -> str | None:
36
+ path = Path(executable)
37
+ name = path.name
38
+ if name == "claude" or (path.parent.name == "versions" and path.parent.parent.name == "claude"):
39
+ return "claude"
40
+ if name == "codex":
41
+ return "codex"
42
+ if name == "grok" or (name.startswith("grok-") and path.parent.name == "downloads"):
43
+ return "grok"
44
+ if name == "agy":
45
+ return "gemini"
46
+ return None
47
+
48
+
49
+ def _flag(argv: list[str], *names: str) -> str | None:
50
+ """The last value given for any of names, as `--name value` or `--name=value`."""
51
+ value = None
52
+ for index, token in enumerate(argv):
53
+ for name in names:
54
+ if token == name and index + 1 < len(argv):
55
+ value = argv[index + 1]
56
+ elif token.startswith(name + "="):
57
+ value = token.split("=", 1)[1]
58
+ return value
59
+
60
+
61
+ def _codex_effort(argv: list[str]) -> str | None:
62
+ effort = None
63
+ for index, token in enumerate(argv[:-1]):
64
+ if token in ("-c", "--config"):
65
+ key, _, value = argv[index + 1].partition("=")
66
+ if key.strip() == "model_reasoning_effort":
67
+ effort = value.strip().strip("'\"")
68
+ return effort
69
+
70
+
71
+ def read_selector(argv: list[str]) -> Selector | None:
72
+ """(vendor, model, effort) when argv launches a vendor CLI, otherwise None."""
73
+ if not argv:
74
+ return None
75
+ vendor = _vendor(argv[0])
76
+ rest = argv[1:]
77
+ if vendor == "claude":
78
+ return vendor, _flag(rest, "--model"), _flag(rest, "--effort")
79
+ if vendor == "codex":
80
+ return vendor, _flag(rest, "-m", "--model"), _codex_effort(rest)
81
+ if vendor == "grok":
82
+ return vendor, _flag(rest, "-m", "--model"), _flag(rest, "--reasoning-effort")
83
+ if vendor == "gemini":
84
+ return vendor, _flag(rest, "--model", "-m"), None
85
+ return None
86
+
87
+
88
+ def resolve(registry: dict, vendor: str, model: str | None,
89
+ effort: str | None) -> tuple[dict | None, str]:
90
+ """The one scored row a launch selector lands on, or None and the reason."""
91
+ if not model:
92
+ return None, f"{vendor} was launched without a model flag"
93
+ if vendor == "gemini" and effort is None:
94
+ base, _, suffix = model.rpartition("-")
95
+ if suffix in ENCODED_EFFORTS:
96
+ model, effort = base, suffix
97
+ if vendor == "codex":
98
+ if effort is None:
99
+ return None, (f"codex was launched without model_reasoning_effort; its configured "
100
+ f"default is not read, so the effort of {model} is unknown")
101
+ if effort == "none":
102
+ effort = None
103
+ rows = [row for row in registry["scored_configs"]
104
+ if row["vendor"] == vendor and row["model"] == model and row["effort"] == effort]
105
+ excluded = []
106
+ if vendor == "claude":
107
+ # ADR-0046: --effort has no reasoning-off value, so a non-reasoning row
108
+ # can never be what a Claude launch selected.
109
+ excluded = [row for row in rows if row["reasoning"] == "non-reasoning"]
110
+ rows = [row for row in rows if row["reasoning"] != "non-reasoning"]
111
+ if len(rows) == 1:
112
+ return rows[0], ""
113
+ if rows:
114
+ return None, f"{vendor} {model} at effort {effort} matches {len(rows)} scored configurations"
115
+ if excluded:
116
+ return None, (f"the only scored {model} row at effort {effort} is non-reasoning, which a "
117
+ f"Claude launch cannot select")
118
+ if vendor == "claude" and effort is None:
119
+ return None, f"{model} was launched without --effort and no default is scored for it"
120
+ return None, f"no scored configuration for {vendor} {model} at effort {effort}"
121
+
122
+
123
+ def _process(pid: int) -> tuple[int, list[str]] | None:
124
+ """(ppid, argv) for pid. /proc keeps argv exact; ps joins it with spaces, so
125
+ argv[0] comes from `comm`, which keeps a path like `Application Support` whole."""
126
+ proc = Path(f"/proc/{pid}")
127
+ if proc.is_dir():
128
+ try:
129
+ argv = [part.decode(errors="replace")
130
+ for part in (proc / "cmdline").read_bytes().split(b"\0") if part]
131
+ ppid = int((proc / "stat").read_text().rsplit(")", 1)[1].split()[1])
132
+ except (OSError, ValueError, IndexError):
133
+ return None
134
+ return ppid, argv
135
+ try:
136
+ head = subprocess.run(["ps", "-o", "ppid=", "-o", "comm=", "-p", str(pid)],
137
+ capture_output=True, text=True, timeout=5).stdout.strip()
138
+ args = subprocess.run(["ps", "-o", "args=", "-p", str(pid)],
139
+ capture_output=True, text=True, timeout=5).stdout.strip()
140
+ except (OSError, subprocess.SubprocessError):
141
+ return None
142
+ parts = head.split(None, 1)
143
+ if len(parts) != 2 or not parts[0].isdigit():
144
+ return None
145
+ comm = parts[1].strip()
146
+ rest = args[len(comm):] if args.startswith(comm) else args.partition(" ")[2]
147
+ return int(parts[0]), [comm, *rest.split()]
148
+
149
+
150
+ def find_launcher(pid: int, lookup: Lookup = _process) -> tuple[int, Selector] | None:
151
+ """The nearest process at or above pid that is a vendor CLI, with its selector.
152
+
153
+ Nearest wins: a codex worker started by a Claude session is a codex caller.
154
+ """
155
+ seen: set[int] = set()
156
+ for _ in range(MAX_DEPTH):
157
+ if pid <= 0 or pid in seen:
158
+ return None
159
+ seen.add(pid)
160
+ entry = lookup(pid)
161
+ if entry is None:
162
+ return None
163
+ ppid, argv = entry
164
+ selector = read_selector(argv)
165
+ if selector is not None:
166
+ return pid, selector
167
+ pid = ppid
168
+ return None
169
+
170
+
171
+ def load_registry(path: str | Path) -> tuple[dict, str]:
172
+ """The approved registry without the transport overlay, which says nothing
173
+ about the caller and must not stop one from learning who it is."""
174
+ saved = {key: os.environ.pop(key)
175
+ for key in ("OMNILANE_AA_TRANSPORT_OVERLAY", "OMNILANE_AA_OVERLAY_SHA256")
176
+ if key in os.environ}
177
+ try:
178
+ return aa_policy.load_registry(path)
179
+ finally:
180
+ os.environ.update(saved)
181
+
182
+
183
+ def write_context(row: dict, registry: dict, home: Path) -> Path:
184
+ """One file per identity: every session launched the same way is the same caller."""
185
+ directory = Path(home) / "caller-context"
186
+ directory.mkdir(parents=True, exist_ok=True)
187
+ path = directory / (row["id"].replace("/", "--") + ".json")
188
+ text = json.dumps({
189
+ "schema_version": 1,
190
+ "snapshot_id": registry["snapshot"]["id"],
191
+ "kind": "model",
192
+ "caller": {key: row[key] for key in aa_policy.IDENTITY_FIELDS},
193
+ "inherited_ceiling": row["score"],
194
+ }, indent=2, sort_keys=True) + "\n"
195
+ try:
196
+ if path.read_text() == text:
197
+ return path
198
+ except OSError:
199
+ pass
200
+ staging = path.with_name(f".{path.name}.{os.getpid()}.tmp")
201
+ staging.write_text(text)
202
+ os.replace(staging, path)
203
+ return path
204
+
205
+
206
+ def main(argv: list[str] | None = None) -> int:
207
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
208
+ parser.add_argument("--registry",
209
+ default=os.environ.get("OMNILANE_AA_POLICY_FILE")
210
+ or str(REPO / "config" / "aa-model-policy.json"),
211
+ help="frozen AA registry (default: the repository copy)")
212
+ args = parser.parse_args(argv)
213
+ try:
214
+ registry, _ = load_registry(args.registry)
215
+ except (aa_policy.PolicyError, OSError, ValueError) as error:
216
+ print(f"omnilane: cannot read the AA registry: {error}", file=sys.stderr)
217
+ return 3
218
+ found = find_launcher(os.getpid())
219
+ if found is None:
220
+ print("omnilane: no vendor CLI among this process's ancestors; a model caller passes "
221
+ "--caller-context FILE and a human operator --operator-asserted-human",
222
+ file=sys.stderr)
223
+ return 3
224
+ pid, (vendor, model, effort) = found
225
+ row, reason = resolve(registry, vendor, model, effort)
226
+ if row is None:
227
+ print(f"omnilane: cannot read the caller identity from pid {pid}: {reason}", file=sys.stderr)
228
+ return 3
229
+ home = Path(os.environ.get("OMNILANE_HOME") or Path.home() / ".omnilane")
230
+ path = write_context(row, registry, home)
231
+ print(f"omnilane: caller is {row['id']} (score {row['score']}), read from pid {pid}",
232
+ file=sys.stderr)
233
+ print(path)
234
+ return 0
235
+
236
+
237
+ if __name__ == "__main__":
238
+ raise SystemExit(main())
@@ -19,10 +19,13 @@ You (the main loop) may be Claude, GPT, Grok, or Gemini. The procedure is identi
19
19
  Read-only work uses advise; edits require `--mode work --workdir <repo>`.
20
20
  `<repo>/scripts/dispatch.sh --caller-context FILE [--executor auto|native|cli] [--native-context FILE] [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
21
21
 
22
- A model caller MUST pass `--caller-context`; without it every dispatch is
23
- refused with `missing-caller-context` before a job exists. Build that file
24
- before the first dispatch — see **Frozen exact-AA downward gate** for the
25
- schema, a worked example, and what to do when your own effort is unverifiable.
22
+ A model caller needs a caller identity. Dispatch reads it from the CLI that
23
+ launched you when that CLI names its model and effort, so an ordinary session
24
+ passes nothing. When it cannot, dispatch is refused with
25
+ `missing-caller-context` before a job exists: run `omnilane whoami` (or
26
+ `<repo>/bin/omnilane whoami`) and pass the file it prints as
27
+ `--caller-context`. See **Frozen exact-AA downward gate** for the schema and
28
+ what to do when your effort is genuinely unverifiable.
26
29
 
27
30
  Add `--background` for long tasks; poll with `scripts/jobs.sh status|result <id>`.
28
31
  Use `--thread NAME` when later claude, codex, grok or gemini dispatches
@@ -355,15 +358,23 @@ and `snapshot_id` must equal the registry's own `snapshot.id`.
355
358
  Set `inherited_ceiling` to your own row's score when you are the root caller, or
356
359
  to the ceiling you were handed when you are a child.
357
360
 
358
- **When your harness names a model but no effort, look before you guess.** The
359
- launching process usually carries the exact flags. Walk your own ancestor chain
360
- (`ps -o ppid=,comm= -p <pid>` upward, then `ps -o args= -p <ancestor>`) and read
361
- its `--model` / `--effort`. Match the ancestor chain rather than the first
362
- matching process on the host — a second session of the same CLI is common and
363
- its flags are not yours. This is request-selector evidence, the same class the
364
- transport overlay carries, and it does not certify upstream identity.
365
-
366
- Only when that genuinely yields nothing: ask the operator, or declare the
361
+ **Your identity is read from the CLI that launched you.** When no
362
+ `--caller-context` is given, dispatch walks up the process tree to the nearest
363
+ vendor CLI and reads the selector it was started with: `--model` / `--effort`
364
+ for claude, `-m` and `-c model_reasoning_effort` for codex, `--reasoning-effort`
365
+ for grok, the effort-encoded model id for agy. `omnilane whoami` runs the same
366
+ walk and prints the resulting caller-context file, for a retry or to see what
367
+ you will be held to. Nearest wins, so a codex worker started by a Claude session
368
+ is a codex caller, and another session of the same CLI elsewhere on the host is
369
+ never consulted. It does not guess: a missing flag, a model alias, or a Claude
370
+ effort whose only scored row is non-reasoning is refused with the reason. This
371
+ is request-selector evidence, the same class the transport overlay carries; it
372
+ does not certify upstream identity, but unlike a hand-written file the model
373
+ cannot edit it. An explicit `--caller-context` or `--operator-asserted-human`
374
+ still wins, and `OMNILANE_AA_CALLER_FROM_PROCESS=0` restores the file-only
375
+ contract.
376
+
377
+ Only when `omnilane whoami` refuses: ask the operator, or declare the
367
378
  lowest-scoring row of your model and say so in your report. Understating only
368
379
  narrows what you may dispatch to, so it fails in the safe direction — but it is
369
380
  the fallback, not the first move, and an unnecessarily low ceiling silently
@@ -372,7 +383,9 @@ declared effort to unblock a refused target, and never assert
372
383
  `--operator-asserted-human` on your own behalf.
373
384
 
374
385
  Three refusal codes mean different things and need different fixes.
375
- `missing-caller-context` means you passed no file — write one.
386
+ `missing-caller-context` means no identity reached the gate: you passed no file
387
+ and dispatch could not read one from your launching CLI. Run `omnilane whoami`;
388
+ its refusal says exactly why, and that reason is what to fix or report.
376
389
  `runtime-mapping-unverified` means the file is fine but the *target* has no proven
377
390
  host-local request selector; that is fixed by a `--transport-overlay` entry backed
378
391
  by real evidence, never by editing the frozen registry (its sha256 is pinned in