omnilane 0.42.5 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +98 -1
- package/README.ja.md +29 -5
- package/README.ko.md +29 -5
- package/README.md +60 -6
- package/README.zh-CN.md +52 -6
- package/README.zh-TW.md +52 -6
- package/VERSION +1 -1
- package/bin/omnilane +8 -0
- package/completions/_omnilane +1 -1
- package/completions/omnilane.bash +1 -1
- package/completions/omnilane.fish +1 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/dispatch.sh +11 -0
- package/scripts/lib/aa_policy.py +13 -1
- package/scripts/lib/aa_retry.py +3 -1
- package/scripts/lib/build_overlay.py +42 -17
- package/scripts/lib/caller_identity.py +238 -0
- package/scripts/lib/overlay_health.py +8 -2
- package/scripts/lib/probe.py +261 -37
- package/skills/omnilane/SKILL.md +69 -17
package/scripts/lib/probe.py
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
Runs one CLI invocation, captures raw stdout/stderr to files, and writes a
|
|
5
5
|
descriptor with the original command/stream fields plus a vendor-specific
|
|
6
|
-
verdict, its reason,
|
|
6
|
+
verdict, its reason, the observed model, and the tier of evidence that model
|
|
7
|
+
rests on.
|
|
7
8
|
"""
|
|
8
9
|
import argparse
|
|
10
|
+
import glob
|
|
9
11
|
import json
|
|
10
12
|
import os
|
|
13
|
+
import re
|
|
11
14
|
import subprocess
|
|
12
15
|
import time
|
|
13
16
|
from datetime import datetime, timezone
|
|
@@ -16,6 +19,88 @@ from pathlib import Path
|
|
|
16
19
|
SWEEP_ID = os.environ.get("OMNILANE_TRANSPORT_SWEEP_ID", "overlay-reprobe-20260909")
|
|
17
20
|
DEFAULT_ROOT = Path.home() / ".omnilane" / "transport-evidence" / SWEEP_ID
|
|
18
21
|
|
|
22
|
+
# Ordered strongest first. The tier is read off the evidence a run produced, not
|
|
23
|
+
# off the vendor: a CLI that starts reporting a billed model earns the higher
|
|
24
|
+
# tier with no change here.
|
|
25
|
+
TIER_BILLED = "billed-model" # the provider named the model it charged for
|
|
26
|
+
TIER_ECHO = "client-echo" # the CLI recorded the model it asked for
|
|
27
|
+
TIER_SELECTOR = "selector-only" # the CLI accepted the selector and said no more
|
|
28
|
+
EVIDENCE_TIERS = (TIER_BILLED, TIER_ECHO, TIER_SELECTOR)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def codex_failure(stdout_text: str) -> str:
|
|
32
|
+
"""The last thing codex's event stream said went wrong, if anything.
|
|
33
|
+
|
|
34
|
+
Later events supersede earlier ones: a retry notice is progress, the message
|
|
35
|
+
on `turn.failed` is the outcome.
|
|
36
|
+
"""
|
|
37
|
+
latest = ""
|
|
38
|
+
for line in stdout_text.splitlines():
|
|
39
|
+
line = line.strip()
|
|
40
|
+
if not line.startswith("{"):
|
|
41
|
+
continue
|
|
42
|
+
try:
|
|
43
|
+
event = json.loads(line)
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
continue
|
|
46
|
+
if not isinstance(event, dict):
|
|
47
|
+
continue
|
|
48
|
+
if event.get("type") == "turn.failed":
|
|
49
|
+
message = (event.get("error") or {}).get("message")
|
|
50
|
+
elif event.get("type") == "error":
|
|
51
|
+
message = event.get("message")
|
|
52
|
+
else:
|
|
53
|
+
continue
|
|
54
|
+
if isinstance(message, str) and message:
|
|
55
|
+
latest = message
|
|
56
|
+
return f"; codex-event: {latest[:200]}" if latest else ""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _requested_model(command: list) -> str | None:
|
|
60
|
+
requested = None
|
|
61
|
+
for index, argument in enumerate(command):
|
|
62
|
+
if not isinstance(argument, str):
|
|
63
|
+
continue
|
|
64
|
+
if argument in ("--model", "-m") and index + 1 < len(command):
|
|
65
|
+
requested = command[index + 1]
|
|
66
|
+
elif argument.startswith("--model="):
|
|
67
|
+
requested = argument.split("=", 1)[1]
|
|
68
|
+
return requested
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def record_values(record_text: str) -> list[str]:
|
|
72
|
+
"""Comparable values from a client record digest: `key<TAB>value` lines.
|
|
73
|
+
|
|
74
|
+
`#` lines carry provenance and display labels that must never be compared —
|
|
75
|
+
agy's backend label is "Gemini 3.8 Flash (Low)", not a model identifier.
|
|
76
|
+
"""
|
|
77
|
+
values = []
|
|
78
|
+
for line in (record_text or "").splitlines():
|
|
79
|
+
if line.startswith("#") or "\t" not in line:
|
|
80
|
+
continue
|
|
81
|
+
value = line.split("\t", 1)[1].strip()
|
|
82
|
+
if value:
|
|
83
|
+
values.append(value)
|
|
84
|
+
return values
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _client_record(
|
|
88
|
+
evidence_json: dict,
|
|
89
|
+
record_text: str,
|
|
90
|
+
passed_reason: str,
|
|
91
|
+
) -> tuple[str, str, str | None, str]:
|
|
92
|
+
"""Judge a CLI's own on-disk record of the request it sent."""
|
|
93
|
+
values = record_values(record_text)
|
|
94
|
+
if not values:
|
|
95
|
+
return "pass", f"{passed_reason}; no-client-record", None, TIER_SELECTOR
|
|
96
|
+
observed_model = ", ".join(sorted(set(values)))
|
|
97
|
+
requested_model = _requested_model(evidence_json.get("command", []))
|
|
98
|
+
if not requested_model:
|
|
99
|
+
return "fail", "missing-requested-model", observed_model, TIER_SELECTOR
|
|
100
|
+
if requested_model not in values:
|
|
101
|
+
return "fail", "client-record-mismatch", observed_model, TIER_SELECTOR
|
|
102
|
+
return "pass", f"{passed_reason}-and-client-record-matched", observed_model, TIER_ECHO
|
|
103
|
+
|
|
19
104
|
|
|
20
105
|
def verdict(
|
|
21
106
|
evidence_json: dict,
|
|
@@ -23,25 +108,27 @@ def verdict(
|
|
|
23
108
|
stderr_text: str,
|
|
24
109
|
vendor: str,
|
|
25
110
|
expected_token: str | None,
|
|
26
|
-
|
|
111
|
+
extra: dict | None = None,
|
|
112
|
+
) -> tuple[str, str, str | None, str]:
|
|
27
113
|
"""Judge raw evidence without reading files, running commands or mutating it.
|
|
28
114
|
|
|
29
|
-
|
|
30
|
-
|
|
115
|
+
`extra` carries text the caller already gathered from disk, so this stays a
|
|
116
|
+
pure function that can re-judge an old sweep offline.
|
|
31
117
|
"""
|
|
118
|
+
extra = extra or {}
|
|
32
119
|
if evidence_json.get("timed_out"):
|
|
33
|
-
return "fail", "timeout", None
|
|
120
|
+
return "fail", "timeout", None, TIER_SELECTOR
|
|
34
121
|
if not expected_token:
|
|
35
|
-
return "fail", "missing-expected-token", None
|
|
122
|
+
return "fail", "missing-expected-token", None, TIER_SELECTOR
|
|
36
123
|
|
|
37
124
|
exit_code = evidence_json.get("exit_code")
|
|
38
125
|
if vendor == "claude":
|
|
39
126
|
try:
|
|
40
127
|
response = json.loads(stdout_text)
|
|
41
128
|
except (json.JSONDecodeError, TypeError):
|
|
42
|
-
return "fail", "invalid-json", None
|
|
129
|
+
return "fail", "invalid-json", None, TIER_SELECTOR
|
|
43
130
|
if not isinstance(response, dict):
|
|
44
|
-
return "fail", "invalid-json-result", None
|
|
131
|
+
return "fail", "invalid-json-result", None, TIER_SELECTOR
|
|
45
132
|
usage = response.get("modelUsage")
|
|
46
133
|
models = sorted(usage) if isinstance(usage, dict) else []
|
|
47
134
|
observed_model = ", ".join(models) or None
|
|
@@ -56,48 +143,168 @@ def verdict(
|
|
|
56
143
|
reason = "api-error"
|
|
57
144
|
else:
|
|
58
145
|
reason = "result-error"
|
|
59
|
-
return "fail", f"{reason}: {result[:120]}", observed_model
|
|
146
|
+
return "fail", f"{reason}: {result[:120]}", observed_model, TIER_SELECTOR
|
|
60
147
|
if not models:
|
|
61
|
-
return "fail", "missing-model-usage", None
|
|
62
|
-
|
|
63
|
-
requested_model = None
|
|
64
|
-
for index, argument in enumerate(command):
|
|
65
|
-
if argument == "--model" and index + 1 < len(command):
|
|
66
|
-
requested_model = command[index + 1]
|
|
67
|
-
elif isinstance(argument, str) and argument.startswith("--model="):
|
|
68
|
-
requested_model = argument.split("=", 1)[1]
|
|
148
|
+
return "fail", "missing-model-usage", None, TIER_SELECTOR
|
|
149
|
+
requested_model = _requested_model(evidence_json.get("command", []))
|
|
69
150
|
if not requested_model:
|
|
70
|
-
return "fail", "missing-requested-model", observed_model
|
|
151
|
+
return "fail", "missing-requested-model", observed_model, TIER_SELECTOR
|
|
71
152
|
if models != [requested_model]:
|
|
72
|
-
return "fail", "model-mismatch", observed_model
|
|
153
|
+
return "fail", "model-mismatch", observed_model, TIER_SELECTOR
|
|
73
154
|
if "unknown --effort" in stderr_text.lower():
|
|
74
|
-
return "fail", "effort-silently-defaulted", observed_model
|
|
155
|
+
return "fail", "effort-silently-defaulted", observed_model, TIER_SELECTOR
|
|
75
156
|
if exit_code != 0:
|
|
76
|
-
return "fail", f"exit-code: {exit_code}", observed_model
|
|
157
|
+
return "fail", f"exit-code: {exit_code}", observed_model, TIER_SELECTOR
|
|
77
158
|
if expected_token not in result:
|
|
78
|
-
return "fail", "missing-expected-token", observed_model
|
|
79
|
-
return "pass", "expected-token-and-model-matched", observed_model
|
|
159
|
+
return "fail", "missing-expected-token", observed_model, TIER_SELECTOR
|
|
160
|
+
return "pass", "expected-token-and-model-matched", observed_model, TIER_BILLED
|
|
161
|
+
|
|
162
|
+
if vendor == "grok":
|
|
163
|
+
if exit_code != 0:
|
|
164
|
+
return "fail", f"exit-code: {exit_code}: {stderr_text[:120]}", None, TIER_SELECTOR
|
|
165
|
+
if stderr_text:
|
|
166
|
+
return "fail", f"unexpected-stderr: {stderr_text[:120]}", None, TIER_SELECTOR
|
|
167
|
+
if expected_token not in stdout_text:
|
|
168
|
+
return "fail", "missing-expected-token", None, TIER_SELECTOR
|
|
169
|
+
try:
|
|
170
|
+
response = json.loads(stdout_text)
|
|
171
|
+
except (json.JSONDecodeError, TypeError):
|
|
172
|
+
response = None
|
|
173
|
+
usage = response.get("modelUsage") if isinstance(response, dict) else None
|
|
174
|
+
models = sorted(usage) if isinstance(usage, dict) else []
|
|
175
|
+
if not models:
|
|
176
|
+
return "pass", "expected-token-and-clean-stderr; no-billed-model", None, TIER_SELECTOR
|
|
177
|
+
observed_model = ", ".join(models)
|
|
178
|
+
requested_model = _requested_model(evidence_json.get("command", []))
|
|
179
|
+
if not requested_model:
|
|
180
|
+
return "fail", "missing-requested-model", observed_model, TIER_SELECTOR
|
|
181
|
+
# Grok bills `grok-4.6` as `grok-4.6-build`. Accept that one suffix and
|
|
182
|
+
# nothing else: a prefix test would let `grok-4.6-anything` pass.
|
|
183
|
+
if models not in ([requested_model], [f"{requested_model}-build"]):
|
|
184
|
+
return "fail", "model-mismatch", observed_model, TIER_SELECTOR
|
|
185
|
+
return "pass", "expected-token-and-billed-model-matched", observed_model, TIER_BILLED
|
|
80
186
|
|
|
81
|
-
if vendor
|
|
187
|
+
if vendor == "agy":
|
|
82
188
|
if exit_code != 0:
|
|
83
|
-
return "fail", f"exit-code: {exit_code}: {stderr_text[:120]}", None
|
|
189
|
+
return "fail", f"exit-code: {exit_code}: {stderr_text[:120]}", None, TIER_SELECTOR
|
|
84
190
|
if stderr_text:
|
|
85
|
-
return "fail", f"unexpected-stderr: {stderr_text[:120]}", None
|
|
191
|
+
return "fail", f"unexpected-stderr: {stderr_text[:120]}", None, TIER_SELECTOR
|
|
86
192
|
if expected_token not in stdout_text:
|
|
87
|
-
return "fail", "missing-expected-token", None
|
|
88
|
-
return "
|
|
193
|
+
return "fail", "missing-expected-token", None, TIER_SELECTOR
|
|
194
|
+
return _client_record(evidence_json, extra.get("cli_log", ""),
|
|
195
|
+
"expected-token-and-clean-stderr")
|
|
89
196
|
|
|
90
197
|
if vendor == "codex":
|
|
91
198
|
diagnostics = [line[:120] for line in stderr_text.splitlines()
|
|
92
199
|
if "error" in line.lower() or "warning" in line.lower()]
|
|
93
200
|
review = "; stderr-review: " + " | ".join(diagnostics) if diagnostics else ""
|
|
201
|
+
# Under `--json` the refusal that ended the run is an stdout event, not
|
|
202
|
+
# a stderr line, so a failure would otherwise be recorded as a bare exit
|
|
203
|
+
# code and leave unproven[] saying nothing a reader can act on.
|
|
204
|
+
why = codex_failure(stdout_text)
|
|
94
205
|
if exit_code != 0:
|
|
95
|
-
return "fail", f"exit-code: {exit_code}{review}", None
|
|
206
|
+
return "fail", f"exit-code: {exit_code}{why}{review}", None, TIER_SELECTOR
|
|
96
207
|
if expected_token not in stdout_text:
|
|
97
|
-
return "fail", f"missing-expected-token{review}", None
|
|
98
|
-
|
|
208
|
+
return "fail", f"missing-expected-token{why}{review}", None, TIER_SELECTOR
|
|
209
|
+
result, reason, observed, tier = _client_record(
|
|
210
|
+
evidence_json, extra.get("rollout", ""), "expected-token-matched")
|
|
211
|
+
return result, f"{reason}{review}", observed, tier
|
|
212
|
+
|
|
213
|
+
return "fail", f"unsupported-vendor: {vendor}", None, TIER_SELECTOR
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _sha256(path: Path) -> str:
|
|
217
|
+
import hashlib
|
|
218
|
+
|
|
219
|
+
digest = hashlib.sha256()
|
|
220
|
+
with open(path, "rb") as stream:
|
|
221
|
+
for block in iter(lambda: stream.read(1 << 20), b""):
|
|
222
|
+
digest.update(block)
|
|
223
|
+
return digest.hexdigest()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _walk_models(node, prefix: str, found: dict) -> None:
|
|
227
|
+
if isinstance(node, dict):
|
|
228
|
+
for key, value in node.items():
|
|
229
|
+
here = f"{prefix}.{key}" if prefix else key
|
|
230
|
+
if key == "model" and isinstance(value, str):
|
|
231
|
+
found.setdefault(here, value)
|
|
232
|
+
_walk_models(value, here, found)
|
|
233
|
+
elif isinstance(node, list):
|
|
234
|
+
for item in node:
|
|
235
|
+
_walk_models(item, f"{prefix}[]", found)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def codex_record(stdout_text: str, sessions_dir: Path | None = None) -> str:
|
|
239
|
+
"""Digest the rollout codex persisted for the thread this run started.
|
|
240
|
+
|
|
241
|
+
Needs `codex exec --json` for the thread id and no `--ephemeral`, which
|
|
242
|
+
would suppress the rollout this reads.
|
|
243
|
+
"""
|
|
244
|
+
thread_id = None
|
|
245
|
+
for line in stdout_text.splitlines():
|
|
246
|
+
line = line.strip()
|
|
247
|
+
if not line.startswith("{"):
|
|
248
|
+
continue
|
|
249
|
+
try:
|
|
250
|
+
event = json.loads(line)
|
|
251
|
+
except json.JSONDecodeError:
|
|
252
|
+
continue
|
|
253
|
+
if isinstance(event, dict) and isinstance(event.get("thread_id"), str):
|
|
254
|
+
thread_id = event["thread_id"]
|
|
255
|
+
break
|
|
256
|
+
if not thread_id:
|
|
257
|
+
return ""
|
|
258
|
+
root = sessions_dir or Path.home() / ".codex" / "sessions"
|
|
259
|
+
matches = sorted(glob.glob(str(root / "**" / f"rollout-*{thread_id}.jsonl"), recursive=True))
|
|
260
|
+
if not matches:
|
|
261
|
+
return ""
|
|
262
|
+
path = Path(matches[0])
|
|
263
|
+
found: dict[str, str] = {}
|
|
264
|
+
for line in path.read_text(errors="replace").splitlines():
|
|
265
|
+
line = line.strip()
|
|
266
|
+
if not line.startswith("{"):
|
|
267
|
+
continue
|
|
268
|
+
try:
|
|
269
|
+
event = json.loads(line)
|
|
270
|
+
except json.JSONDecodeError:
|
|
271
|
+
continue
|
|
272
|
+
_walk_models(event, "", found)
|
|
273
|
+
lines = [f"# thread_id\t{thread_id}", f"# source\t{path}", f"# sha256\t{_sha256(path)}"]
|
|
274
|
+
lines += [f"{key}\t{value}" for key, value in sorted(found.items())]
|
|
275
|
+
return "\n".join(lines) + "\n"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
AGY_MODEL = re.compile(r"Resolving model (\S+)")
|
|
279
|
+
AGY_LABEL = re.compile(r'selected model override to backend: label="([^"]+)"')
|
|
280
|
+
|
|
99
281
|
|
|
100
|
-
|
|
282
|
+
def agy_record(app_root: Path, since: float) -> str:
|
|
283
|
+
"""Digest the model agy resolved in the logs this run wrote under app_root.
|
|
284
|
+
|
|
285
|
+
`since` rejects logs from an earlier run, so a reused app root cannot lend
|
|
286
|
+
its model string to a later probe.
|
|
287
|
+
"""
|
|
288
|
+
app_root = Path(app_root).expanduser()
|
|
289
|
+
if not app_root.is_dir():
|
|
290
|
+
return ""
|
|
291
|
+
lines: list[str] = []
|
|
292
|
+
values: dict[str, str] = {}
|
|
293
|
+
for path in sorted(app_root.rglob("cli*.log")):
|
|
294
|
+
if not path.is_file() or path.stat().st_mtime < since - 2:
|
|
295
|
+
continue
|
|
296
|
+
text = path.read_text(errors="replace")
|
|
297
|
+
models = AGY_MODEL.findall(text)
|
|
298
|
+
if not models:
|
|
299
|
+
continue
|
|
300
|
+
lines += [f"# source\t{path}", f"# sha256\t{_sha256(path)}"]
|
|
301
|
+
lines += [f"# label\t{label}" for label in dict.fromkeys(AGY_LABEL.findall(text))]
|
|
302
|
+
for model in models:
|
|
303
|
+
values.setdefault(f"{path.name}:resolved-model:{model}", model)
|
|
304
|
+
if not values:
|
|
305
|
+
return ""
|
|
306
|
+
lines += [f"{key}\t{value}" for key, value in sorted(values.items())]
|
|
307
|
+
return "\n".join(lines) + "\n"
|
|
101
308
|
|
|
102
309
|
|
|
103
310
|
def probe(
|
|
@@ -109,6 +316,7 @@ def probe(
|
|
|
109
316
|
*,
|
|
110
317
|
vendor: str | None = None,
|
|
111
318
|
expected_token: str | None = None,
|
|
319
|
+
app_root: Path | None = None,
|
|
112
320
|
) -> dict:
|
|
113
321
|
if not expected_token:
|
|
114
322
|
raise ValueError("expected_token is required before running a probe")
|
|
@@ -148,10 +356,24 @@ def probe(
|
|
|
148
356
|
"stdout": str(out_path),
|
|
149
357
|
"stderr": str(err_path),
|
|
150
358
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
359
|
+
stdout_text = out_path.read_text(errors="replace")
|
|
360
|
+
extra = {}
|
|
361
|
+
if vendor == "codex":
|
|
362
|
+
extra["rollout"] = codex_record(stdout_text)
|
|
363
|
+
elif vendor == "agy" and app_root is not None:
|
|
364
|
+
extra["cli_log"] = agy_record(app_root, started)
|
|
365
|
+
for key, text in extra.items():
|
|
366
|
+
if not text:
|
|
367
|
+
continue
|
|
368
|
+
# Stored so the sweep hashes it and verdict() can re-judge it offline.
|
|
369
|
+
record_path = evidence / f"{name}.{key}"
|
|
370
|
+
record_path.write_text(text)
|
|
371
|
+
record[key] = str(record_path)
|
|
372
|
+
record["verdict"], record["verdict_reason"], record["observed_model"], \
|
|
373
|
+
record["evidence_tier"] = verdict(
|
|
374
|
+
record, stdout_text, err_path.read_text(errors="replace"),
|
|
375
|
+
vendor, expected_token, extra,
|
|
376
|
+
)
|
|
155
377
|
(evidence / f"{name}.json").write_text(json.dumps(record, indent=2) + "\n")
|
|
156
378
|
return record
|
|
157
379
|
|
|
@@ -174,11 +396,13 @@ if __name__ == "__main__":
|
|
|
174
396
|
parser.add_argument("--expect", required=True, help="expected response token")
|
|
175
397
|
parser.add_argument("--vendor", choices=("claude", "grok", "agy", "codex"),
|
|
176
398
|
help="defaults to the command executable's basename")
|
|
399
|
+
parser.add_argument("--app-root", type=Path,
|
|
400
|
+
help="agy app data root for this run, to read back its cli log")
|
|
177
401
|
parser.add_argument("name")
|
|
178
402
|
parser.add_argument("command", nargs=argparse.REMAINDER)
|
|
179
403
|
args = parser.parse_args()
|
|
180
404
|
if not args.command:
|
|
181
405
|
parser.error("command is required")
|
|
182
|
-
rec = probe(args.name, args.command, root=args.root,
|
|
406
|
+
rec = probe(args.name, args.command, root=args.root, app_root=args.app_root,
|
|
183
407
|
vendor=args.vendor, expected_token=args.expect)
|
|
184
408
|
print(json.dumps(rec, ensure_ascii=False))
|
package/skills/omnilane/SKILL.md
CHANGED
|
@@ -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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
**
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
|
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
|
|
@@ -388,9 +401,24 @@ selector evidence. Evidence entries carry a `vendor` tag: a tagged entry that
|
|
|
388
401
|
drifts marks only its own vendor stale, and the other three keep dispatching.
|
|
389
402
|
Untagged evidence — `probe-manifest.json`, and any overlay built before the tags
|
|
390
403
|
existed — still fails the whole gate closed, which is what an unpatched host
|
|
391
|
-
looks like. Codex and Claude
|
|
392
|
-
(`releases/0.153.4-…`, `versions/2.1.
|
|
393
|
-
rather than change its digest; both are treated as staleness, not corruption.
|
|
404
|
+
looks like. Codex and Claude resolve through version directories
|
|
405
|
+
(`releases/0.153.4-…`, `versions/2.1.266`), so their upgrades remove the anchored
|
|
406
|
+
file rather than change its digest; both are treated as staleness, not corruption.
|
|
407
|
+
|
|
408
|
+
Do not expect these upgrades to be operator actions. agy and grok update
|
|
409
|
+
themselves in the background when invoked — agy's own `cli.log` records
|
|
410
|
+
`auto_updater.go: Spawned background update process`, and both binaries changed
|
|
411
|
+
under a probing session on 2026-09-10, minutes after their first call. Overlay
|
|
412
|
+
drift is therefore a routine consequence of using a vendor, not an occasional
|
|
413
|
+
maintenance event, which is why per-vendor degradation matters more than it
|
|
414
|
+
looks. It also means any test asserting a fixed number of verified live
|
|
415
|
+
mappings will go red on its own schedule.
|
|
416
|
+
|
|
417
|
+
Because of that, `build_overlay.py` anchors the executable `shutil.which` resolves
|
|
418
|
+
rather than a version written into the script. A pinned path drifts out of use
|
|
419
|
+
silently: before 0.42.6 the overlay hashed claude `2.1.263` while every dispatch
|
|
420
|
+
ran `2.1.266`, so eleven mappings were "verified" against a binary that had not
|
|
421
|
+
run for a day.
|
|
394
422
|
|
|
395
423
|
Re-signing is a probe, a rebuild, and an install, in that order. Back up
|
|
396
424
|
`~/.omnilane/transport-contracts.local.json` first; restoring it is the rollback.
|
|
@@ -408,6 +436,30 @@ Keep the sweep where its default `--root` puts it,
|
|
|
408
436
|
inside a repository is one `git clean -fdx` away from taking every vendor down
|
|
409
437
|
at once — the same global refusal a re-signing session is usually trying to end.
|
|
410
438
|
|
|
439
|
+
Every mapping carries an `evidence_tier` saying how strongly its probe pinned the
|
|
440
|
+
responder. `billed-model` means the provider named the model it charged for —
|
|
441
|
+
Claude's `modelUsage`, grok's under `--output-format json`. `client-echo` means
|
|
442
|
+
the CLI wrote down the model it asked for — codex's session rollout, agy's
|
|
443
|
+
`cli.log` resolver line. `selector-only` means the CLI accepted the selector and
|
|
444
|
+
said nothing more. Put plainly: `client-echo` is the CLI's copy of your order,
|
|
445
|
+
`billed-model` is the provider's receipt. Neither certifies upstream identity,
|
|
446
|
+
but only one of them was written by the party that answered.
|
|
447
|
+
|
|
448
|
+
The tier is reported, never enforced. Dispatch still turns on `runtime_verified`
|
|
449
|
+
alone, so a mapping that drops to `selector-only` keeps working and simply shows
|
|
450
|
+
up in doctor as worth re-probing. Do not add a tier check to the gate: that would
|
|
451
|
+
rebuild the failure 0.42.5 removed, where evidence quality could refuse a lane
|
|
452
|
+
that runs. The tier is read off the evidence a run produced rather than assigned
|
|
453
|
+
per vendor, so a sweep predating 0.42.6 re-judges as `selector-only` and a CLI
|
|
454
|
+
that starts reporting a billed model is promoted with no code change.
|
|
455
|
+
|
|
456
|
+
Two probe details follow from this. Codex needs `exec --json` (the thread id that
|
|
457
|
+
locates the rollout) and must *not* use `--ephemeral`, which suppresses the very
|
|
458
|
+
rollout the tier reads. agy needs its own app data directory, prepared exactly
|
|
459
|
+
the way `run-gemini.sh` does it — `prepare-agy-mode.py --mode advise` returns a
|
|
460
|
+
path relative to `~/.gemini` that is passed as `--app_data_dir=`; the environment
|
|
461
|
+
variables that look like they would do this are ignored.
|
|
462
|
+
|
|
411
463
|
Never sign a probe you did not read. `probe.py` records a `verdict` because exit
|
|
412
464
|
status alone is not evidence: the Claude CLI answers a quota refusal with a JSON
|
|
413
465
|
body carrying `is_error`, and it accepts an unknown `--effort` by silently using
|