claude-dev-env 2.17.0 → 2.18.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.
Files changed (25) hide show
  1. package/.agents/skills/orchestrator/SKILL.md +6 -4
  2. package/.agents/skills/orchestrator-refresh/SKILL.md +9 -5
  3. package/.agents/skills/team-advisor/SKILL.md +5 -5
  4. package/_shared/advisor/AGENTS.md +5 -5
  5. package/_shared/advisor/advisor-protocol.md +54 -31
  6. package/_shared/advisor/reference/advisor-block.md +5 -1
  7. package/_shared/advisor/reference/consult-format.md +1 -1
  8. package/_shared/advisor/reference/identity.md +28 -0
  9. package/_shared/advisor/reference/lifecycle.md +8 -1
  10. package/_shared/advisor/reference/sol-rung.md +12 -7
  11. package/_shared/advisor/reference/spawn-walk-log.md +6 -5
  12. package/_shared/advisor/reference/third-party-bind.md +5 -6
  13. package/_shared/advisor/reference/warm-up.md +9 -2
  14. package/_shared/advisor/scripts/codex_sol_advisor.py +66 -11
  15. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +9 -0
  16. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +16 -4
  17. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +11 -10
  18. package/_shared/advisor/scripts/model_tier_run_validator.py +50 -23
  19. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +108 -3
  20. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +171 -82
  21. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +43 -0
  22. package/_shared/advisor/scripts/tier_model_ids.py +75 -7
  23. package/_shared/pr-loop/worker-spawn.md +4 -3
  24. package/docs/references/team-advisor-skill.md +2 -2
  25. package/package.json +1 -1
@@ -40,46 +40,61 @@ CODEX_BIND_SUCCESS_TOKEN = model_tier_run_validator.CODEX_BIND_SUCCESS_TOKEN
40
40
  def test_clean_single_spawn_at_top_of_slice_passes() -> None:
41
41
  run = ModelTierRun(
42
42
  own_tier="Opus",
43
- candidate_tiers=["Fable", "Opus"],
43
+ candidate_tiers=["Fable"],
44
44
  attempts=[{"tier": "Fable", "result": "spawned"}],
45
45
  selected_tier="Fable",
46
46
  )
47
47
  assert validate_model_tier_run(run) is None
48
48
 
49
49
 
50
- def test_sol_codex_bind_is_first_success_when_enabled() -> None:
50
+ def test_sol_codex_bind_succeeds_after_fable_when_enabled() -> None:
51
51
  run = ModelTierRun(
52
52
  own_tier="Opus",
53
- candidate_tiers=[ADVISOR_MODEL_TIER, "Fable", "Opus"],
54
- attempts=[{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN}],
53
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
54
+ attempts=[
55
+ {"tier": "Fable", "result": "unavailable"},
56
+ {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
57
+ ],
55
58
  selected_tier=ADVISOR_MODEL_TIER,
56
59
  is_sol_enabled=True,
57
60
  )
58
61
  assert validate_model_tier_run(run) is None
59
62
 
60
63
 
61
- def test_sol_failure_falls_through_to_fable_when_enabled() -> None:
64
+ def test_fable_success_with_sol_enabled_stops_before_sol() -> None:
62
65
  run = ModelTierRun(
63
66
  own_tier="Opus",
64
- candidate_tiers=[ADVISOR_MODEL_TIER, "Fable", "Opus"],
65
- attempts=[
66
- {"tier": ADVISOR_MODEL_TIER, "result": "unavailable"},
67
- {"tier": "Fable", "result": "spawned"},
68
- ],
67
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
68
+ attempts=[{"tier": "Fable", "result": "spawned"}],
69
69
  selected_tier="Fable",
70
70
  is_sol_enabled=True,
71
71
  )
72
72
  assert validate_model_tier_run(run) is None
73
73
 
74
74
 
75
- def test_sol_rung_precedes_third_party_cli_floor_when_enabled() -> None:
75
+ def test_sol_first_walk_raises_when_sol_is_enabled() -> None:
76
76
  run = ModelTierRun(
77
- own_tier="ThirdParty",
78
- candidate_tiers=[ADVISOR_MODEL_TIER, "Fable", "Opus"],
77
+ own_tier="Opus",
78
+ candidate_tiers=[ADVISOR_MODEL_TIER, "Fable"],
79
79
  attempts=[{"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN}],
80
80
  selected_tier=ADVISOR_MODEL_TIER,
81
81
  is_sol_enabled=True,
82
82
  )
83
+ with pytest.raises(ModelTierRunError):
84
+ validate_model_tier_run(run)
85
+
86
+
87
+ def test_sol_rung_follows_fable_on_third_party_cli_floor_when_enabled() -> None:
88
+ run = ModelTierRun(
89
+ own_tier="ThirdParty",
90
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
91
+ attempts=[
92
+ {"tier": "Fable", "result": "unavailable"},
93
+ {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
94
+ ],
95
+ selected_tier=ADVISOR_MODEL_TIER,
96
+ is_sol_enabled=True,
97
+ )
83
98
 
84
99
  assert validate_model_tier_run(run) is None
85
100
 
@@ -87,7 +102,7 @@ def test_sol_rung_precedes_third_party_cli_floor_when_enabled() -> None:
87
102
  def test_sol_codex_result_requires_sol_candidate() -> None:
88
103
  run = ModelTierRun(
89
104
  own_tier="Opus",
90
- candidate_tiers=["Fable", "Opus"],
105
+ candidate_tiers=["Fable"],
91
106
  attempts=[{"tier": "Fable", "result": CODEX_BIND_SUCCESS_TOKEN}],
92
107
  selected_tier="Fable",
93
108
  )
@@ -98,8 +113,11 @@ def test_sol_codex_result_requires_sol_candidate() -> None:
98
113
  def test_sol_spawned_result_does_not_count_as_codex_success() -> None:
99
114
  run = ModelTierRun(
100
115
  own_tier="Opus",
101
- candidate_tiers=[ADVISOR_MODEL_TIER, "Fable", "Opus"],
102
- attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "spawned"}],
116
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
117
+ attempts=[
118
+ {"tier": "Fable", "result": "unavailable"},
119
+ {"tier": ADVISOR_MODEL_TIER, "result": "spawned"},
120
+ ],
103
121
  selected_tier=ADVISOR_MODEL_TIER,
104
122
  is_sol_enabled=True,
105
123
  )
@@ -107,15 +125,13 @@ def test_sol_spawned_result_does_not_count_as_codex_success() -> None:
107
125
  validate_model_tier_run(run)
108
126
 
109
127
 
110
- def test_fallthrough_to_floor_tier_passes() -> None:
128
+ def test_exhausted_fable_walk_fails_closed() -> None:
111
129
  run = ModelTierRun(
112
130
  own_tier="Opus",
113
- candidate_tiers=["Fable", "Opus"],
114
- attempts=[
115
- {"tier": "Fable", "result": "unavailable"},
116
- {"tier": "Opus", "result": "spawned"},
117
- ],
118
- selected_tier="Opus",
131
+ candidate_tiers=["Fable"],
132
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
133
+ selected_tier=None,
134
+ fallback_reason="Fable did not bind; no advisor",
119
135
  )
120
136
  assert validate_model_tier_run(run) is None
121
137
 
@@ -123,21 +139,18 @@ def test_fallthrough_to_floor_tier_passes() -> None:
123
139
  def test_fully_exhausted_walk_with_fallback_reason_passes() -> None:
124
140
  run = ModelTierRun(
125
141
  own_tier="Opus",
126
- candidate_tiers=["Fable", "Opus"],
127
- attempts=[
128
- {"tier": "Fable", "result": "unavailable"},
129
- {"tier": "Opus", "result": "unavailable"},
130
- ],
142
+ candidate_tiers=["Fable"],
143
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
131
144
  selected_tier=None,
132
- fallback_reason="every candidate tier failed; CLI fallback took over",
145
+ fallback_reason="every candidate tier failed",
133
146
  )
134
147
  assert validate_model_tier_run(run) is None
135
148
 
136
149
 
137
- def test_candidate_tiers_shorter_than_ladder_slice_raises() -> None:
150
+ def test_opus_candidate_on_advisor_walk_raises() -> None:
138
151
  run = ModelTierRun(
139
152
  own_tier="Opus",
140
- candidate_tiers=["Fable"],
153
+ candidate_tiers=["Fable", "Opus"],
141
154
  attempts=[{"tier": "Fable", "result": "spawned"}],
142
155
  selected_tier="Fable",
143
156
  )
@@ -148,7 +161,7 @@ def test_candidate_tiers_shorter_than_ladder_slice_raises() -> None:
148
161
  def test_attempt_tier_outside_candidate_slice_raises() -> None:
149
162
  run = ModelTierRun(
150
163
  own_tier="Opus",
151
- candidate_tiers=["Fable", "Opus"],
164
+ candidate_tiers=["Fable"],
152
165
  attempts=[{"tier": "Haiku", "result": "spawned"}],
153
166
  selected_tier="Haiku",
154
167
  )
@@ -159,12 +172,13 @@ def test_attempt_tier_outside_candidate_slice_raises() -> None:
159
172
  def test_attempts_out_of_ladder_order_raises() -> None:
160
173
  run = ModelTierRun(
161
174
  own_tier="Opus",
162
- candidate_tiers=["Fable", "Opus"],
175
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
163
176
  attempts=[
164
- {"tier": "Opus", "result": "unavailable"},
165
- {"tier": "Fable", "result": "spawned"},
177
+ {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
178
+ {"tier": "Fable", "result": "unavailable"},
166
179
  ],
167
- selected_tier="Fable",
180
+ selected_tier=ADVISOR_MODEL_TIER,
181
+ is_sol_enabled=True,
168
182
  )
169
183
  with pytest.raises(ModelTierRunError):
170
184
  validate_model_tier_run(run)
@@ -173,12 +187,13 @@ def test_attempts_out_of_ladder_order_raises() -> None:
173
187
  def test_selected_tier_not_first_spawned_attempt_raises() -> None:
174
188
  run = ModelTierRun(
175
189
  own_tier="Opus",
176
- candidate_tiers=["Fable", "Opus"],
190
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
177
191
  attempts=[
178
192
  {"tier": "Fable", "result": "unavailable"},
179
- {"tier": "Opus", "result": "spawned"},
193
+ {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN},
180
194
  ],
181
195
  selected_tier="Fable",
196
+ is_sol_enabled=True,
182
197
  )
183
198
  with pytest.raises(
184
199
  ModelTierRunError,
@@ -193,12 +208,9 @@ def test_selected_tier_not_first_spawned_attempt_raises() -> None:
193
208
  def test_exhausted_walk_with_non_null_selected_tier_raises() -> None:
194
209
  run = ModelTierRun(
195
210
  own_tier="Opus",
196
- candidate_tiers=["Fable", "Opus"],
197
- attempts=[
198
- {"tier": "Fable", "result": "unavailable"},
199
- {"tier": "Opus", "result": "unavailable"},
200
- ],
201
- selected_tier="Opus",
211
+ candidate_tiers=["Fable"],
212
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
213
+ selected_tier="Fable",
202
214
  fallback_reason="every candidate tier failed",
203
215
  )
204
216
  with pytest.raises(ModelTierRunError):
@@ -208,11 +220,8 @@ def test_exhausted_walk_with_non_null_selected_tier_raises() -> None:
208
220
  def test_exhausted_walk_missing_fallback_reason_raises() -> None:
209
221
  run = ModelTierRun(
210
222
  own_tier="Opus",
211
- candidate_tiers=["Fable", "Opus"],
212
- attempts=[
213
- {"tier": "Fable", "result": "unavailable"},
214
- {"tier": "Opus", "result": "unavailable"},
215
- ],
223
+ candidate_tiers=["Fable"],
224
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
216
225
  selected_tier=None,
217
226
  )
218
227
  with pytest.raises(ModelTierRunError):
@@ -233,7 +242,7 @@ def test_unknown_own_tier_raises() -> None:
233
242
  def test_empty_attempts_with_null_selected_tier_raises() -> None:
234
243
  run = ModelTierRun(
235
244
  own_tier="Opus",
236
- candidate_tiers=["Fable", "Opus"],
245
+ candidate_tiers=["Fable"],
237
246
  attempts=[],
238
247
  selected_tier=None,
239
248
  fallback_reason="skipped straight to CLI fallback",
@@ -242,13 +251,14 @@ def test_empty_attempts_with_null_selected_tier_raises() -> None:
242
251
  validate_model_tier_run(run)
243
252
 
244
253
 
245
- def test_incomplete_fallback_walk_before_floor_raises() -> None:
254
+ def test_incomplete_fallback_walk_before_sol_raises() -> None:
246
255
  run = ModelTierRun(
247
256
  own_tier="Opus",
248
- candidate_tiers=["Fable", "Opus"],
257
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
249
258
  attempts=[{"tier": "Fable", "result": "unavailable"}],
250
259
  selected_tier=None,
251
- fallback_reason="stopped after Fable without trying Opus",
260
+ fallback_reason="stopped after Fable without trying Sol",
261
+ is_sol_enabled=True,
252
262
  )
253
263
  with pytest.raises(ModelTierRunError):
254
264
  validate_model_tier_run(run)
@@ -257,12 +267,9 @@ def test_incomplete_fallback_walk_before_floor_raises() -> None:
257
267
  def test_lowercase_own_tier_and_candidates_pass() -> None:
258
268
  run = ModelTierRun(
259
269
  own_tier="opus",
260
- candidate_tiers=["fable", "opus"],
261
- attempts=[
262
- {"tier": "fable", "result": "unavailable"},
263
- {"tier": "opus", "result": "spawned"},
264
- ],
265
- selected_tier="opus",
270
+ candidate_tiers=["fable"],
271
+ attempts=[{"tier": "fable", "result": "spawned"}],
272
+ selected_tier="fable",
266
273
  )
267
274
  assert validate_model_tier_run(run) is None
268
275
 
@@ -273,7 +280,7 @@ def test_cli_validates_json_log_file(tmp_path: Path) -> None:
273
280
  json.dumps(
274
281
  {
275
282
  "own_tier": "Opus",
276
- "candidate_tiers": ["Fable", "Opus"],
283
+ "candidate_tiers": ["Fable"],
277
284
  "attempts": [{"tier": "Fable", "result": "spawned"}],
278
285
  "selected_tier": "Fable",
279
286
  }
@@ -283,6 +290,24 @@ def test_cli_validates_json_log_file(tmp_path: Path) -> None:
283
290
  assert main([str(log_path)]) == 0
284
291
  loaded_run = load_model_tier_run_from_json_path(from_path=log_path)
285
292
  assert loaded_run.selected_tier == "Fable"
293
+ assert loaded_run.host_profile == "Claude"
294
+
295
+
296
+ def test_cli_rejects_non_string_host_profile(tmp_path: Path) -> None:
297
+ log_path = tmp_path / "invalid-host-profile.json"
298
+ log_path.write_text(
299
+ json.dumps(
300
+ {
301
+ "own_tier": "Opus",
302
+ "candidate_tiers": ["Fable"],
303
+ "attempts": [{"tier": "Fable", "result": "spawned"}],
304
+ "selected_tier": "Fable",
305
+ "host_profile": 1,
306
+ }
307
+ ),
308
+ encoding="utf-8",
309
+ )
310
+ assert main([str(log_path)]) == 2
286
311
 
287
312
 
288
313
  def test_cli_rejects_non_boolean_sol_enabled(tmp_path: Path) -> None:
@@ -291,7 +316,7 @@ def test_cli_rejects_non_boolean_sol_enabled(tmp_path: Path) -> None:
291
316
  json.dumps(
292
317
  {
293
318
  "own_tier": "Opus",
294
- "candidate_tiers": ["Fable", "Opus"],
319
+ "candidate_tiers": ["Fable"],
295
320
  "attempts": [{"tier": "Fable", "result": "spawned"}],
296
321
  "selected_tier": "Fable",
297
322
  "sol_enabled": "false",
@@ -308,10 +333,11 @@ def test_cli_rejects_incomplete_fallback_log(tmp_path: Path) -> None:
308
333
  json.dumps(
309
334
  {
310
335
  "own_tier": "Opus",
311
- "candidate_tiers": ["Fable", "Opus"],
336
+ "candidate_tiers": ["Fable", "Sol"],
312
337
  "attempts": [{"tier": "Fable", "result": "unavailable"}],
313
338
  "selected_tier": None,
314
339
  "fallback_reason": "incomplete",
340
+ "sol_enabled": True,
315
341
  }
316
342
  ),
317
343
  encoding="utf-8",
@@ -326,14 +352,14 @@ def test_cli_missing_path_returns_usage_exit_code() -> None:
326
352
  def test_cli_bind_at_fable_passes() -> None:
327
353
  run = ModelTierRun(
328
354
  own_tier="Opus",
329
- candidate_tiers=["Fable", "Opus"],
355
+ candidate_tiers=["Fable"],
330
356
  attempts=[{"tier": "Fable", "result": "cli"}],
331
357
  selected_tier="Fable",
332
358
  )
333
359
  assert validate_model_tier_run(run) is None
334
360
 
335
361
 
336
- def test_cli_bind_fallthrough_to_opus_passes() -> None:
362
+ def test_cli_bind_fallthrough_to_opus_raises() -> None:
337
363
  run = ModelTierRun(
338
364
  own_tier="Opus",
339
365
  candidate_tiers=["Fable", "Opus"],
@@ -343,13 +369,14 @@ def test_cli_bind_fallthrough_to_opus_passes() -> None:
343
369
  ],
344
370
  selected_tier="Opus",
345
371
  )
346
- assert validate_model_tier_run(run) is None
372
+ with pytest.raises(ModelTierRunError):
373
+ validate_model_tier_run(run)
347
374
 
348
375
 
349
- def test_third_party_own_tier_maps_to_fable_opus_cli_bind_passes() -> None:
376
+ def test_third_party_own_tier_maps_to_fable_cli_bind_passes() -> None:
350
377
  run = ModelTierRun(
351
378
  own_tier="ThirdParty",
352
- candidate_tiers=["Fable", "Opus"],
379
+ candidate_tiers=["Fable"],
353
380
  attempts=[{"tier": "Fable", "result": "cli"}],
354
381
  selected_tier="Fable",
355
382
  )
@@ -359,7 +386,7 @@ def test_third_party_own_tier_maps_to_fable_opus_cli_bind_passes() -> None:
359
386
  def test_third_party_own_tier_lowercase_cli_bind_passes() -> None:
360
387
  run = ModelTierRun(
361
388
  own_tier="thirdparty",
362
- candidate_tiers=["fable", "opus"],
389
+ candidate_tiers=["fable"],
363
390
  attempts=[{"tier": "fable", "result": "cli"}],
364
391
  selected_tier="fable",
365
392
  )
@@ -369,7 +396,7 @@ def test_third_party_own_tier_lowercase_cli_bind_passes() -> None:
369
396
  def test_self_token_is_not_bind_success_raises() -> None:
370
397
  run = ModelTierRun(
371
398
  own_tier="Opus",
372
- candidate_tiers=["Fable", "Opus"],
399
+ candidate_tiers=["Fable"],
373
400
  attempts=[{"tier": "Fable", "result": "self"}],
374
401
  selected_tier="Fable",
375
402
  )
@@ -380,7 +407,7 @@ def test_self_token_is_not_bind_success_raises() -> None:
380
407
  def test_third_party_self_token_is_not_bind_success_raises() -> None:
381
408
  run = ModelTierRun(
382
409
  own_tier="ThirdParty",
383
- candidate_tiers=["Fable", "Opus"],
410
+ candidate_tiers=["Fable"],
384
411
  attempts=[{"tier": "Fable", "result": "self"}],
385
412
  selected_tier="Fable",
386
413
  )
@@ -402,11 +429,8 @@ def test_third_party_host_legacy_single_tier_self_bind_raises() -> None:
402
429
  def test_third_party_cli_exhausted_fail_closed_passes() -> None:
403
430
  run = ModelTierRun(
404
431
  own_tier="ThirdParty",
405
- candidate_tiers=["Fable", "Opus"],
406
- attempts=[
407
- {"tier": "Fable", "result": "unavailable"},
408
- {"tier": "Opus", "result": "unavailable"},
409
- ],
432
+ candidate_tiers=["Fable"],
433
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
410
434
  selected_tier=None,
411
435
  fallback_reason=(
412
436
  "third-party host CLI Claude-chain exhausted; fail closed"
@@ -418,11 +442,8 @@ def test_third_party_cli_exhausted_fail_closed_passes() -> None:
418
442
  def test_third_party_cli_exhausted_without_fallback_reason_raises() -> None:
419
443
  run = ModelTierRun(
420
444
  own_tier="ThirdParty",
421
- candidate_tiers=["Fable", "Opus"],
422
- attempts=[
423
- {"tier": "Fable", "result": "unavailable"},
424
- {"tier": "Opus", "result": "unavailable"},
425
- ],
445
+ candidate_tiers=["Fable"],
446
+ attempts=[{"tier": "Fable", "result": "unavailable"}],
426
447
  selected_tier=None,
427
448
  )
428
449
  with pytest.raises(ModelTierRunError):
@@ -432,7 +453,7 @@ def test_third_party_cli_exhausted_without_fallback_reason_raises() -> None:
432
453
  def test_third_party_cli_selected_tier_mismatch_raises() -> None:
433
454
  run = ModelTierRun(
434
455
  own_tier="ThirdParty",
435
- candidate_tiers=["Fable", "Opus"],
456
+ candidate_tiers=["Fable"],
436
457
  attempts=[{"tier": "Fable", "result": "cli"}],
437
458
  selected_tier="Opus",
438
459
  )
@@ -443,9 +464,77 @@ def test_third_party_cli_selected_tier_mismatch_raises() -> None:
443
464
  def test_claude_host_self_token_is_not_spawn_success_raises() -> None:
444
465
  run = ModelTierRun(
445
466
  own_tier="Opus",
446
- candidate_tiers=["Fable", "Opus"],
467
+ candidate_tiers=["Fable"],
447
468
  attempts=[{"tier": "Fable", "result": "self"}],
448
469
  selected_tier="Fable",
449
470
  )
450
471
  with pytest.raises(ModelTierRunError):
451
472
  validate_model_tier_run(run)
473
+
474
+
475
+ def test_codex_host_sol_in_session_spawn_passes() -> None:
476
+ run = ModelTierRun(
477
+ own_tier="Opus",
478
+ candidate_tiers=[ADVISOR_MODEL_TIER],
479
+ attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "spawned"}],
480
+ selected_tier=ADVISOR_MODEL_TIER,
481
+ host_profile="Codex",
482
+ )
483
+ assert validate_model_tier_run(run) is None
484
+
485
+
486
+ def test_codex_host_sol_codex_token_counts_as_success() -> None:
487
+ run = ModelTierRun(
488
+ own_tier="Opus",
489
+ candidate_tiers=[ADVISOR_MODEL_TIER],
490
+ attempts=[
491
+ {"tier": ADVISOR_MODEL_TIER, "result": CODEX_BIND_SUCCESS_TOKEN}
492
+ ],
493
+ selected_tier=ADVISOR_MODEL_TIER,
494
+ host_profile="Codex",
495
+ )
496
+ assert validate_model_tier_run(run) is None
497
+
498
+
499
+ def test_codex_host_fable_then_sol_walk_raises() -> None:
500
+ run = ModelTierRun(
501
+ own_tier="Opus",
502
+ candidate_tiers=["Fable", ADVISOR_MODEL_TIER],
503
+ attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "spawned"}],
504
+ selected_tier=ADVISOR_MODEL_TIER,
505
+ is_sol_enabled=True,
506
+ host_profile="Codex",
507
+ )
508
+ with pytest.raises(ModelTierRunError):
509
+ validate_model_tier_run(run)
510
+
511
+
512
+ def test_codex_host_exhausted_sol_fails_closed() -> None:
513
+ run = ModelTierRun(
514
+ own_tier="Opus",
515
+ candidate_tiers=[ADVISOR_MODEL_TIER],
516
+ attempts=[{"tier": ADVISOR_MODEL_TIER, "result": "unavailable"}],
517
+ selected_tier=None,
518
+ fallback_reason="Codex in-session Sol spawn did not bind",
519
+ host_profile="Codex",
520
+ )
521
+ assert validate_model_tier_run(run) is None
522
+
523
+
524
+ def test_cli_loads_codex_host_profile(tmp_path: Path) -> None:
525
+ log_path = tmp_path / "codex-host-walk.json"
526
+ log_path.write_text(
527
+ json.dumps(
528
+ {
529
+ "own_tier": "Opus",
530
+ "candidate_tiers": ["Sol"],
531
+ "attempts": [{"tier": "Sol", "result": "spawned"}],
532
+ "selected_tier": "Sol",
533
+ "host_profile": "Codex",
534
+ }
535
+ ),
536
+ encoding="utf-8",
537
+ )
538
+ assert main([str(log_path)]) == 0
539
+ loaded_run = load_model_tier_run_from_json_path(from_path=log_path)
540
+ assert loaded_run.host_profile == "Codex"
@@ -20,6 +20,7 @@ from advisor_scripts_constants.model_tier_run_validator_constants import ( # no
20
20
  ALL_KNOWN_TIER_NAMES,
21
21
  ALL_MODEL_TIERS,
22
22
  HOST_PROFILE_CLAUDE,
23
+ HOST_PROFILE_CODEX,
23
24
  HOST_PROFILE_THIRD_PARTY,
24
25
  THIRD_PARTY_MODEL_TIER,
25
26
  )
@@ -49,6 +50,8 @@ resolve_cli_model_id = tier_model_ids.resolve_cli_model_id
49
50
  resolve_codex_model_id = tier_model_ids.resolve_codex_model_id
50
51
  canonical_tier_name = tier_model_ids.canonical_tier_name
51
52
  detect_host_profile = tier_model_ids.detect_host_profile
53
+ resolve_session_identity = tier_model_ids.resolve_session_identity
54
+ canonical_host_profile = tier_model_ids.canonical_host_profile
52
55
 
53
56
  SCRIPTS_ROOT = Path(__file__).parent.parent
54
57
  DOCUMENTED_RESOLVE_ONE_LINER = (
@@ -183,6 +186,46 @@ def test_detect_host_profile_rejects_unknown_explicit_value() -> None:
183
186
  detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "Titan"})
184
187
 
185
188
 
189
+ def test_canonical_host_profile_normalizes_known_names() -> None:
190
+ assert canonical_host_profile("codex") == HOST_PROFILE_CODEX
191
+ assert canonical_host_profile(" Claude ") == HOST_PROFILE_CLAUDE
192
+ assert canonical_host_profile("Titan") is None
193
+ assert canonical_host_profile("") is None
194
+
195
+
196
+ def test_detect_host_profile_reads_codex_override() -> None:
197
+ assert (
198
+ detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "Codex"})
199
+ == HOST_PROFILE_CODEX
200
+ )
201
+ assert (
202
+ detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "codex"})
203
+ == HOST_PROFILE_CODEX
204
+ )
205
+
206
+
207
+ def test_resolve_session_identity_maps_codex_text_to_codex() -> None:
208
+ assert resolve_session_identity("codex") == HOST_PROFILE_CODEX
209
+ assert resolve_session_identity("I am Codex") == HOST_PROFILE_CODEX
210
+ assert resolve_session_identity("Codex CLI") == HOST_PROFILE_CODEX
211
+
212
+
213
+ def test_resolve_session_identity_maps_claude_text_to_claude() -> None:
214
+ assert resolve_session_identity("claude") == HOST_PROFILE_CLAUDE
215
+ assert resolve_session_identity("Claude Code") == HOST_PROFILE_CLAUDE
216
+
217
+
218
+ def test_resolve_session_identity_maps_neither_to_third_party() -> None:
219
+ assert resolve_session_identity("grok") == HOST_PROFILE_THIRD_PARTY
220
+ assert resolve_session_identity("cursor") == HOST_PROFILE_THIRD_PARTY
221
+ assert resolve_session_identity("") == HOST_PROFILE_THIRD_PARTY
222
+ assert resolve_session_identity(" ") == HOST_PROFILE_THIRD_PARTY
223
+
224
+
225
+ def test_resolve_session_identity_prefers_codex_when_both_tokens_appear() -> None:
226
+ assert resolve_session_identity("claude using Codex") == HOST_PROFILE_CODEX
227
+
228
+
186
229
  def test_documented_resolve_one_liner_runs_without_prior_path_pollution() -> None:
187
230
  clean_environment = {
188
231
  each_key: each_value
@@ -12,6 +12,12 @@
12
12
  detect_host_profile(
13
13
  setting_by_name={"ADVISOR_HOST_PROFILE": "Claude"}
14
14
  ) # ok: "Claude"
15
+ detect_host_profile(
16
+ setting_by_name={"ADVISOR_HOST_PROFILE": "Codex"}
17
+ ) # ok: "Codex"
18
+ resolve_session_identity("I am Codex") # ok: "Codex"
19
+ resolve_session_identity("Claude Code") # ok: "Claude"
20
+ resolve_session_identity("grok") # ok: "ThirdParty"
15
21
 
16
22
  Values are stable short aliases (``opus``, ``sonnet``, ``third-party``), not dated
17
23
  full model IDs such as ``claude-opus-4-…``. The map lives in
@@ -25,6 +31,7 @@ Args and returns for the public helpers are documented on each function.
25
31
  from __future__ import annotations
26
32
 
27
33
  import os
34
+ import re
28
35
  import sys
29
36
  from collections.abc import Mapping
30
37
  from pathlib import Path
@@ -43,8 +50,12 @@ from advisor_scripts_constants.model_tier_run_validator_constants import ( # no
43
50
  ALL_KNOWN_TIER_NAMES,
44
51
  ALL_THIRD_PARTY_TRUTHY_VALUES,
45
52
  HOST_PROFILE_CLAUDE,
53
+ HOST_PROFILE_CODEX,
46
54
  HOST_PROFILE_ENV_VAR,
47
55
  HOST_PROFILE_THIRD_PARTY,
56
+ SESSION_IDENTITY_CLAUDE_TOKEN,
57
+ SESSION_IDENTITY_CODEX_TOKEN,
58
+ SESSION_IDENTITY_WORD_PATTERN,
48
59
  THIRD_PARTY_ENV_VAR,
49
60
  UNKNOWN_HOST_PROFILE_ERROR,
50
61
  UNKNOWN_LADDER_NAME_ERROR,
@@ -139,6 +150,65 @@ def resolve_codex_model_id(tier: str) -> str:
139
150
  return maybe_model_id
140
151
 
141
152
 
153
+ def canonical_host_profile(host_profile_name: str) -> str | None:
154
+ """Return the Title Case host profile for ``host_profile_name``, or ``None``.
155
+
156
+ ::
157
+
158
+ canonical_host_profile("codex") # ok: "Codex"
159
+ canonical_host_profile(" Claude ") # ok: "Claude"
160
+ canonical_host_profile("Titan") # ok: None
161
+ canonical_host_profile("") # ok: None
162
+
163
+ Strips leading and trailing whitespace, then matches any letter case
164
+ against ``ALL_HOST_PROFILES``.
165
+
166
+ Args:
167
+ host_profile_name: Raw host profile text from env, a spawn log, or a
168
+ caller.
169
+
170
+ Returns:
171
+ The canonical Title Case host profile, or ``None`` when unknown.
172
+ """
173
+ stripped_host_profile_name = host_profile_name.strip()
174
+ if not stripped_host_profile_name:
175
+ return None
176
+ host_profile_by_lower_name = {
177
+ each_profile.lower(): each_profile for each_profile in ALL_HOST_PROFILES
178
+ }
179
+ return host_profile_by_lower_name.get(stripped_host_profile_name.lower())
180
+
181
+
182
+ def resolve_session_identity(identity_text: str) -> str:
183
+ """Map a session's self-identified name to an advisor host profile.
184
+
185
+ ::
186
+
187
+ resolve_session_identity("I am Codex") # ok: "Codex"
188
+ resolve_session_identity("Claude Code") # ok: "Claude"
189
+ resolve_session_identity("grok") # ok: "ThirdParty"
190
+ resolve_session_identity("") # ok: "ThirdParty"
191
+
192
+ Split the text into letter-or-digit tokens. A ``codex`` token selects
193
+ Codex. A ``claude`` token selects Claude. Any other identity, including
194
+ empty text, selects ThirdParty. When both tokens appear, Codex wins.
195
+
196
+ Args:
197
+ identity_text: The session's named identity.
198
+
199
+ Returns:
200
+ ``HOST_PROFILE_CODEX``, ``HOST_PROFILE_CLAUDE``, or
201
+ ``HOST_PROFILE_THIRD_PARTY``.
202
+ """
203
+ identity_word_pattern = re.compile(SESSION_IDENTITY_WORD_PATTERN)
204
+ all_identity_tokens = identity_word_pattern.findall(identity_text.lower())
205
+ if SESSION_IDENTITY_CODEX_TOKEN in all_identity_tokens:
206
+ return HOST_PROFILE_CODEX
207
+ if SESSION_IDENTITY_CLAUDE_TOKEN in all_identity_tokens:
208
+ return HOST_PROFILE_CLAUDE
209
+ return HOST_PROFILE_THIRD_PARTY
210
+
211
+
142
212
  def detect_host_profile(
143
213
  setting_by_name: Mapping[str, str] | None = None,
144
214
  ) -> str:
@@ -148,6 +218,8 @@ def detect_host_profile(
148
218
 
149
219
  detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "ThirdParty"})
150
220
  # ok: "ThirdParty"
221
+ detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "Codex"})
222
+ # ok: "Codex"
151
223
  detect_host_profile(setting_by_name={"THIRD_PARTY": "1"}) # ok: "ThirdParty"
152
224
  detect_host_profile(setting_by_name={}) # ok: "Claude"
153
225
  detect_host_profile(setting_by_name={"ADVISOR_HOST_PROFILE": "X"})
@@ -163,7 +235,8 @@ def detect_host_profile(
163
235
  setting_by_name: Env name → setting text (defaults to ``os.environ``).
164
236
 
165
237
  Returns:
166
- ``HOST_PROFILE_THIRD_PARTY`` or ``HOST_PROFILE_CLAUDE``.
238
+ ``HOST_PROFILE_CODEX``, ``HOST_PROFILE_THIRD_PARTY``, or
239
+ ``HOST_PROFILE_CLAUDE``.
167
240
 
168
241
  Raises:
169
242
  ValueError: When ``ADVISOR_HOST_PROFILE`` is set to an unknown name.
@@ -175,12 +248,7 @@ def detect_host_profile(
175
248
  HOST_PROFILE_ENV_VAR, ""
176
249
  ).strip()
177
250
  if explicit_host_profile:
178
- host_profile_by_lower_name = {
179
- each_profile.lower(): each_profile for each_profile in ALL_HOST_PROFILES
180
- }
181
- maybe_canonical_host = host_profile_by_lower_name.get(
182
- explicit_host_profile.lower()
183
- )
251
+ maybe_canonical_host = canonical_host_profile(explicit_host_profile)
184
252
  if maybe_canonical_host is None:
185
253
  raise ValueError(UNKNOWN_HOST_PROFILE_ERROR.format(explicit_host_profile))
186
254
  return maybe_canonical_host