Skip to content

Commit e0460e7

Browse files
authored
Route Codex Aliases to GPT-5.5 (#4219)
## Summary Codex model aliases now route `sonnet`, `opus`, and `haiku` to `gpt-5.5`, preventing default Codex fleet sessions from launching on forbidden 5.4-era model IDs. The local class distinction is preserved through `model_reasoning_effort`. The doctor allowlist and model-alias regression tests now reject `gpt-5.4` and `gpt-5.4-mini` as Codex alias targets. Closes #4218 ## Implementation Plan Plan file: `/home/talon/projects/generic_automation_mcp/.autoskillit/temp/prepare-pr/plan_codex_model_alias_gpt55_4218.md` Generated via AutoSkillit <!-- autoskillit:pipeline-signature steps=prepare_pr,run_arch_lenses,compose_pr,annotate_pr_diff,review_pr -->
1 parent d89fb2a commit e0460e7

9 files changed

Lines changed: 63 additions & 35 deletions

File tree

src/autoskillit/cli/doctor/_doctor_runtime.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
atomic_write,
2020
default_log_dir,
2121
get_logger,
22+
is_valid_codex_model_id,
2223
)
2324
from autoskillit.execution import QUOTA_CACHE_SCHEMA_VERSION
2425

@@ -28,8 +29,6 @@
2829

2930
CODEX_MIN_VERSION: tuple[int, ...] = (0, 130, 0)
3031

31-
VALID_CODEX_MODEL_IDS: frozenset[str] = frozenset({"gpt-5.4", "gpt-5.4-mini", "gpt-5.5"})
32-
3332
_CODEX_ALIAS_STALENESS_DAYS: int = 90
3433

3534

@@ -390,14 +389,14 @@ def _check_codex_model_alias_staleness() -> DoctorResult:
390389
f" (threshold {_CODEX_ALIAS_STALENESS_DAYS}d);"
391390
f" re-verify alias targets and update CODEX_MODEL_ALIASES_LAST_VERIFIED",
392391
)
393-
invalid = {k: v for k, v in CODEX_MODEL_ALIASES.items() if v not in VALID_CODEX_MODEL_IDS}
392+
invalid = {k: v for k, v in CODEX_MODEL_ALIASES.items() if not is_valid_codex_model_id(v)}
394393
if invalid:
395394
pairs = ", ".join(f"{k}={v!r}" for k, v in invalid.items())
396395
return DoctorResult(
397396
Severity.WARNING,
398397
check_name,
399398
f"CODEX_MODEL_ALIASES contains unrecognized model IDs: {pairs};"
400-
f" update VALID_CODEX_MODEL_IDS or fix the alias",
399+
f" update CODEX_VALID_MODEL_IDS or fix the alias",
401400
)
402401
return DoctorResult(
403402
Severity.OK,

src/autoskillit/core/__init__.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ from .types import CODEX_MODEL_ALIASES as CODEX_MODEL_ALIASES
147147
from .types import CODEX_MODEL_ALIASES_LAST_VERIFIED as CODEX_MODEL_ALIASES_LAST_VERIFIED
148148
from .types import CODEX_SCHEMA_VERSION as CODEX_SCHEMA_VERSION
149149
from .types import CODEX_SESSIONS_SUBDIR as CODEX_SESSIONS_SUBDIR
150+
from .types import CODEX_VALID_MODEL_IDS as CODEX_VALID_MODEL_IDS
150151
from .types import CONFIG_AUTHORITY_KEYS as CONFIG_AUTHORITY_KEYS
151152
from .types import CONTEXT_EXHAUSTION_MARKER as CONTEXT_EXHAUSTION_MARKER
152153
from .types import CORE_PACKS as CORE_PACKS
@@ -368,6 +369,7 @@ from .types import extract_positional_args as extract_positional_args
368369
from .types import extract_skill_name as extract_skill_name
369370
from .types import fleet_error as fleet_error
370371
from .types import is_path_like_token as is_path_like_token
372+
from .types import is_valid_codex_model_id as is_valid_codex_model_id
371373
from .types import model_class as model_class
372374
from .types import resolve_payload_field as resolve_payload_field
373375
from .types import resolve_skill_name as resolve_skill_name

src/autoskillit/core/types/_type_backend.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from collections.abc import Mapping
77
from dataclasses import dataclass, field
88
from pathlib import Path
9+
from types import MappingProxyType
910
from typing import Any
1011

1112
from ._type_checkpoint import SessionCheckpoint
@@ -22,6 +23,7 @@
2223
"CODEX_EFFORT_MAPPING",
2324
"CODEX_MODEL_ALIASES",
2425
"CODEX_MODEL_ALIASES_LAST_VERIFIED",
26+
"CODEX_VALID_MODEL_IDS",
2527
"CmdOrigin",
2628
"CmdSpec",
2729
"ModelTranslation",
@@ -30,6 +32,7 @@
3032
"CodexEventData",
3133
"SessionEvent",
3234
"AgentSessionResult",
35+
"is_valid_codex_model_id",
3336
"model_class",
3437
"strip_context_window_suffix",
3538
]
@@ -177,27 +180,43 @@ class BackendCapabilities:
177180
"haiku": "haiku",
178181
}
179182

180-
CODEX_MODEL_ALIASES: dict[str, str] = {
181-
"sonnet": "gpt-5.4",
182-
"opus": "gpt-5.5",
183-
"haiku": "gpt-5.4-mini",
184-
}
183+
CODEX_MODEL_ALIASES: Mapping[str, str] = MappingProxyType(
184+
{
185+
"sonnet": "gpt-5.5",
186+
"opus": "gpt-5.5",
187+
"haiku": "gpt-5.5",
188+
}
189+
)
185190

186-
CODEX_MODEL_ALIASES_LAST_VERIFIED: str = "2026-06-11"
191+
CODEX_MODEL_ALIASES_LAST_VERIFIED: str = "2026-07-09"
192+
193+
CODEX_VALID_MODEL_IDS: frozenset[str] = frozenset({"gpt-5.5"})
194+
195+
assert set(CODEX_MODEL_ALIASES.values()).issubset(CODEX_VALID_MODEL_IDS), (
196+
"CODEX_MODEL_ALIASES values must all be members of CODEX_VALID_MODEL_IDS; "
197+
f"got {sorted(set(CODEX_MODEL_ALIASES.values()) - CODEX_VALID_MODEL_IDS)}"
198+
)
187199

188200
CODEX_EFFORT_MAPPING: dict[str, str] = {
189201
"sonnet": "high",
190202
"opus": "xhigh",
191203
"haiku": "medium",
192204
}
193205

194-
_CODEX_MODEL_REVERSE: dict[str, str] = {v: k for k, v in CODEX_MODEL_ALIASES.items()}
195-
_codex_alias_values = list(CODEX_MODEL_ALIASES.values())
196-
assert len(_CODEX_MODEL_REVERSE) == len(CODEX_MODEL_ALIASES), (
197-
"CODEX_MODEL_ALIASES values must be unique — duplicate makes an alias unreachable "
198-
f"via model_class(). Duplicates: "
199-
f"{[v for v in _codex_alias_values if _codex_alias_values.count(v) > 1]}"
200-
)
206+
207+
def _codex_unique_model_reverse(aliases: Mapping[str, str]) -> Mapping[str, str]:
208+
"""Return reverse aliases only for native model IDs used by one local class.
209+
210+
Shared native IDs are intentionally omitted so model_class() falls back to
211+
the Codex model ID instead of projecting to an arbitrary local class.
212+
"""
213+
values = tuple(aliases.values())
214+
return {model_id: alias for alias, model_id in aliases.items() if values.count(model_id) == 1}
215+
216+
217+
# Reverse lookup is valid only for one-to-one native IDs. When multiple local
218+
# classes share a Codex model, the class is carried by model_reasoning_effort.
219+
_CODEX_MODEL_REVERSE: Mapping[str, str] = _codex_unique_model_reverse(CODEX_MODEL_ALIASES)
201220

202221

203222
@dataclass(frozen=True, slots=True)
@@ -217,6 +236,10 @@ def strip_context_window_suffix(model: str) -> str:
217236
return _CONTEXT_WINDOW_SUFFIX_RE.sub("", model)
218237

219238

239+
def is_valid_codex_model_id(model_id: str) -> bool:
240+
return model_id in CODEX_VALID_MODEL_IDS
241+
242+
220243
def model_class(model: str) -> str:
221244
base = strip_context_window_suffix(model)
222245
if base in CLAUDE_MODEL_ALIASES:

tests/arch/test_backend_translate_model.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def test_translate_model_called_at_terminal_model_sites() -> None:
6868
)
6969

7070

71-
def test_translate_model_distinct_per_alias_class() -> None:
71+
def test_translate_model_resolution_distinct_per_alias_class() -> None:
7272
from autoskillit.core.types._type_backend import CLAUDE_MODEL_ALIASES
7373

7474
alias_keys = list(CLAUDE_MODEL_ALIASES.keys())
@@ -77,8 +77,10 @@ def test_translate_model_distinct_per_alias_class() -> None:
7777
backend = cls()
7878
except TypeError:
7979
pytest.skip(f"{backend_name}: requires constructor args — cannot validate")
80-
translated = [backend.translate_model(k) for k in alias_keys]
80+
translated = [
81+
(backend.translate_model(k), backend.model_config_overrides(k)) for k in alias_keys
82+
]
8183
assert len(translated) == len(set(translated)), (
82-
f"{backend_name}: translate_model produced duplicate outputs for alias keys "
84+
f"{backend_name}: model resolution produced duplicate outputs for alias keys "
8385
f"{alias_keys}: {translated}"
8486
)

tests/cli/test_doctor_runtime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_ok_when_within_threshold_and_valid_aliases(
2121
monkeypatch.setattr(
2222
mod,
2323
"CODEX_MODEL_ALIASES",
24-
{"sonnet": "gpt-5.4", "opus": "gpt-5.5", "haiku": "gpt-5.4-mini"},
24+
{"sonnet": "gpt-5.5", "opus": "gpt-5.5", "haiku": "gpt-5.5"},
2525
)
2626
result = mod._check_codex_model_alias_staleness()
2727
assert result.severity == Severity.OK
@@ -45,7 +45,7 @@ def test_warning_when_alias_value_invalid(self, monkeypatch: pytest.MonkeyPatch)
4545
monkeypatch.setattr(
4646
mod,
4747
"CODEX_MODEL_ALIASES",
48-
{"sonnet": "gpt-5.4", "opus": "BOGUS-MODEL"},
48+
{"sonnet": "gpt-5.5", "opus": "BOGUS-MODEL"},
4949
)
5050
result = mod._check_codex_model_alias_staleness()
5151
assert result.severity == Severity.WARNING

tests/core/test_backend_dataclasses.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def test_backend_module_all_exhaustive():
197197
"CODEX_EFFORT_MAPPING",
198198
"CODEX_MODEL_ALIASES",
199199
"CODEX_MODEL_ALIASES_LAST_VERIFIED",
200+
"CODEX_VALID_MODEL_IDS",
200201
"CmdOrigin",
201202
"CmdSpec",
202203
"ModelTranslation",
@@ -205,6 +206,7 @@ def test_backend_module_all_exhaustive():
205206
"CodexEventData",
206207
"SessionEvent",
207208
"AgentSessionResult",
209+
"is_valid_codex_model_id",
208210
"model_class",
209211
"strip_context_window_suffix",
210212
}

tests/execution/backends/test_codex_config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,10 @@ def test_preserves_foreign_sections_with_dotted_keys(self, tmp_path):
499499
def test_preserves_nested_sections_with_special_keys(self, tmp_path):
500500
p = tmp_path / "config.toml"
501501
p.parent.mkdir(parents=True, exist_ok=True)
502-
p.write_text('[notice]\nmodel_migrations = {"gpt-5.3-codex" = "gpt-5.4"}\n')
502+
p.write_text('[notice]\nmodel_migrations = {"gpt-5.3-codex" = "gpt-5.5"}\n')
503503
ensure_codex_mcp_registered(config_path=p)
504504
data = _read_codex_config(p).data
505-
assert data["notice"]["model_migrations"]["gpt-5.3-codex"] == "gpt-5.4"
505+
assert data["notice"]["model_migrations"]["gpt-5.3-codex"] == "gpt-5.5"
506506

507507
def test_updates_stale_tool_timeout(self, tmp_path):
508508
p = tmp_path / "config.toml"
@@ -742,7 +742,7 @@ def test_round_trip_key_with_dot(self):
742742
assert parsed == original
743743

744744
def test_round_trip_key_with_multiple_dots(self):
745-
original = {"notice": {"model_migrations": {"gpt-5.3-codex": "gpt-5.4"}}}
745+
original = {"notice": {"model_migrations": {"gpt-5.3-codex": "gpt-5.5"}}}
746746
serialized = _serialize_toml(original)
747747
parsed = tomllib.loads(serialized)
748748
assert parsed == original

tests/execution/backends/test_model_translation.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,14 @@ def test_suffix_stripping(self) -> None:
200200
assert model_class("sonnet[1m]") == "sonnet"
201201
assert model_class("haiku[1m]") == "haiku"
202202

203-
def test_codex_reverse_map(self) -> None:
203+
def test_codex_shared_model_id_stays_native_when_class_is_ambiguous(self) -> None:
204204
from autoskillit.core import model_class
205205

206-
assert model_class(CODEX_MODEL_ALIASES["opus"]) == "opus"
207-
assert model_class(CODEX_MODEL_ALIASES["sonnet"]) == "sonnet"
208-
assert model_class(CODEX_MODEL_ALIASES["haiku"]) == "haiku"
206+
shared_model_id = CODEX_MODEL_ALIASES["opus"]
207+
assert {CODEX_MODEL_ALIASES[key] for key in ("sonnet", "opus", "haiku")} == {
208+
shared_model_id
209+
}
210+
assert model_class(shared_model_id) == shared_model_id
209211

210212
def test_unknown_passthrough(self) -> None:
211213
from autoskillit.core import model_class

tests/execution/test_model_alias_registry.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
pytestmark = [pytest.mark.layer("execution"), pytest.mark.small]
88

9-
VALID_CODEX_MODEL_IDS: frozenset[str] = frozenset({"gpt-5.4", "gpt-5.4-mini", "gpt-5.5"})
10-
119
VALID_CLAUDE_MODEL_IDS: frozenset[str] = frozenset({"sonnet", "opus", "haiku"})
1210

1311

@@ -27,12 +25,12 @@ def test_anomaly_detection_aliases_keys_match_shared() -> None:
2725

2826

2927
def test_codex_alias_values_in_allowlist() -> None:
30-
from autoskillit.core.types._type_backend import CODEX_MODEL_ALIASES
28+
from autoskillit.core.types._type_backend import CODEX_MODEL_ALIASES, is_valid_codex_model_id
3129

3230
for key, value in CODEX_MODEL_ALIASES.items():
33-
assert value in VALID_CODEX_MODEL_IDS, (
34-
f"CODEX_MODEL_ALIASES[{key!r}] = {value!r} is not in VALID_CODEX_MODEL_IDS. "
35-
"Update VALID_CODEX_MODEL_IDS if the intended target model changed."
31+
assert is_valid_codex_model_id(value), (
32+
f"CODEX_MODEL_ALIASES[{key!r}] = {value!r} is not a valid Codex model ID. "
33+
"Update CODEX_VALID_MODEL_IDS if the intended target model changed."
3634
)
3735

3836

0 commit comments

Comments
 (0)