omnilane 0.14.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +27 -0
- package/.claude-plugin/plugin.json +9 -0
- package/CHANGELOG.md +71 -1
- package/README.ja.md +50 -0
- package/README.ko.md +48 -0
- package/README.md +65 -0
- package/README.zh-CN.md +45 -0
- package/README.zh-TW.md +45 -0
- package/VERSION +1 -1
- package/hooks/hooks.json +33 -0
- package/hooks/record-foreman-session.sh +57 -0
- package/hooks/report-completions.sh +134 -0
- package/hooks/routing-instruction.md +18 -0
- package/package.json +6 -2
- package/plugin.json +6 -0
- package/scripts/dispatch.sh +57 -2
- package/scripts/doctor.sh +126 -0
- package/scripts/jobs.sh +149 -8
- package/scripts/lib/common.sh +107 -0
- package/scripts/lib/i18n.sh +8 -0
- package/scripts/lib/job-worker.sh +173 -4
- package/scripts/runners/run-claude.sh +106 -0
- package/scripts/runners/run-codex.sh +22 -5
- package/skills/omnilane/SKILL.md +172 -0
package/scripts/lib/common.sh
CHANGED
|
@@ -64,6 +64,94 @@ write_current_pid_file() {
|
|
|
64
64
|
mv "$tmp" "$path"
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
process_start_time() { # pid -> stable ps(1) start timestamp
|
|
68
|
+
local pid="$1" value
|
|
69
|
+
[[ "$pid" =~ ^[1-9][0-9]{0,9}$ ]] || return 1
|
|
70
|
+
value="$(LC_ALL=C ps -o lstart= -p "$pid" 2>/dev/null)" || return 1
|
|
71
|
+
value="${value#"${value%%[![:space:]]*}"}"
|
|
72
|
+
value="${value%"${value##*[![:space:]]}"}"
|
|
73
|
+
[[ -n "$value" ]] || return 1
|
|
74
|
+
printf '%s\n' "$value"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
process_parent_pid() {
|
|
78
|
+
local pid="$1" value
|
|
79
|
+
[[ "$pid" =~ ^[1-9][0-9]{0,9}$ ]] || return 1
|
|
80
|
+
value="$(LC_ALL=C ps -o ppid= -p "$pid" 2>/dev/null)" || return 1
|
|
81
|
+
value="${value//[[:space:]]/}"
|
|
82
|
+
[[ "$value" =~ ^[1-9][0-9]{0,9}$ ]] || return 1
|
|
83
|
+
printf '%s\n' "$value"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
read_session_entry() { # path -> SESSION_ENTRY_{PID,START,ID}
|
|
87
|
+
local path="$1" fields rest
|
|
88
|
+
SESSION_ENTRY_PID=""
|
|
89
|
+
SESSION_ENTRY_START=""
|
|
90
|
+
SESSION_ENTRY_ID=""
|
|
91
|
+
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
92
|
+
fields="$(perl -MJSON::PP -e '
|
|
93
|
+
use strict;
|
|
94
|
+
use warnings;
|
|
95
|
+
my ($path) = @ARGV;
|
|
96
|
+
my $size = -s $path;
|
|
97
|
+
die "invalid size\n" unless defined($size) && $size <= 4096;
|
|
98
|
+
open my $fh, "<", $path or die $!;
|
|
99
|
+
local $/;
|
|
100
|
+
my $entry = decode_json(<$fh>);
|
|
101
|
+
close $fh or die $!;
|
|
102
|
+
die "invalid entry\n" unless ref($entry) eq "HASH";
|
|
103
|
+
my ($pid, $start, $id) = @{$entry}{qw(pid start_time session_id)};
|
|
104
|
+
die "invalid pid\n" if ref($pid) || !defined($pid) || $pid !~ /\A[1-9][0-9]{0,9}\z/;
|
|
105
|
+
die "invalid start\n" if ref($start) || !defined($start) ||
|
|
106
|
+
$start !~ /\A[ -~]{1,128}\z/;
|
|
107
|
+
die "invalid session\n" if ref($id) || !defined($id) ||
|
|
108
|
+
$id !~ /\A[A-Za-z0-9._:-]{1,256}\z/;
|
|
109
|
+
print "$pid\t$start\t$id";
|
|
110
|
+
' "$path" 2>/dev/null)" || return 1
|
|
111
|
+
SESSION_ENTRY_PID="${fields%%$'\t'*}"
|
|
112
|
+
rest="${fields#*$'\t'}"
|
|
113
|
+
[[ "$rest" != "$fields" ]] || return 1
|
|
114
|
+
SESSION_ENTRY_START="${rest%%$'\t'*}"
|
|
115
|
+
SESSION_ENTRY_ID="${rest#*$'\t'}"
|
|
116
|
+
[[ "$SESSION_ENTRY_ID" != "$rest" ]] || return 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
prune_stale_session_entries() {
|
|
120
|
+
local sessions="$OMNILANE_HOME/sessions" entry live_start
|
|
121
|
+
[[ -d "$sessions" && ! -L "$sessions" ]] || return 0
|
|
122
|
+
for entry in "$sessions"/*.json; do
|
|
123
|
+
[[ -f "$entry" && ! -L "$entry" ]] || continue
|
|
124
|
+
read_session_entry "$entry" || continue
|
|
125
|
+
if ! live_start="$(process_start_time "$SESSION_ENTRY_PID")" ||
|
|
126
|
+
[[ "$live_start" != "$SESSION_ENTRY_START" ]]; then
|
|
127
|
+
rm "$entry" 2>/dev/null || true
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
find_foreman_session() { # dispatch pid -> nearest live Claude foreman session
|
|
133
|
+
local pid="$1" sessions="$OMNILANE_HOME/sessions"
|
|
134
|
+
local parent start entry hops=0
|
|
135
|
+
[[ "$pid" =~ ^[1-9][0-9]{0,9}$ ]] || return 1
|
|
136
|
+
[[ -d "$sessions" && ! -L "$sessions" ]] || return 1
|
|
137
|
+
prune_stale_session_entries
|
|
138
|
+
|
|
139
|
+
while [[ "$hops" -lt 128 ]]; do
|
|
140
|
+
parent="$(process_parent_pid "$pid")" || return 1
|
|
141
|
+
[[ "$parent" =~ ^[1-9][0-9]{0,9}$ && "$parent" != "$pid" ]] || return 1
|
|
142
|
+
start="$(process_start_time "$parent")" || return 1
|
|
143
|
+
entry="$sessions/$parent.json"
|
|
144
|
+
if read_session_entry "$entry" &&
|
|
145
|
+
[[ "$SESSION_ENTRY_PID" == "$parent" && "$SESSION_ENTRY_START" == "$start" ]]; then
|
|
146
|
+
printf '%s\n' "$SESSION_ENTRY_ID"
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
pid="$parent"
|
|
150
|
+
hops=$((hops + 1))
|
|
151
|
+
done
|
|
152
|
+
return 1
|
|
153
|
+
}
|
|
154
|
+
|
|
67
155
|
# ---- Vendor registry (single source of truth) ---------------------------
|
|
68
156
|
# Two vendor kinds. CLI-agent vendors run a local binary (see vendor_bin).
|
|
69
157
|
# Direct-API vendors are OpenAI-compatible /chat/completions endpoints driven
|
|
@@ -181,6 +269,25 @@ read_lock_owner() {
|
|
|
181
269
|
LOCK_OWNER_VALUE="$value"
|
|
182
270
|
}
|
|
183
271
|
|
|
272
|
+
prepare_private_store() { # path, diagnostic label
|
|
273
|
+
local store_root="$1" store_label="$2"
|
|
274
|
+
mkdir -p "$OMNILANE_HOME" || return 1
|
|
275
|
+
if [[ -L "$store_root" || ( -e "$store_root" && ! -d "$store_root" ) ]]; then
|
|
276
|
+
echo "omnilane: unsafe $store_label path (want real directory): $store_root" >&2
|
|
277
|
+
return 1
|
|
278
|
+
fi
|
|
279
|
+
[[ -d "$store_root" ]] || mkdir -m 700 "$store_root" || return 1
|
|
280
|
+
[[ -d "$store_root" && ! -L "$store_root" ]] || {
|
|
281
|
+
echo "omnilane: $store_label changed while preparing it" >&2
|
|
282
|
+
return 1
|
|
283
|
+
}
|
|
284
|
+
chmod 700 "$store_root" || return 1
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
prepare_inbox_store() {
|
|
288
|
+
prepare_private_store "$OMNILANE_HOME/inbox" "inbox store"
|
|
289
|
+
}
|
|
290
|
+
|
|
184
291
|
prepare_lock_store() {
|
|
185
292
|
local lock_root="$OMNILANE_HOME/locks"
|
|
186
293
|
mkdir -p "$OMNILANE_HOME"
|
package/scripts/lib/i18n.sh
CHANGED
|
@@ -28,6 +28,14 @@ msg() { # key -> localized string ({} = placeholder, see msgf)
|
|
|
28
28
|
case "$L" in zh-TW) echo "已偵測到——請自行執行:";; zh-CN) echo "已检测到——请自行执行:";; ja) echo "検出——次を手動で実行:";; ko) echo "감지됨——직접 실행하세요:";; *) echo "found — run this yourself:";; esac ;;
|
|
29
29
|
plugin_hint)
|
|
30
30
|
case "$L" in zh-TW) echo "(選配:把本 repo 裝成 Claude Code 外掛,可用 /route 指令)";; zh-CN) echo "(可选:把本仓库装成 Claude Code 插件,获得 /route 命令)";; ja) echo "(任意:このリポジトリを Claude Code プラグインとして追加すると /route コマンドが使えます)";; ko) echo "(선택: 이 저장소를 Claude Code 플러그인으로 추가하면 /route 명령 사용 가능)";; *) echo "(optional: add this repo as a Claude Code plugin for /route commands)";; esac ;;
|
|
31
|
+
completion_notice_active)
|
|
32
|
+
case "$L" in zh-TW) echo "Claude Code 完成通知:已啟用(omnilane@omnilane 外掛已啟用)。";; zh-CN) echo "Claude Code 完成通知:已启用(omnilane@omnilane 插件已启用)。";; ja) echo "Claude Code 完了通知: 有効です(omnilane@omnilane プラグインが有効)。";; ko) echo "Claude Code 완료 알림: 활성 상태입니다(omnilane@omnilane 플러그인 활성화됨).";; *) echo "Claude Code completion notice: active (omnilane@omnilane is enabled).";; esac ;;
|
|
33
|
+
completion_notice_unknown)
|
|
34
|
+
case "$L" in zh-TW) echo "Claude Code 完成通知:狀態未知(查不到外掛狀態)。";; zh-CN) echo "Claude Code 完成通知:状态未知(无法查询插件状态)。";; ja) echo "Claude Code 完了通知: 状態不明(プラグイン状態を確認できません)。";; ko) echo "Claude Code 완료 알림: 상태를 알 수 없습니다(플러그인 상태 조회 실패).";; *) echo "Claude Code completion notice: unknown (plugin state could not be queried).";; esac ;;
|
|
35
|
+
completion_notice_inactive)
|
|
36
|
+
case "$L" in zh-TW) echo "Claude Code 完成通知:未啟用。這項功能需要安裝外掛;只有 skill symlink 不會送達通知。";; zh-CN) echo "Claude Code 完成通知:未启用。此功能需要安装插件;只有 skill symlink 不会送达通知。";; ja) echo "Claude Code 完了通知: 無効です。この機能にはプラグインのインストールが必要で、skill symlink だけでは通知されません。";; ko) echo "Claude Code 완료 알림: 비활성 상태입니다. 이 기능은 플러그인 설치가 필요하며 skill symlink만으로는 알림이 전달되지 않습니다.";; *) echo "Claude Code completion notice: inactive. This feature requires the plugin install; the skill symlink alone does not deliver notices.";; esac ;;
|
|
37
|
+
completion_notice_install)
|
|
38
|
+
case "$L" in zh-TW) echo "請執行:";; zh-CN) echo "请执行:";; ja) echo "次を実行してください:";; ko) echo "다음을 실행하세요:";; *) echo "Run:";; esac ;;
|
|
31
39
|
linked)
|
|
32
40
|
case "$L" in zh-TW) echo "已連結";; zh-CN) echo "已链接";; ja) echo "リンクしました";; ko) echo "링크됨";; *) echo "linked";; esac ;;
|
|
33
41
|
removed)
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
# Internal worker boundary for one dispatch. An optional whole-job supervisor
|
|
4
5
|
# wraps this process so lock wait, retries, and vote rounds share one budget.
|
|
5
6
|
|
|
7
|
+
# Runtime-relative shared library.
|
|
8
|
+
# shellcheck disable=SC1091
|
|
6
9
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
|
7
10
|
|
|
8
11
|
[[ $# -eq 7 ]] || { echo "omnilane: internal job worker received invalid arguments" >&2; exit 2; }
|
|
@@ -13,11 +16,177 @@ PROMPT_FILE="$6"; OUTPUT_FILE="$7"
|
|
|
13
16
|
RUNNER="$OMNILANE_REPO/scripts/runners/run-$VENDOR.sh"
|
|
14
17
|
[[ -x "$RUNNER" ]] || { echo "omnilane: no runner for vendor '$VENDOR'" >&2; exit 2; }
|
|
15
18
|
|
|
16
|
-
# Two concurrent codex
|
|
19
|
+
# Two concurrent codex execs in one target dir corrupt its job index — serialize.
|
|
17
20
|
[[ "$VENDOR" == "codex" ]] && acquire_cwd_lock codex "$WORKDIR"
|
|
18
21
|
|
|
22
|
+
# The backslash case pattern is intentional.
|
|
23
|
+
# shellcheck disable=SC1003
|
|
24
|
+
json_escape() {
|
|
25
|
+
local s="$1" out="" ch escaped code i
|
|
26
|
+
for ((i = 0; i < ${#s}; i++)); do
|
|
27
|
+
ch="${s:i:1}"
|
|
28
|
+
case "$ch" in
|
|
29
|
+
'"') out="$out\\\"" ;;
|
|
30
|
+
'\\') out="$out\\\\" ;;
|
|
31
|
+
$'\b') out="$out\\b" ;;
|
|
32
|
+
$'\f') out="$out\\f" ;;
|
|
33
|
+
$'\n') out="$out\\n" ;;
|
|
34
|
+
$'\r') out="$out\\r" ;;
|
|
35
|
+
$'\t') out="$out\\t" ;;
|
|
36
|
+
*)
|
|
37
|
+
LC_CTYPE=C printf -v code '%d' "'$ch"
|
|
38
|
+
if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
|
|
39
|
+
printf -v escaped '\\u%04x' "$code"
|
|
40
|
+
out="$out$escaped"
|
|
41
|
+
else
|
|
42
|
+
out="$out$ch"
|
|
43
|
+
fi
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
printf '%s' "$out"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
emit_mode_notice() {
|
|
51
|
+
local notice="$1" notice_file="${OUTPUT_FILE%/*}/mode-notice.txt"
|
|
52
|
+
printf '%s\n' "$notice" >&2
|
|
53
|
+
if [[ -L "$notice_file" || ( -e "$notice_file" && ! -f "$notice_file" ) ]]; then
|
|
54
|
+
echo "omnilane: unsafe mode notice path" >&2
|
|
55
|
+
return 1
|
|
56
|
+
fi
|
|
57
|
+
(umask 077; printf '%s\n' "$notice" > "$notice_file")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run_single_shot() {
|
|
61
|
+
local notice="$1" rc
|
|
62
|
+
set +e
|
|
63
|
+
(
|
|
64
|
+
unset OMNILANE_INBOX
|
|
65
|
+
"$RUNNER" "$MODE" "$WORKDIR" "$MODEL" "$EFFORT" "$PROMPT_FILE" "$OUTPUT_FILE"
|
|
66
|
+
)
|
|
67
|
+
rc=$?
|
|
68
|
+
set -e
|
|
69
|
+
emit_mode_notice "$notice"
|
|
70
|
+
return "$rc"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if [[ "$VENDOR" != "claude" ]]; then
|
|
74
|
+
set +e
|
|
75
|
+
run_single_shot "omnilane: vendor '$VENDOR' is not live-capable; ran in single-shot mode"
|
|
76
|
+
rc=$?
|
|
77
|
+
set -e
|
|
78
|
+
exit "$rc"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
JOB_DIR="${OUTPUT_FILE%/*}"
|
|
82
|
+
JOB_ID="${JOB_DIR##*/}"
|
|
83
|
+
INBOX_FIFO="$JOB_DIR/inbox.fifo"
|
|
84
|
+
HOLDER_PID_FILE="$JOB_DIR/inbox.holder.pid"
|
|
85
|
+
READY_FILE="$JOB_DIR/inbox.ready"
|
|
86
|
+
EVENTS_FILE="${OUTPUT_FILE}.events.jsonl"
|
|
87
|
+
EVENTS_ALIAS="$JOB_DIR/events.jsonl"
|
|
88
|
+
close_requested=0
|
|
89
|
+
inbox_holder_open=0
|
|
90
|
+
runner_pid=""
|
|
91
|
+
|
|
92
|
+
# Invoked by the EXIT trap below.
|
|
93
|
+
# shellcheck disable=SC2329
|
|
94
|
+
cleanup_live_mailbox() {
|
|
95
|
+
if [[ "$inbox_holder_open" -eq 1 ]]; then
|
|
96
|
+
exec 3>&-
|
|
97
|
+
inbox_holder_open=0
|
|
98
|
+
fi
|
|
99
|
+
rm "$READY_FILE" "$HOLDER_PID_FILE" "$INBOX_FIFO" 2>/dev/null || true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
prepare_live_mailbox() {
|
|
103
|
+
local path old_umask rc=0
|
|
104
|
+
for path in "$INBOX_FIFO" "$HOLDER_PID_FILE" "$READY_FILE" "$EVENTS_FILE" "$EVENTS_ALIAS"; do
|
|
105
|
+
[[ ! -e "$path" && ! -L "$path" ]] || return 1
|
|
106
|
+
done
|
|
107
|
+
old_umask="$(umask)"
|
|
108
|
+
umask 077
|
|
109
|
+
mkfifo "$INBOX_FIFO" || rc=$?
|
|
110
|
+
if [[ "$rc" -eq 0 ]]; then
|
|
111
|
+
: > "$EVENTS_FILE" || rc=$?
|
|
112
|
+
fi
|
|
113
|
+
if [[ "$rc" -eq 0 ]]; then
|
|
114
|
+
ln "$EVENTS_FILE" "$EVENTS_ALIAS" || rc=$?
|
|
115
|
+
fi
|
|
116
|
+
if [[ "$rc" -eq 0 ]]; then
|
|
117
|
+
chmod 600 "$INBOX_FIFO" "$EVENTS_FILE" "$EVENTS_ALIAS" || rc=$?
|
|
118
|
+
fi
|
|
119
|
+
umask "$old_umask"
|
|
120
|
+
if [[ "$rc" -ne 0 ]]; then
|
|
121
|
+
rm "$EVENTS_ALIAS" "$EVENTS_FILE" "$INBOX_FIFO" 2>/dev/null || true
|
|
122
|
+
fi
|
|
123
|
+
return "$rc"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
last_result_status() {
|
|
127
|
+
local last_result
|
|
128
|
+
last_result="$(grep -E '"type"[[:space:]]*:[[:space:]]*"result"' "$EVENTS_FILE" 2>/dev/null | tail -n 1 || true)"
|
|
129
|
+
[[ -n "$last_result" ]] || return 1
|
|
130
|
+
[[ ! "$last_result" =~ "is_error"[[:space:]]*:[[:space:]]*true ]]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if ! prepare_live_mailbox; then
|
|
134
|
+
set +e
|
|
135
|
+
run_single_shot "omnilane: Claude live mailbox unavailable because FIFO setup failed; ran in single-shot mode"
|
|
136
|
+
rc=$?
|
|
137
|
+
set -e
|
|
138
|
+
exit "$rc"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
trap 'close_requested=1' USR1
|
|
142
|
+
trap cleanup_live_mailbox EXIT
|
|
143
|
+
truncate_payload "$PROMPT_FILE" 102400
|
|
144
|
+
INITIAL_TEXT="$(cat "$PROMPT_FILE")"
|
|
145
|
+
if [[ "${FOREMAN_SESSION+x}" == "x" ]]; then
|
|
146
|
+
FOREMAN_SESSION_VALUE="$FOREMAN_SESSION"
|
|
147
|
+
else
|
|
148
|
+
FOREMAN_SESSION_VALUE="${foreman_session-}"
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
write_current_pid_file "$HOLDER_PID_FILE"
|
|
152
|
+
export OMNILANE_INBOX="$INBOX_FIFO"
|
|
153
|
+
"$RUNNER" "$MODE" "$WORKDIR" "$MODEL" "$EFFORT" "$PROMPT_FILE" "$OUTPUT_FILE" &
|
|
154
|
+
runner_pid=$!
|
|
155
|
+
|
|
156
|
+
# Open only after the reader starts. The runner was launched first, so it cannot
|
|
157
|
+
# inherit this write descriptor. FD 3 is the named holder for the conversation.
|
|
158
|
+
exec 3> "$INBOX_FIFO"
|
|
159
|
+
inbox_holder_open=1
|
|
160
|
+
if [[ "$close_requested" -eq 0 ]]; then
|
|
161
|
+
printf '{"type":"user","omnilane_job_id":"%s","foreman_session":"%s","message":{"role":"user","content":[{"type":"text","text":"%s"}]}}\n' \
|
|
162
|
+
"$(json_escape "$JOB_ID")" "$(json_escape "$FOREMAN_SESSION_VALUE")" \
|
|
163
|
+
"$(json_escape "$INITIAL_TEXT")" >&3
|
|
164
|
+
fi
|
|
165
|
+
(umask 077; : > "$READY_FILE")
|
|
166
|
+
|
|
19
167
|
set +e
|
|
20
|
-
|
|
21
|
-
|
|
168
|
+
wait "$runner_pid"
|
|
169
|
+
runner_rc=$?
|
|
22
170
|
set -e
|
|
171
|
+
|
|
172
|
+
if [[ "$close_requested" -eq 1 ]]; then
|
|
173
|
+
exec 3>&-
|
|
174
|
+
inbox_holder_open=0
|
|
175
|
+
set +e
|
|
176
|
+
kill -TERM "$runner_pid" 2>/dev/null || true
|
|
177
|
+
wait "$runner_pid" 2>/dev/null
|
|
178
|
+
set -e
|
|
179
|
+
if last_result_status; then
|
|
180
|
+
rc=0
|
|
181
|
+
else
|
|
182
|
+
rc=1
|
|
183
|
+
emit_mode_notice "omnilane: Claude live mailbox closed without a successful result event"
|
|
184
|
+
fi
|
|
185
|
+
else
|
|
186
|
+
exec 3>&-
|
|
187
|
+
inbox_holder_open=0
|
|
188
|
+
rc="$runner_rc"
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
trap - USR1
|
|
23
192
|
exit "$rc"
|
|
@@ -3,6 +3,8 @@ set -euo pipefail
|
|
|
3
3
|
# omnilane runner: Claude Code CLI
|
|
4
4
|
# Usage: run-claude.sh MODE WORKDIR MODEL EFFORT PROMPT_FILE OUTPUT_FILE
|
|
5
5
|
|
|
6
|
+
# Runtime-relative shared library.
|
|
7
|
+
# shellcheck disable=SC1091
|
|
6
8
|
source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh"
|
|
7
9
|
|
|
8
10
|
MODE="$1"; WORKDIR="$2"; MODEL="$3"; EFFORT="$4"; PROMPT_FILE="$5"; OUTPUT_FILE="$6"
|
|
@@ -12,6 +14,110 @@ RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
|
12
14
|
|
|
13
15
|
truncate_payload "$PROMPT_FILE" 102400
|
|
14
16
|
|
|
17
|
+
LIVE_INBOX="${OMNILANE_INBOX:-}"
|
|
18
|
+
if [[ -n "$LIVE_INBOX" && -p "$LIVE_INBOX" ]]; then
|
|
19
|
+
EVENTS_FILE="${OUTPUT_FILE}.events.jsonl"
|
|
20
|
+
STDERR_FILE="${OUTPUT_FILE}.stderr.log"
|
|
21
|
+
LIVE_CHILD_PID=""
|
|
22
|
+
|
|
23
|
+
if [[ -L "$EVENTS_FILE" || ( -e "$EVENTS_FILE" && ! -f "$EVENTS_FILE" ) ]]; then
|
|
24
|
+
echo "omnilane: unsafe Claude live event path" >&2
|
|
25
|
+
exit 125
|
|
26
|
+
fi
|
|
27
|
+
(umask 077; : > "$EVENTS_FILE"; : > "$STDERR_FILE")
|
|
28
|
+
|
|
29
|
+
LIVE_ARGS=(--disable-slash-commands --model "$MODEL")
|
|
30
|
+
[[ -n "$EFFORT" && "$EFFORT" != "-" ]] && LIVE_ARGS+=(--effort "$EFFORT")
|
|
31
|
+
if [[ "$MODE" == "advise" ]]; then
|
|
32
|
+
LIVE_ARGS+=(--tools Read Glob Grep)
|
|
33
|
+
else
|
|
34
|
+
LIVE_ARGS+=(--permission-mode acceptEdits)
|
|
35
|
+
fi
|
|
36
|
+
LIVE_ARGS+=(-p --verbose --input-format stream-json --output-format stream-json)
|
|
37
|
+
|
|
38
|
+
finalize_live_output() {
|
|
39
|
+
local tmp="${OUTPUT_FILE}.tmp"
|
|
40
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
41
|
+
echo "omnilane: cannot extract Claude live result: python3 not found" >> "$STDERR_FILE"
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
if ! python3 - "$EVENTS_FILE" "$tmp" <<'PY'
|
|
45
|
+
import json
|
|
46
|
+
import pathlib
|
|
47
|
+
import sys
|
|
48
|
+
|
|
49
|
+
events_path = pathlib.Path(sys.argv[1])
|
|
50
|
+
output_path = pathlib.Path(sys.argv[2])
|
|
51
|
+
last_result = None
|
|
52
|
+
|
|
53
|
+
with events_path.open(encoding="utf-8") as events:
|
|
54
|
+
for raw_line in events:
|
|
55
|
+
try:
|
|
56
|
+
event = json.loads(raw_line)
|
|
57
|
+
except json.JSONDecodeError:
|
|
58
|
+
continue
|
|
59
|
+
if event.get("type") == "result" and isinstance(event.get("result"), str):
|
|
60
|
+
last_result = event["result"]
|
|
61
|
+
|
|
62
|
+
if last_result is None:
|
|
63
|
+
raise SystemExit(1)
|
|
64
|
+
|
|
65
|
+
output_path.write_text(last_result.rstrip("\n") + "\n", encoding="utf-8")
|
|
66
|
+
PY
|
|
67
|
+
then
|
|
68
|
+
echo "omnilane: Claude live stream ended without a readable result event" >> "$STDERR_FILE"
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
mv "$tmp" "$OUTPUT_FILE"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Invoked by signal traps below.
|
|
75
|
+
# shellcheck disable=SC2329
|
|
76
|
+
stop_live_child() {
|
|
77
|
+
local signal_rc="$1" waited=0
|
|
78
|
+
trap - TERM HUP INT
|
|
79
|
+
set +e
|
|
80
|
+
if [[ -n "$LIVE_CHILD_PID" ]] && kill -0 "$LIVE_CHILD_PID" 2>/dev/null; then
|
|
81
|
+
kill -TERM "-$LIVE_CHILD_PID" 2>/dev/null || kill -TERM "$LIVE_CHILD_PID" 2>/dev/null || true
|
|
82
|
+
while kill -0 "$LIVE_CHILD_PID" 2>/dev/null && [[ "$waited" -lt 50 ]]; do
|
|
83
|
+
sleep 0.1
|
|
84
|
+
waited=$((waited + 1))
|
|
85
|
+
done
|
|
86
|
+
if kill -0 "$LIVE_CHILD_PID" 2>/dev/null; then
|
|
87
|
+
kill -KILL "-$LIVE_CHILD_PID" 2>/dev/null || kill -KILL "$LIVE_CHILD_PID" 2>/dev/null || true
|
|
88
|
+
fi
|
|
89
|
+
wait "$LIVE_CHILD_PID" 2>/dev/null
|
|
90
|
+
fi
|
|
91
|
+
finalize_live_output || true
|
|
92
|
+
[[ -s "$STDERR_FILE" ]] || rm "$STDERR_FILE" 2>/dev/null || true
|
|
93
|
+
exit "$signal_rc"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
set -m
|
|
97
|
+
(
|
|
98
|
+
cd "$WORKDIR" || exit 127
|
|
99
|
+
run_with_timeout "$RUN_TIMEOUT" env \
|
|
100
|
+
OMNILANE_DEPTH=1 \
|
|
101
|
+
"$CLAUDE_BIN" "${LIVE_ARGS[@]}" < "$LIVE_INBOX" > "$EVENTS_FILE" 2> "$STDERR_FILE"
|
|
102
|
+
) &
|
|
103
|
+
LIVE_CHILD_PID=$!
|
|
104
|
+
set +m
|
|
105
|
+
trap 'stop_live_child 143' TERM
|
|
106
|
+
trap 'stop_live_child 129' HUP
|
|
107
|
+
trap 'stop_live_child 130' INT
|
|
108
|
+
|
|
109
|
+
set +e
|
|
110
|
+
wait "$LIVE_CHILD_PID"
|
|
111
|
+
RC=$?
|
|
112
|
+
set -e
|
|
113
|
+
trap - TERM HUP INT
|
|
114
|
+
if ! finalize_live_output && [[ "$RC" -eq 0 ]]; then
|
|
115
|
+
RC=1
|
|
116
|
+
fi
|
|
117
|
+
[[ -s "$STDERR_FILE" ]] || rm "$STDERR_FILE" 2>/dev/null || true
|
|
118
|
+
exit "$RC"
|
|
119
|
+
fi
|
|
120
|
+
|
|
15
121
|
ARGS=(--disable-slash-commands --model "$MODEL" --output-format text)
|
|
16
122
|
[[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(--effort "$EFFORT")
|
|
17
123
|
if [[ "$MODE" == "advise" ]]; then
|
|
@@ -16,7 +16,9 @@ RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
|
16
16
|
|
|
17
17
|
# --skip-git-repo-check: the operator chose WORKDIR explicitly; codex would
|
|
18
18
|
# otherwise refuse any directory that is not a trusted git repo.
|
|
19
|
-
|
|
19
|
+
# --json: without it codex writes nothing until it exits, so a watchdog kill
|
|
20
|
+
# 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)
|
|
20
22
|
[[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(-c "model_reasoning_effort=\"$EFFORT\"")
|
|
21
23
|
if [[ "$MODE" == "advise" ]]; then
|
|
22
24
|
ARGS+=(--ephemeral -s read-only)
|
|
@@ -41,10 +43,25 @@ set +e
|
|
|
41
43
|
RC=$?
|
|
42
44
|
set -e
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if [[ "$RC" -eq 142 || "$RC" -eq 124 ]]; then
|
|
47
|
+
{
|
|
48
|
+
echo "omnilane: codex timed out after ${RUN_TIMEOUT}s. This does NOT identify the cause."
|
|
49
|
+
echo "omnilane: an empty progress log is NOT evidence of a stall — check, in order:"
|
|
50
|
+
echo " 1. the streamed events in ${OUTPUT_FILE}.progress.log — the last event says how far the run got"
|
|
51
|
+
echo " 2. the rollout file below — if it stops at task_started, the request never left the CLI"
|
|
52
|
+
echo " 3. your provider proxy's log for the run window: rate-limit responses, and whether a"
|
|
53
|
+
echo " request was ever sent upstream at all"
|
|
54
|
+
# The rollout holds the full turn history a killed run never got to report;
|
|
55
|
+
# thread_id is the only handle onto it once the process is gone.
|
|
56
|
+
thread_id="$(head -1 "${OUTPUT_FILE}.progress.log" 2>/dev/null |
|
|
57
|
+
sed -n 's/.*"thread_id":"\([^"]*\)".*/\1/p')"
|
|
58
|
+
if [[ -n "$thread_id" ]]; then
|
|
59
|
+
rollout="$(find "${CODEX_HOME:-$HOME/.codex}/sessions" -name "rollout-*-${thread_id}.jsonl" 2>/dev/null | head -1)"
|
|
60
|
+
echo "omnilane: rollout: ${rollout:-not found (thread ${thread_id})}"
|
|
61
|
+
else
|
|
62
|
+
echo "omnilane: rollout: unknown — no thread_id in the progress log, so codex died before its first event"
|
|
63
|
+
fi
|
|
64
|
+
} >> "${OUTPUT_FILE}.stderr.log"
|
|
48
65
|
fi
|
|
49
66
|
|
|
50
67
|
if [[ -f "${OUTPUT_FILE}.tmp" ]]; then
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
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; the main loop self-executes its own lane and shells out to every other vendor via dispatch.sh.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# omnilane — one routing table, every harness
|
|
7
|
+
|
|
8
|
+
You (the main loop) may be Claude, GPT, Grok, or Gemini. The procedure is identical:
|
|
9
|
+
|
|
10
|
+
1. **Identify your main model.** You know which model you are running as.
|
|
11
|
+
2. **Split the work into subtasks and classify each into a lane** (table below).
|
|
12
|
+
3. **If the lane's model is you, self-execute.** Otherwise dispatch:
|
|
13
|
+
`<repo>/scripts/dispatch.sh [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
|
|
14
|
+
Add `--background` for long tasks; poll with `scripts/jobs.sh status|result <id>`.
|
|
15
|
+
Before changing lane order from anecdotal outcomes, run
|
|
16
|
+
`scripts/jobs.sh recommend [--last N] [--lane L] [--min-samples N]` and report
|
|
17
|
+
its evidence threshold. The command is read-only and never changes routing.
|
|
18
|
+
Preview old completed-job cleanup with `scripts/jobs.sh prune --keep <N>`;
|
|
19
|
+
deletion requires the explicit `--apply` flag and never targets running jobs.
|
|
20
|
+
A deep task whose CLI call may outrun the 600s per-call watchdog can raise its
|
|
21
|
+
cap with `--timeout <seconds>` (e.g. `--timeout 1200` for hard-judgment /
|
|
22
|
+
long-context). It bounds each CLI call, not the whole dispatch.
|
|
23
|
+
For one aggregate fuse across lock wait, retries, voters, and rounds, add
|
|
24
|
+
`--job-timeout <seconds>`. It is disabled by default; deep full-repository
|
|
25
|
+
audits typically need 7200–14400 seconds, and expiry returns 124. The one
|
|
26
|
+
automatic exception is non-Git Codex `work`: without an explicit, lane, or
|
|
27
|
+
global job timeout, its resolved per-call timeout becomes the whole-job fuse,
|
|
28
|
+
capped at the supervisor's 999999999-second maximum. If the bundled Perl
|
|
29
|
+
supervisor is unavailable, it warns and continues through the existing
|
|
30
|
+
per-call watchdog path.
|
|
31
|
+
|
|
32
|
+
Run `scripts/dispatch.sh --list` to see the effective table (local overrides win).
|
|
33
|
+
When routing is unexpectedly unavailable, run `bin/omnilane doctor` before
|
|
34
|
+
changing configuration; it reports state and dependencies without repairing them.
|
|
35
|
+
Doctor remains offline unless the operator explicitly adds `--probe V`; that
|
|
36
|
+
bounded probe returns metadata only. Use `bin/omnilane benchmark` for a fixed
|
|
37
|
+
no-call route plan, and add `--run` only when actual advise-mode comparison calls
|
|
38
|
+
were explicitly requested. Neither command changes routing.
|
|
39
|
+
Lanes are fallback chains — dispatch uses the first vendor CLI actually installed,
|
|
40
|
+
so the same table works with any subset of subscriptions.
|
|
41
|
+
|
|
42
|
+
## Lanes (defaults; see routing.yaml for the live values)
|
|
43
|
+
|
|
44
|
+
Each lane's **backup** is the next candidate in its `routing.yaml` chain —
|
|
45
|
+
what dispatch picks when the first-choice vendor CLI is not installed.
|
|
46
|
+
|
|
47
|
+
| Lane | First choice | Backup | When |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| hardest-coding | GPT-5.6 Sol (xhigh) | Claude Opus 5 (xhigh) | Hardest implementation, deep root-cause debug, correctness-critical edits |
|
|
50
|
+
| bulk-mechanical | GPT-5.6 Terra (max) | Claude Sonnet 5 (high) | Refactors, migrations, tests, review sweeps — mechanical endurance |
|
|
51
|
+
| triage | GPT-5.6 Luna (medium) | Gemini 3.6 Flash (Low) | High-volume scans, first-pass filtering |
|
|
52
|
+
| hard-judgment | Claude Opus 5 (xhigh) | GPT-5.6 Sol (max) | Architecture arbitration, deep reasoning, second opinions |
|
|
53
|
+
| taste-final | Claude Opus 5 (high) | GPT-5.6 Sol (max) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
|
|
54
|
+
| consult | Explicit named vendor/model | — (no fallback) | Direct natural-language consultation; always keep `--vendor` |
|
|
55
|
+
| 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 |
|
|
56
|
+
| 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 |
|
|
57
|
+
| fast-agentic | GPT-5.6 Luna (max) | Gemini 3.6 Flash (High) | Fast multi-step agentic loops, multimodal checks |
|
|
58
|
+
| live-search | Grok 4.5 | — (off) | Realtime X/web search and social context |
|
|
59
|
+
| coding-overflow | Grok 4.5 | Kimi K3 → Qwen3 Coder Plus → OpenCode | Codex-quota relief valve for mid-tier coding; verify factual claims |
|
|
60
|
+
| 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) |
|
|
61
|
+
|
|
62
|
+
Claude Fable 5 (`claude-fable-5`) is absent from the defaults on purpose: the
|
|
63
|
+
top Claude tier is usually the main loop itself, not a dispatched worker, and
|
|
64
|
+
it prices at twice Opus 5. This is a cost / guardrail / main-loop policy choice,
|
|
65
|
+
not a capability verdict — Artificial Analysis calls Opus 5 (61) and Fable 5 (60)
|
|
66
|
+
"effectively tied" on the Intelligence Index, but Opus 5 leads AA-Briefcase by
|
|
67
|
+
146 Elo at 20% lower cost per task. Fable 5 keeps the lead on factual breadth
|
|
68
|
+
(AA-Omniscience), so name it explicitly for recall-heavy consults. To route to
|
|
69
|
+
it anyway, select it in the configurator or override a lane in
|
|
70
|
+
`~/.omnilane/routing.local.yaml` (e.g. `taste-final: claude claude-fable-5 high`).
|
|
71
|
+
|
|
72
|
+
## Natural-language consultation
|
|
73
|
+
|
|
74
|
+
Users may speak normally; they do not need lane names.
|
|
75
|
+
|
|
76
|
+
1. Capability-only question (`which model`, `what can Claude do`, `哪個模型`,
|
|
77
|
+
`誰適合`) → classify the need, then answer with the first available model
|
|
78
|
+
shown for that lane by `dispatch.sh --list`; do not dispatch unless execution
|
|
79
|
+
is also requested.
|
|
80
|
+
2. Generic vendor name (`Claude`, `Codex`, `Grok`, `Gemini`, `OpenCode`) → run
|
|
81
|
+
`dispatch.sh --vendor <vendor> consult "<task>"`.
|
|
82
|
+
3. Canonical model alias → pass its vendor, model, and effort from the table
|
|
83
|
+
below. Never silently substitute another model family.
|
|
84
|
+
4. No named target → classify into an existing lane and dispatch normally.
|
|
85
|
+
5. Unknown or ambiguous nickname → ask for clarification; do not guess or run.
|
|
86
|
+
|
|
87
|
+
| Alias | Vendor | Model | Effort |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| Opus | claude | claude-opus-5 | high |
|
|
90
|
+
| Fable | claude | claude-fable-5 | high |
|
|
91
|
+
| Sonnet | claude | claude-sonnet-5 | high |
|
|
92
|
+
| Haiku | claude | claude-haiku-4-5 | - |
|
|
93
|
+
| Sol | codex | gpt-5.6-sol | max |
|
|
94
|
+
| Terra | codex | gpt-5.6-terra | max |
|
|
95
|
+
| Luna | codex | gpt-5.6-luna | medium |
|
|
96
|
+
| Grok 4.5 | grok | grok-4.5 | - |
|
|
97
|
+
| Gemini Pro | gemini | Gemini 3.1 Pro (High) | - |
|
|
98
|
+
| Gemini Flash | gemini | Gemini 3.6 Flash (High) | - |
|
|
99
|
+
| Kimi | kimi | kimi-k3 | - |
|
|
100
|
+
| Qwen | qwen | qwen3-coder-plus | - |
|
|
101
|
+
| OpenCode | opencode | provider/model form, or `-` for its own default | - |
|
|
102
|
+
| OpenRouter | openrouter | explicit OpenRouter slug (e.g. anthropic/claude-sonnet-5) | - |
|
|
103
|
+
|
|
104
|
+
OpenCode is the multi-provider aggregator CLI (75+ providers): work-capable,
|
|
105
|
+
last resort in coding-overflow. OpenRouter is direct-API — no CLI needed, only
|
|
106
|
+
`OPENROUTER_API_KEY` — and is **advise/consult only** (it cannot edit files);
|
|
107
|
+
its model slug is mandatory. "Ask <any hosted model> via OpenRouter" →
|
|
108
|
+
`dispatch.sh --vendor openrouter --model <slug> consult "<task>"`.
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
|
|
112
|
+
- Ask Opus to challenge this architecture →
|
|
113
|
+
`dispatch.sh --vendor claude --model claude-opus-5 --effort high consult "challenge this architecture"`
|
|
114
|
+
- 請 Grok 查最新公開資訊 →
|
|
115
|
+
`dispatch.sh --vendor grok consult "查最新公開資訊"`
|
|
116
|
+
- 哪個模型適合檢查大型 repo? → answer only; do not dispatch.
|
|
117
|
+
|
|
118
|
+
Consultation defaults to `advise`. Use `--mode work --workdir <dir>` only for
|
|
119
|
+
an explicit edit request. Missing explicit targets fail clearly; never remove
|
|
120
|
+
`--vendor` to obtain a fallback.
|
|
121
|
+
|
|
122
|
+
## Live UI is observation only
|
|
123
|
+
|
|
124
|
+
The optional Live UI is a read-only observer, not a prompt or dispatch path.
|
|
125
|
+
It displays existing jobs' `task.txt` and public `out.txt`, but never raw logs;
|
|
126
|
+
its history search and state filters can export only the currently visible public
|
|
127
|
+
metadata as local JSON; tokens and task/result bodies are excluded from export.
|
|
128
|
+
it cannot interpret natural language, choose routes, dispatch, retry, cancel,
|
|
129
|
+
delete jobs, or edit configuration. Natural-language interpretation and
|
|
130
|
+
dispatch stay in this skill and the CLI. Manage the local board with
|
|
131
|
+
`omnilane ui start|status|url|stop`, and stop it when monitoring is finished.
|
|
132
|
+
|
|
133
|
+
## Rules
|
|
134
|
+
|
|
135
|
+
- **Dispatch in `advise` mode by default** (read-only worker). Use `--mode work`
|
|
136
|
+
only when the worker must edit files, and give it an explicit `--workdir`.
|
|
137
|
+
- **`--mode sysops`** is `work` minus the vendor sandbox, for service
|
|
138
|
+
operations the sandbox denies (launchctl, system daemons). Codex runs with
|
|
139
|
+
`-s danger-full-access`; other vendors treat it as `work`. Explicit
|
|
140
|
+
per-dispatch opt-in only — never a lane default, and the task text must
|
|
141
|
+
name the exact service commands the worker is authorized to run.
|
|
142
|
+
Codex `work`/`sysops` still needs a git-repo `--workdir` (non-git
|
|
143
|
+
directories trip the whole-job fuse).
|
|
144
|
+
- **Every dispatched task states acceptance criteria and the exact verification
|
|
145
|
+
command.** Do not accept "done" without evidence.
|
|
146
|
+
- **No nested dispatch**: workers must not fan out again (enforced via
|
|
147
|
+
`OMNILANE_DEPTH`). Escalate back to the main loop instead.
|
|
148
|
+
- **Same-directory codex dispatches are serialized automatically** (lock);
|
|
149
|
+
do not try to parallelize them yourself.
|
|
150
|
+
- Escalate without asking: two failed attempts on a lane → move one lane up
|
|
151
|
+
(triage → bulk-mechanical → hardest-coding).
|
|
152
|
+
- Vendor quota exhausted (429 / "stream disconnected" / usage-limit message):
|
|
153
|
+
send mid-tier coding through coding-overflow instead; never silently downgrade
|
|
154
|
+
hardest-coding — wait or escalate to the user.
|
|
155
|
+
|
|
156
|
+
## Per-model notes (apply the row matching YOUR main model)
|
|
157
|
+
|
|
158
|
+
- **Claude (Fable/Opus main)**: top judgment and taste are yours — self-execute;
|
|
159
|
+
push mechanical coding volume out to the codex lanes.
|
|
160
|
+
- **Claude Sonnet main**: coordination/tools/mid-tier coding only; never
|
|
161
|
+
self-assign top judgment or hardest implementation.
|
|
162
|
+
- **GPT Sol main**: hardest coding + hard judgment are yours (use max for
|
|
163
|
+
judgment turns, xhigh for coding); cross to taste-final for style calls.
|
|
164
|
+
- **GPT Terra main**: bulk work is yours at max; escalate the genuinely hardest
|
|
165
|
+
pieces to Sol instead of grinding.
|
|
166
|
+
- **Grok 4.5 main**: mid-tier coding + live-search are yours; verify every API
|
|
167
|
+
signature and cited fact before shipping (measured high hallucination rate).
|
|
168
|
+
- **Gemini Flash main**: fast agentic/multimodal loops are yours; never
|
|
169
|
+
self-assign top judgment.
|
|
170
|
+
- **Gemini 3.1 Pro main**: 1M-context synthesis and context-heavy agentic work
|
|
171
|
+
are yours. Prefer Gemini Flash for fast repeated tool loops on speed/cost;
|
|
172
|
+
route hardest coding and judgment to the stronger codex lanes.
|