omnilane 0.31.0 → 0.33.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.
@@ -9,6 +9,34 @@ export OMNILANE_REPO
9
9
  # Publishable default is plain CLIs on PATH; power users add ~/.omnilane/local.sh.
10
10
  [[ -f "$OMNILANE_HOME/local.sh" ]] && source "$OMNILANE_HOME/local.sh"
11
11
 
12
+ # The unquoted backslash case pattern intentionally matches one backslash.
13
+ # shellcheck disable=SC1003
14
+ json_escape() {
15
+ local s="$1" out="" ch escaped code i
16
+ for ((i = 0; i < ${#s}; i++)); do
17
+ ch="${s:i:1}"
18
+ case "$ch" in
19
+ '"') out="$out\\\"" ;;
20
+ \\) out="$out\\\\" ;;
21
+ $'\b') out="$out\\b" ;;
22
+ $'\f') out="$out\\f" ;;
23
+ $'\n') out="$out\\n" ;;
24
+ $'\r') out="$out\\r" ;;
25
+ $'\t') out="$out\\t" ;;
26
+ *)
27
+ LC_CTYPE=C printf -v code '%d' "'$ch"
28
+ if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
29
+ printf -v escaped '\\u%04x' "$code"
30
+ out="$out$escaped"
31
+ else
32
+ out="$out$ch"
33
+ fi
34
+ ;;
35
+ esac
36
+ done
37
+ printf '%s' "$out"
38
+ }
39
+
12
40
  resolve_timeout_cmd() {
13
41
  if command -v timeout &>/dev/null; then echo "timeout";
14
42
  elif command -v gtimeout &>/dev/null; then echo "gtimeout";
@@ -284,6 +312,66 @@ prepare_private_store() { # path, diagnostic label
284
312
  chmod 700 "$store_root" || return 1
285
313
  }
286
314
 
315
+ OMNILANE_THREAD_NAME_PATTERN='^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
316
+
317
+ omnilane_valid_thread_name() {
318
+ [[ "${1:-}" =~ $OMNILANE_THREAD_NAME_PATTERN ]]
319
+ }
320
+
321
+ prepare_threads_store() {
322
+ prepare_private_store "$OMNILANE_HOME/threads" "thread store"
323
+ }
324
+
325
+ read_thread_state() { # path, expected name; populates THREAD_STATE_*
326
+ local path="$1" expected_name="$2" fields separator=$'\034'
327
+ THREAD_STATE_NAME=""
328
+ THREAD_STATE_VENDOR=""
329
+ THREAD_STATE_MODEL=""
330
+ THREAD_STATE_EFFORT=""
331
+ THREAD_STATE_WORKDIR=""
332
+ THREAD_STATE_SESSION_ID=""
333
+ THREAD_STATE_TURNS=""
334
+ THREAD_STATE_LAST_JOB_ID=""
335
+ THREAD_STATE_CREATED=""
336
+ THREAD_STATE_UPDATED=""
337
+
338
+ [[ -f "$path" && ! -L "$path" ]] || return 1
339
+ fields="$(perl -MJSON::PP -e '
340
+ use strict;
341
+ use warnings;
342
+ my ($path, $expected) = @ARGV;
343
+ my $size = -s $path;
344
+ die "invalid size\n" unless defined($size) && $size > 0 && $size <= 16384;
345
+ open my $fh, "<", $path or die $!;
346
+ local $/;
347
+ my $state = decode_json(<$fh>);
348
+ die "invalid state\n" unless ref($state) eq "HASH";
349
+ my @string_keys = qw(name vendor model effort workdir session_id last_job_id created updated);
350
+ for my $key (@string_keys) {
351
+ my $value = $state->{$key};
352
+ die "invalid $key\n" if !defined($value) || ref($value) || $value =~ /[\x00-\x1f\x7f]/;
353
+ }
354
+ die "wrong name\n" unless $state->{name} eq $expected;
355
+ die "invalid name\n" unless $state->{name} =~ /\A[A-Za-z0-9][A-Za-z0-9._-]{0,63}\z/;
356
+ die "invalid vendor\n" unless $state->{vendor} =~ /\A[a-z][a-z0-9-]*\z/;
357
+ die "invalid session\n" unless $state->{session_id} =~ /\A[A-Za-z0-9._:-]{1,256}\z/;
358
+ die "invalid turns\n" if ref($state->{turns}) || ($state->{turns} // "") !~ /\A[1-9][0-9]{0,8}\z/;
359
+ die "invalid job id\n" unless $state->{last_job_id} =~ /\A[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+\z/;
360
+ for my $key (qw(created updated)) {
361
+ die "invalid timestamp\n" unless $state->{$key} =~ /\A[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\z/;
362
+ }
363
+ die "field too long\n" if length($state->{model}) > 512 || length($state->{effort}) > 128 || length($state->{workdir}) > 4096;
364
+ print join(chr(28), map { $state->{$_} } qw(name vendor model effort workdir session_id turns last_job_id created updated));
365
+ ' "$path" "$expected_name" 2>/dev/null)" || return 1
366
+
367
+ # shellcheck disable=SC2034 # globals consumed by dispatch.sh and jobs.sh
368
+ IFS="$separator" read -r THREAD_STATE_NAME THREAD_STATE_VENDOR \
369
+ THREAD_STATE_MODEL THREAD_STATE_EFFORT THREAD_STATE_WORKDIR \
370
+ THREAD_STATE_SESSION_ID THREAD_STATE_TURNS THREAD_STATE_LAST_JOB_ID \
371
+ THREAD_STATE_CREATED THREAD_STATE_UPDATED <<< "$fields"
372
+ [[ -n "$THREAD_STATE_UPDATED" ]]
373
+ }
374
+
287
375
  prepare_inbox_store() {
288
376
  prepare_private_store "$OMNILANE_HOME/inbox" "inbox store"
289
377
  }
@@ -21,34 +21,6 @@ RUNNER="$OMNILANE_REPO/scripts/runners/run-$VENDOR.sh"
21
21
  # Two concurrent codex execs in one target dir corrupt its job index — serialize.
22
22
  [[ "$VENDOR" == "codex" ]] && acquire_cwd_lock codex "$WORKDIR"
23
23
 
24
- # The backslash case pattern is intentional.
25
- # shellcheck disable=SC1003
26
- json_escape() {
27
- local s="$1" out="" ch escaped code i
28
- for ((i = 0; i < ${#s}; i++)); do
29
- ch="${s:i:1}"
30
- case "$ch" in
31
- '"') out="$out\\\"" ;;
32
- '\\') out="$out\\\\" ;;
33
- $'\b') out="$out\\b" ;;
34
- $'\f') out="$out\\f" ;;
35
- $'\n') out="$out\\n" ;;
36
- $'\r') out="$out\\r" ;;
37
- $'\t') out="$out\\t" ;;
38
- *)
39
- LC_CTYPE=C printf -v code '%d' "'$ch"
40
- if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
41
- printf -v escaped '\\u%04x' "$code"
42
- out="$out$escaped"
43
- else
44
- out="$out$ch"
45
- fi
46
- ;;
47
- esac
48
- done
49
- printf '%s' "$out"
50
- }
51
-
52
24
  emit_mode_notice() {
53
25
  local notice="$1" notice_file="${OUTPUT_FILE%/*}/mode-notice.txt"
54
26
  printf '%s\n' "$notice" >&2
@@ -23,7 +23,7 @@ json_escape() {
23
23
  ch="${s:i:1}"
24
24
  case "$ch" in
25
25
  '"') out="$out\\\"" ;;
26
- '\\') out="$out\\\\" ;;
26
+ \\) out="$out\\\\" ;;
27
27
  $'\b') out="$out\\b" ;;
28
28
  $'\f') out="$out\\f" ;;
29
29
  $'\n') out="$out\\n" ;;
@@ -57,7 +57,7 @@ json_escape() {
57
57
  ch="${value:i:1}"
58
58
  case "$ch" in
59
59
  '"') out="$out\\\"" ;;
60
- '\\') out="$out\\\\" ;;
60
+ \\) out="$out\\\\" ;;
61
61
  $'\b') out="$out\\b" ;;
62
62
  $'\f') out="$out\\f" ;;
63
63
  $'\n') out="$out\\n" ;;
@@ -14,6 +14,21 @@ RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
14
14
 
15
15
  truncate_payload "$PROMPT_FILE" 102400
16
16
 
17
+ THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
18
+ THREAD_ID="${OMNILANE_THREAD_ID:-}"
19
+ THREAD_ARGS=()
20
+ if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
21
+ [[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
22
+ echo "omnilane: invalid Claude thread session id" >&2
23
+ exit 2
24
+ }
25
+ case "$THREAD_MODE" in
26
+ new) THREAD_ARGS=(--session-id "$THREAD_ID") ;;
27
+ resume) THREAD_ARGS=(--resume "$THREAD_ID") ;;
28
+ *) echo "omnilane: invalid Claude thread mode" >&2; exit 2 ;;
29
+ esac
30
+ fi
31
+
17
32
  LIVE_INBOX="${OMNILANE_INBOX:-}"
18
33
  if [[ -n "$LIVE_INBOX" && -p "$LIVE_INBOX" ]]; then
19
34
  EVENTS_FILE="${OUTPUT_FILE}.events.jsonl"
@@ -118,7 +133,7 @@ PY
118
133
  exit "$RC"
119
134
  fi
120
135
 
121
- ARGS=(--disable-slash-commands --model "$MODEL" --output-format text)
136
+ ARGS=(--disable-slash-commands --model "$MODEL")
122
137
  [[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(--effort "$EFFORT")
123
138
  if [[ "$MODE" == "advise" ]]; then
124
139
  # Read-only surface: the worker can inspect the repo but not change or run anything.
@@ -126,18 +141,62 @@ if [[ "$MODE" == "advise" ]]; then
126
141
  else
127
142
  ARGS+=(--permission-mode acceptEdits)
128
143
  fi
144
+ if [[ -n "$THREAD_MODE" ]]; then
145
+ ARGS+=(--verbose --output-format stream-json)
146
+ ARGS+=("${THREAD_ARGS[@]}")
147
+ else
148
+ ARGS+=(--output-format text)
149
+ fi
129
150
  ARGS+=(-p "$(cat "$PROMPT_FILE")")
130
151
 
131
152
  set +e
132
153
  (
133
154
  cd "$WORKDIR" || exit 127
134
- run_with_timeout "$RUN_TIMEOUT" env \
135
- OMNILANE_DEPTH=1 \
136
- "$CLAUDE_BIN" "${ARGS[@]}" > "${OUTPUT_FILE}.tmp" 2> "${OUTPUT_FILE}.stderr.log"
155
+ if [[ -n "$THREAD_MODE" ]]; then
156
+ run_with_timeout "$RUN_TIMEOUT" env \
157
+ OMNILANE_DEPTH=1 \
158
+ "$CLAUDE_BIN" "${ARGS[@]}" > "${OUTPUT_FILE}.events.jsonl" 2> "${OUTPUT_FILE}.stderr.log"
159
+ else
160
+ run_with_timeout "$RUN_TIMEOUT" env \
161
+ OMNILANE_DEPTH=1 \
162
+ "$CLAUDE_BIN" "${ARGS[@]}" > "${OUTPUT_FILE}.tmp" 2> "${OUTPUT_FILE}.stderr.log"
163
+ fi
137
164
  )
138
165
  RC=$?
139
166
  set -e
140
167
 
168
+ if [[ -n "$THREAD_MODE" ]]; then
169
+ if [[ "$RC" -eq 0 ]]; then
170
+ if ! python3 - "$OUTPUT_FILE.events.jsonl" "${OUTPUT_FILE}.tmp" <<'PY'
171
+ import json
172
+ import pathlib
173
+ import sys
174
+
175
+ events_path = pathlib.Path(sys.argv[1])
176
+ output_path = pathlib.Path(sys.argv[2])
177
+ last_result = None
178
+ with events_path.open(encoding="utf-8") as events:
179
+ for raw_line in events:
180
+ try:
181
+ event = json.loads(raw_line)
182
+ except json.JSONDecodeError:
183
+ continue
184
+ if event.get("type") == "result" and isinstance(event.get("result"), str):
185
+ last_result = event["result"]
186
+ if last_result is None:
187
+ raise SystemExit(1)
188
+ output_path.write_text(last_result.rstrip("\n") + "\n", encoding="utf-8")
189
+ PY
190
+ then
191
+ echo "omnilane: Claude thread stream ended without readable result event" >> "${OUTPUT_FILE}.stderr.log"
192
+ RC=1
193
+ fi
194
+ fi
195
+ if [[ "$RC" -ne 0 && -s "${OUTPUT_FILE}.stderr.log" ]]; then
196
+ cat "${OUTPUT_FILE}.stderr.log" >&2
197
+ cat "${OUTPUT_FILE}.stderr.log" > "${OUTPUT_FILE}.tmp"
198
+ fi
199
+ fi
141
200
  [[ -f "${OUTPUT_FILE}.tmp" ]] && mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE"
142
201
  [[ -s "${OUTPUT_FILE}.stderr.log" ]] || rm "${OUTPUT_FILE}.stderr.log" 2>/dev/null || true
143
202
  exit "$RC"
@@ -14,19 +14,46 @@ MODE="$1"; WORKDIR="$2"; MODEL="$3"; EFFORT="$4"; PROMPT_FILE="$5"; OUTPUT_FILE=
14
14
  CODEX_BIN="${CODEX_BIN:-codex}"
15
15
  RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
16
16
 
17
+ THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
18
+ THREAD_ID="${OMNILANE_THREAD_ID:-}"
19
+ if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
20
+ [[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
21
+ echo "omnilane: invalid Codex thread session id" >&2
22
+ exit 2
23
+ }
24
+ case "$THREAD_MODE" in
25
+ new) ;;
26
+ resume) ;;
27
+ *) echo "omnilane: invalid Codex thread mode" >&2; exit 2 ;;
28
+ esac
29
+ fi
30
+
17
31
  # --skip-git-repo-check: the operator chose WORKDIR explicitly; codex would
18
32
  # otherwise refuse any directory that is not a trusted git repo.
19
33
  # --json: without it codex writes nothing until it exits, so a watchdog kill
20
34
  # leaves an empty progress log that looks identical to a run that never started.
21
- ARGS=(exec --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
35
+ if [[ "$THREAD_MODE" == "resume" ]]; then
36
+ ARGS=(exec resume --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
37
+ else
38
+ ARGS=(exec --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
39
+ fi
22
40
  [[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(-c "model_reasoning_effort=\"$EFFORT\"")
23
41
  if [[ "$MODE" == "advise" ]]; then
24
- ARGS+=(--ephemeral -s read-only)
42
+ [[ -z "$THREAD_MODE" ]] && ARGS+=(--ephemeral)
43
+ SANDBOX=read-only
25
44
  elif [[ "$MODE" == "sysops" ]]; then
26
- ARGS+=(-s danger-full-access)
45
+ SANDBOX=danger-full-access
46
+ else
47
+ SANDBOX=workspace-write
48
+ fi
49
+ # `codex exec resume` has no -s/--sandbox flag (rejects it with exit 2); the
50
+ # same policy is only reachable there through the sandbox_mode config override.
51
+ if [[ "$THREAD_MODE" == "resume" ]]; then
52
+ ARGS+=(-c "sandbox_mode=\"$SANDBOX\"")
27
53
  else
28
- ARGS+=(-s workspace-write)
54
+ ARGS+=(-s "$SANDBOX")
29
55
  fi
56
+ [[ "$THREAD_MODE" == "resume" ]] && ARGS+=("$THREAD_ID" -)
30
57
 
31
58
  truncate_payload "$PROMPT_FILE" 140000
32
59
 
@@ -14,6 +14,21 @@ AGY_BIN="${AGY_BIN:-agy}"
14
14
  RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
15
15
  CAPACITY_PATTERN='MODEL_CAPACITY_EXHAUSTED|No capacity available for model|rateLimitExceeded|RESOURCE_EXHAUSTED'
16
16
 
17
+ THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
18
+ THREAD_ID="${OMNILANE_THREAD_ID:-}"
19
+ THREAD_ARGS=()
20
+ if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
21
+ [[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
22
+ echo "omnilane: invalid Gemini thread session id" >&2
23
+ exit 2
24
+ }
25
+ case "$THREAD_MODE" in
26
+ new) ;;
27
+ resume) THREAD_ARGS=(--conversation "$THREAD_ID") ;;
28
+ *) echo "omnilane: invalid Gemini thread mode" >&2; exit 2 ;;
29
+ esac
30
+ fi
31
+
17
32
  truncate_payload "$PROMPT_FILE" 140000
18
33
 
19
34
  # Both modes run inside the target WORKDIR so the worker can actually see the
@@ -142,6 +157,38 @@ PY
142
157
  exit "$RC"
143
158
  fi
144
159
 
160
+ if [[ -n "$THREAD_MODE" ]]; then
161
+ set +e
162
+ (
163
+ cd "$RUN_DIR" || exit 127
164
+ env -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_AI_API_KEY \
165
+ NO_BROWSER=1 OMNILANE_DEPTH=1 \
166
+ "$AGY_BIN" --dangerously-skip-permissions --add-dir "$RUN_DIR" \
167
+ "${MODE_ARGS[@]}" ${MODEL_ARGS[@]+"${MODEL_ARGS[@]}"} \
168
+ --print-timeout "${RUN_TIMEOUT}s" --output-format json \
169
+ "${THREAD_ARGS[@]}" -p "$(cat "$PROMPT_FILE")" \
170
+ > "${OUTPUT_FILE}.result.json" 2> "${OUTPUT_FILE}.stderr.log"
171
+ )
172
+ RC=$?
173
+ set -e
174
+ if [[ "$RC" -eq 0 ]]; then
175
+ if ! python3 - "${OUTPUT_FILE}.result.json" "${OUTPUT_FILE}.tmp" <<'PY'
176
+ import json
177
+ import pathlib
178
+ import sys
179
+
180
+ result = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
181
+ response = result.get("response")
182
+ if result.get("status") != "SUCCESS" or not isinstance(response, str):
183
+ raise SystemExit(1)
184
+ pathlib.Path(sys.argv[2]).write_text(response.rstrip("\n") + "\n", encoding="utf-8")
185
+ PY
186
+ then
187
+ echo "omnilane: Gemini thread result was not readable SUCCESS JSON" >> "${OUTPUT_FILE}.stderr.log"
188
+ RC=1
189
+ fi
190
+ fi
191
+ else
145
192
  set +e
146
193
  (
147
194
  cd "$RUN_DIR" || exit 127
@@ -158,8 +205,9 @@ set +e
158
205
  )
159
206
  RC=$?
160
207
  set -e
208
+ fi
161
209
 
162
- if grep -Eiq "$CAPACITY_PATTERN" "${OUTPUT_FILE}.tmp" "${OUTPUT_FILE}.stderr.log" 2>/dev/null; then
210
+ if grep -Eiq "$CAPACITY_PATTERN" "${OUTPUT_FILE}.tmp" "${OUTPUT_FILE}.result.json" "${OUTPUT_FILE}.stderr.log" 2>/dev/null; then
163
211
  echo "omnilane: gemini capacity exhausted" >> "${OUTPUT_FILE}.stderr.log"
164
212
  RC=126
165
213
  fi
@@ -17,14 +17,30 @@ MAX_ATTEMPTS="${OMNILANE_GROK_MAX_ATTEMPTS:-5}"
17
17
  exit 2
18
18
  }
19
19
 
20
+ THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
21
+ THREAD_ID="${OMNILANE_THREAD_ID:-}"
22
+ THREAD_ARGS=()
23
+ if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
24
+ [[ "$THREAD_ID" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]] || {
25
+ echo "omnilane: invalid Grok thread session id" >&2
26
+ exit 2
27
+ }
28
+ case "$THREAD_MODE" in
29
+ new) THREAD_ARGS=(--session-id "$THREAD_ID") ;;
30
+ resume) THREAD_ARGS=(--resume "$THREAD_ID") ;;
31
+ *) echo "omnilane: invalid Grok thread mode" >&2; exit 2 ;;
32
+ esac
33
+ fi
34
+
20
35
  # Subscription OAuth path: an exhausted API key in env causes 403s.
21
36
  unset XAI_API_KEY 2>/dev/null || true
22
37
 
23
38
  truncate_payload "$PROMPT_FILE" 140000
24
39
 
25
40
  ARGS=(--cwd "$WORKDIR" --model "$MODEL"
26
- --no-memory --no-subagents --no-plan --no-alt-screen
27
- --output-format plain --verbatim --prompt-file "$PROMPT_FILE")
41
+ --no-memory --no-subagents --no-plan --no-alt-screen
42
+ --output-format plain --verbatim --prompt-file "$PROMPT_FILE")
43
+ ARGS+=("${THREAD_ARGS[@]}")
28
44
  [[ "$MODE" == "advise" ]] && ARGS+=(--permission-mode plan)
29
45
  # Web/X search stays ON by default — it is this vendor's signature lane.
30
46
  [[ "${OMNILANE_GROK_NO_WEB:-0}" == "1" ]] && ARGS+=(--disable-web-search)
@@ -31,9 +31,9 @@ trap cleanup_temp_files EXIT
31
31
  voter_spec() { # vendor -> "model<TAB>effort"
32
32
  case "$1" in
33
33
  codex) printf 'gpt-5.6-sol\thigh' ;;
34
- claude) printf 'claude-opus-5\thigh' ;;
35
- gemini) printf 'Gemini 3.1 Pro (High)\t-' ;;
36
- grok) printf 'grok-4.5\t-' ;;
34
+ claude) printf 'claude-fable-5-1\thigh' ;;
35
+ gemini) printf 'Gemini 3.7 Flash (High)\t-' ;;
36
+ grok) printf 'grok-4.6\t-' ;;
37
37
  *) return 1 ;;
38
38
  esac
39
39
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: omnilane
3
- description: 'Universal model-routing table + cross-vendor dispatch for ANY harness (Claude Code, Codex, Grok Build, Antigravity). Use when delegating subtasks, choosing a model for work, planning multi-part tasks, or when asked about model routing, delegate, dispatch, which model, tier selection, escalate, 派工, 模型路由. One routing table; implementation work dispatches by default via dispatch.sh — the main loop self-executes only reserved commander items.'
3
+ description: 'Universal model-routing table + cross-vendor dispatch for ANY harness (Claude Code, Codex, Grok Build, Antigravity). Use when delegating subtasks, choosing a model for work, planning multi-part tasks, or when asked about model routing, delegate, dispatch, which model, tier selection, escalate, 派工, 模型路由. One routing table; every task dispatches by default via dispatch.sh — the main loop self-executes only reserved commander items.'
4
4
  ---
5
5
 
6
6
  # omnilane — one routing table, every harness
@@ -9,13 +9,31 @@ You (the main loop) may be Claude, GPT, Grok, or Gemini. The procedure is identi
9
9
 
10
10
  1. **Identify your main model.** You know which model you are running as.
11
11
  2. **Split the work into subtasks and classify each into a lane** (table below).
12
- 3. **Dispatch implementation work by default — even when the lane's model is
13
- you.** Self-execute only reserved commander items: planning and
14
- decomposition, writing task briefs, reviewing reports, acceptance checks,
15
- replies to the operator, git commit/push, read-only verification, and
16
- fixes of one line or less. Dispatch:
12
+ 3. **Dispatch every task by default — even when the lane's model is you:**
13
+ implementation, search, investigation, file reads, verification, tests,
14
+ builds, deploys. The commander self-executes only: planning and
15
+ decomposition, writing task briefs, reading worker reports and job files
16
+ (`out.txt`, `events.jsonl`, inbox records), acceptance judgment, replies to
17
+ the operator, git commit/push, and edits to governance files. Read-only
18
+ work goes out in advise mode: `triage` for high-volume scans, `long-context`
19
+ for large documents, `live-search` for web or X, `hard-judgment` for second
20
+ opinions. Editing work uses `--mode work --workdir <repo> --timeout 3600`
21
+ or more. Re-verify a worker's claim by reading its attached evidence or by
22
+ dispatching a second worker (change `--vendor`); the commander runs no
23
+ commands itself. Invalid reasons to skip dispatch: "this lane is mine",
24
+ "I am not dispatching so the rule does not apply", "it is only a file
25
+ read", "dispatch is slower", "it is one line". Dispatch:
17
26
  `<repo>/scripts/dispatch.sh [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
18
27
  Add `--background` for long tasks; poll with `scripts/jobs.sh status|result <id>`.
28
+ Use `--thread NAME` when later claude, codex, grok or gemini dispatches
29
+ must retain earlier context. Threads in 0.33.0 pin vendor, model, effort and
30
+ physical workdir; inspect or remove local state with `scripts/jobs.sh threads`,
31
+ `threads show NAME`, and `threads rm NAME` (removal leaves the vendor session).
32
+ Implementation dispatches (code edits, new files, tests, builds, deploys)
33
+ must carry `--mode work --workdir <repo>` and a `--timeout` of at least
34
+ 3600 seconds. The advise default is a read-only worker under a 600 s
35
+ per-call watchdog, and on an implementation task it yields zero output.
36
+ Advise stays the default for reviews, questions, and second opinions.
19
37
  Before changing lane order from anecdotal outcomes, run
20
38
  `scripts/jobs.sh recommend [--last N] [--lane L] [--min-samples N]` and report
21
39
  its evidence threshold. The command is read-only and never changes routing.
@@ -50,28 +68,26 @@ what dispatch picks when the first-choice vendor CLI is not installed.
50
68
 
51
69
  | Lane | First choice | Backup | When |
52
70
  |---|---|---|---|
53
- | hardest-coding | GPT-5.6 Sol (xhigh) | Claude Opus 5 (xhigh) | Hardest implementation, deep root-cause debug, correctness-critical edits |
54
- | bulk-mechanical | GPT-5.6 Terra (max) | Claude Sonnet 5 (high) | Refactors, migrations, tests, review sweeps — mechanical endurance |
55
- | triage | GPT-5.6 Luna (medium) | Gemini 3.6 Flash (Low) | High-volume scans, first-pass filtering |
56
- | hard-judgment | Claude Opus 5 (xhigh) | GPT-5.6 Sol (max) | Architecture arbitration, deep reasoning, second opinions |
57
- | taste-final | Claude Opus 5 (high) | GPT-5.6 Sol (max) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
58
- | consult | Explicit named vendor/model | — (no fallback) | Direct natural-language consultation; always keep `--vendor` |
59
- | ui-draft | GPT-5.6 Sol (xhigh) | Claude Opus 5 (high) | UI drafts only WITH a design system / reference images; open-ended visual taste goes to taste-final |
60
- | long-context | Gemini 3.1 Pro (High) | GPT-5.6 Sol (high) | 1M-token synthesis; Pro is agentic-capable, while fast repeated loops prefer Flash on speed/cost |
61
- | fast-agentic | GPT-5.6 Luna (max) | Gemini 3.6 Flash (High) | Fast multi-step agentic loops, multimodal checks |
62
- | live-search | Grok 4.5 | — (off) | Realtime X/web search and social context |
63
- | coding-overflow | Grok 4.5 | Kimi K3 → Qwen3 Coder Plus → OpenCode | Codex-quota relief valve for mid-tier coding; verify factual claims |
71
+ | hardest-coding | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (xhigh) | Hardest implementation, deep root-cause debug, correctness-critical edits |
72
+ | bulk-mechanical | GPT-5.6 Sol (high) | Gemini 3.7 Flash (High) → Claude Sonnet 5 (high) | Refactors, migrations, tests, review sweeps — mechanical endurance |
73
+ | triage | GPT-5.6 Luna (high) | Gemini 3.7 Flash (Low) → Claude Haiku 4.5 | High-volume scans, first-pass filtering |
74
+ | hard-judgment | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (max) → Grok 4.6 | Architecture arbitration, deep reasoning, second opinions |
75
+ | taste-final | Claude Fable 5.1 (high) | GPT-5.6 Sol (max) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
76
+ | consult | GPT-5.6 Sol (max) | Claude Fable 5.1 (high) → Grok 4.6 → Gemini 3.7 Flash (High) | Direct named-model consultation; always keep `--vendor` |
77
+ | ui-draft | GPT-5.6 Sol (xhigh) | Claude Fable 5.1 (high) | UI drafts only WITH a design system / reference images; open-ended visual taste goes to taste-final |
78
+ | long-context | Gemini 3.7 Flash (Medium) | GPT-5.6 Terra (max) → Claude Opus 5 (medium) | Long-context synthesis ordered on AA-LCR, then cost and throughput |
79
+ | fast-agentic | Gemini 3.7 Flash (Medium) | GPT-5.6 Luna (high) | Fast multi-step agentic loops, multimodal checks |
80
+ | live-search | Grok 4.6 | — (off) | Realtime X/web search and social context |
81
+ | coding-overflow | Grok 4.6 | Gemini 3.7 Flash (High) → Kimi K3 → Qwen3 Coder Plus → OpenCode | Codex-quota relief valve for mid-tier coding; verify factual claims |
64
82
  | arbitrate | off (opt-in vote panel) | — | Disabled by default. Enable with `arbitrate: vote codex,claude,grok -` in routing.local.yaml or via the configurator (any 1-4 voters). One quota hit PER VOTER PER ROUND; you chair: read the opinions and own the decision. Effort field 2 = debate round (voters rebut each other) |
65
83
 
66
- Claude Fable 5 (`claude-fable-5`) is absent from the defaults on purpose: the
67
- top Claude tier is usually the main loop itself, not a dispatched worker, and
68
- it prices at twice Opus 5. This is a cost / guardrail / main-loop policy choice,
69
- not a capability verdict — Artificial Analysis calls Opus 5 (61) and Fable 5 (60)
70
- "effectively tied" on the Intelligence Index, but Opus 5 leads AA-Briefcase by
71
- 146 Elo at 20% lower cost per task. Fable 5 keeps the lead on factual breadth
72
- (AA-Omniscience), so name it explicitly for recall-heavy consults. To route to
73
- it anyway, select it in the configurator or override a lane in
74
- `~/.omnilane/routing.local.yaml` (e.g. `taste-final: claude claude-fable-5 high`).
84
+ Claude Fable 5.1 (`claude-fable-5-1`) is in the judgment, taste, and
85
+ hardest-coding defaults because it leads Opus 5 on every Artificial Analysis
86
+ axis at the same effort. It is not in bulk or triage because it prices at twice
87
+ Opus 5 per token and consumes the most subscription quota per turn. Opus 5
88
+ remains the lower-hallucination, lower-price Claude choice and can return to any
89
+ lane via `~/.omnilane/routing.local.yaml`, for example:
90
+ `hard-judgment: claude claude-opus-5 xhigh`.
75
91
 
76
92
  ## Natural-language consultation
77
93
 
@@ -91,15 +107,15 @@ Users may speak normally; they do not need lane names.
91
107
  | Alias | Vendor | Model | Effort |
92
108
  |---|---|---|---|
93
109
  | Opus | claude | claude-opus-5 | high |
94
- | Fable | claude | claude-fable-5 | high |
110
+ | Fable 5.1 | claude | claude-fable-5-1 | high |
95
111
  | Sonnet | claude | claude-sonnet-5 | high |
96
112
  | Haiku | claude | claude-haiku-4-5 | - |
97
113
  | Sol | codex | gpt-5.6-sol | max |
98
114
  | Terra | codex | gpt-5.6-terra | max |
99
- | Luna | codex | gpt-5.6-luna | medium |
100
- | Grok 4.5 | grok | grok-4.5 | - |
101
- | Gemini Pro | gemini | Gemini 3.1 Pro (High) | - |
102
- | Gemini Flash | gemini | Gemini 3.6 Flash (High) | - |
115
+ | Luna | codex | gpt-5.6-luna | high |
116
+ | Grok 4.6 | grok | grok-4.6 | - |
117
+ | Gemini 3.1 Pro | gemini | Gemini 3.1 Pro (High) | - |
118
+ | Gemini 3.7 Flash | gemini | Gemini 3.7 Flash (High) | - |
103
119
  | Kimi | kimi | kimi-k3 | - |
104
120
  | Qwen | qwen | qwen3-coder-plus | - |
105
121
  | OpenCode | opencode | provider/model form, or `-` for its own default | - |
@@ -134,6 +150,24 @@ delete jobs, or edit configuration. Natural-language interpretation and
134
150
  dispatch stay in this skill and the CLI. Manage the local board with
135
151
  `omnilane ui start|status|url|stop`, and stop it when monitoring is finished.
136
152
 
153
+ ## Job lifecycle defaults
154
+
155
+ - **Completion inbox**: with the Claude Code plugin's hooks installed, a
156
+ finished `--background` job is delivered into the foreman's next prompt by
157
+ the bundled `UserPromptSubmit` hook, so do not poll for it. Outside Claude
158
+ Code, block on `scripts/jobs.sh wait <id> [--timeout N]` instead.
159
+ - **Live mailbox**: a `--background` dispatch to Claude or Gemini is a
160
+ resident worker. Send follow-up instructions with `scripts/jobs.sh send <id>
161
+ "<text>"` and end it with `scripts/jobs.sh close <id>`; other vendors (and
162
+ `--single-shot`) run one-shot. Do not use a mailbox for fire-and-forget work.
163
+ - **Goal orchestration**: when the next step depends on the previous result,
164
+ wrap the dispatches in `omnilane goal open "<objective>" --workdir DIR`, then
165
+ `goal dispatch <goal-id> ...`, `goal note`, `goal status`, `goal close --summary`.
166
+ Budgets are unlimited unless `--budget-jobs` / `--budget-seconds` is passed.
167
+ A single obvious task is dispatched directly, never through a goal.
168
+ - **Job hygiene**: `scripts/jobs.sh cancel <id>` stops a runaway job.
169
+ `stats`, `recommend`, and `audit` are read-only and never change routing.
170
+
137
171
  ## Rules
138
172
 
139
173
  - **Dispatch in `advise` mode by default** (read-only worker). Use `--mode work`
@@ -159,19 +193,24 @@ dispatch stay in this skill and the CLI. Manage the local board with
159
193
 
160
194
  ## Per-model notes (apply the row matching YOUR main model)
161
195
 
162
- - **Claude (Fable/Opus main)**: top judgment and taste are yours, but
163
- implementation still dispatches by default — self-execute only reserved
164
- commander items; push all coding volume out to the lanes.
196
+ - **Claude Fable 5.1 main**: hard judgment, taste finalization, and the hardest
197
+ coding are yours. Dispatch bulk work to Sol high and long-context or fast
198
+ loops to Gemini 3.7 Flash.
199
+ - **Claude Opus 5 main**: judgment and taste remain its strongest lanes, but the commander still dispatches them;
200
+ use local overrides when its lower hallucination rate or price is preferred.
165
201
  - **Claude Sonnet main**: coordination/tools/mid-tier coding only; never
166
202
  self-assign top judgment or hardest implementation.
167
203
  - **GPT Sol main**: hardest coding + hard judgment are yours (use max for
168
204
  judgment turns, xhigh for coding); cross to taste-final for style calls.
169
- - **GPT Terra main**: bulk work is yours at max; escalate the genuinely hardest
170
- pieces to Sol instead of grinding.
171
- - **Grok 4.5 main**: mid-tier coding + live-search are yours; verify every API
172
- signature and cited fact before shipping (measured high hallucination rate).
173
- - **Gemini Flash main**: fast agentic/multimodal loops are yours; never
174
- self-assign top judgment.
175
- - **Gemini 3.1 Pro main**: 1M-context synthesis and context-heavy agentic work
176
- are yours. Prefer Gemini Flash for fast repeated tool loops on speed/cost;
177
- route hardest coding and judgment to the stronger codex lanes.
205
+ - **GPT Terra main**: long-context Codex fallback work is yours at max;
206
+ bulk-mechanical now defaults to Sol high, and genuinely hardest pieces
207
+ escalate to Sol xhigh.
208
+ - **Grok 4.6 main**: live-search and coding overflow are yours; its measured
209
+ hallucination rate is the lowest among the frontier rows, but still verify
210
+ every API signature and cited fact before shipping.
211
+ - **Gemini 3.7 Flash main**: long-context and fast agentic/multimodal loops
212
+ are yours at the lane's configured effort; bulk and overflow use the high row.
213
+ Never self-assign top judgment.
214
+ - **Gemini 3.1 Pro main**: it remains directly selectable, but the default
215
+ long-context lane now prefers Gemini 3.7 Flash on LCR, cost, and throughput;
216
+ route hardest coding and judgment to the stronger Codex and Claude lanes.