Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/architecture/upstream-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,9 @@ endpoint exists has three independent guards:
3. **`agent_models` default is empty.** Slice 2's
`PipelineConfig.agent_models` field defaults to `{}`, and the
repository-level `default_agent_model` defaults to `None`.
Without operator action, refine/plan phase spawns resolve to the
built-in `"fable"` Claude path; every other spawn resolves to
`"opus"`. Both use `upstream="anthropic"`.
Without operator action, all spawns resolve to the built-in
`"opus"` Claude path (fable is no longer the default, though still
opt-in selectable). All use `upstream="anthropic"`.

Any one of the three suffices to keep the LiteLLM client cold on a
given deployment. All three are independent: a misconfiguration on
Expand Down
30 changes: 10 additions & 20 deletions docs/guides/per-agent-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ PipelineConfig(

Default-constructed `PipelineConfig.agent_models` is `{}` — every
existing pipeline continues to spawn every role on the built-in
Claude default with `upstream="anthropic"` (`"fable"` for the
refine/plan phase roles, `"opus"` for everything else).
Claude default with `upstream="anthropic"` (`"opus"` for all roles —
fable is no longer the built-in default, though it remains opt-in
selectable via `agent_models` / `default_agent_model`).

### Repository-level default — `default_agent_model`

Expand Down Expand Up @@ -102,20 +103,11 @@ in `orchestrator/agent_model_resolution.py` walks the chain:
1. `pipeline_config.agent_models.get(role.value)` — per-pipeline,
per-role override
2. `get_default_agent_model(repo)` — repository-level default
3. Built-in default — `"fable"` for the refine and plan phase roles
(producers and reviewers, `_FABLE_DEFAULT_ROLES`), `"opus"` for
everything else

> **Minimum sandbox Claude Code version.** The `fable` / `fable[1m]`
> aliases require Claude Code ≥ 2.1.170. The sandbox's
> `CLAUDE_CODE_VERSION` build-arg in `sandbox/Dockerfile` defaults to
> `latest` (#3137 — `stable` lagged the fable launch and crash-looped
> refine/plan agents per #3136). A build-time gate in the Dockerfile
> fails the image build if the installed binary doesn't know `fable`
> or `opus`, so a stale pin surfaces at build time rather than at
> spawn. If you need to pin to an older `CLAUDE_CODE_VERSION` that
> predates the alias, also set a repo-level `default_agent_model: opus`
> AND temporarily drop `fable` from the Dockerfile gate.
3. Built-in default — `"opus"` for all roles. Fable is no longer the
built-in default (it remains opt-in selectable via `agent_models` /
`default_agent_model`); the refine/plan branch
(`_FABLE_DEFAULT_ROLES`) now resolves to opus alongside everything
else.

The result is an `AgentModelDecision` dataclass with fields
`(claude_code_alias: str, upstream: str, upstream_model: str | None,
Expand Down Expand Up @@ -689,8 +681,7 @@ optional `branch` override:

Per-pipeline `agent_models` entries **override** the repo-level
`default_agent_model`. Both can be unset — the resolver falls back to
the built-in default (`"fable"` for refine/plan roles, `"opus"`
otherwise).
the built-in default (`"opus"` for all roles).

### 4. Run the pipeline and observe routing

Expand Down Expand Up @@ -768,8 +759,7 @@ invariant](../architecture/upstream-routing.md#no-op-by-default-invariant)):
3. **`agent_models` default is empty.** Both
`PipelineConfig.agent_models` and repo-level
`default_agent_model` default to nothing — the resolver returns
the built-in Anthropic path (`"fable"` for refine/plan roles,
`"opus"` otherwise).
the built-in Anthropic path (`"opus"` for all roles).

Any single guard suffices. All three are independent; a
misconfiguration on one does not silently activate the LiteLLM path.
Expand Down
18 changes: 9 additions & 9 deletions sandbox/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,11 @@ RUN if [ "$CLAUDE_AGENT_SDK_VERSION" = "latest" ]; then \
# Install Claude Code CLI (native installer) for the egg user
#
# Channel choice: 'latest', not 'stable'. orchestrator/agent_model_resolution.py
# defaults refine/plan roles to bare model-family aliases at launch ('fable'),
# and the stable channel lags those launches — at the time of #3136 stable
# (2.1.153) predated the fable alias entirely, so a stable-channel image
# crash-loops every refine/plan agent at spawn.
# spawns agents on bare model-family aliases ('opus' is the built-in default;
# 'fable' remains opt-in selectable per #3197), and the stable channel lags
# new family launches — at the time of #3136 stable (2.1.153) predated the
# fable alias entirely, so a stable-channel image crash-loops any agent
# spawned on an alias it doesn't know.
ARG CLAUDE_CODE_VERSION=latest
# Cache-bust (#3136): BuildKit re-checks this URL's content on every build, so
# the install layer below re-runs exactly when the release channel moves to a
Expand Down Expand Up @@ -285,8 +286,8 @@ RUN su - egg -c "curl -fsSL https://claude.ai/install.sh | bash -s -- $CLAUDE_CO
# Save installed version for update checks (non-fatal if this fails)
(/home/egg/.local/bin/claude --version 2>/dev/null | head -1 > /home/egg/.local/VERSION || true) && \
# Fail the build if the installed build predates a model family that
# orchestrator/agent_model_resolution.py uses as a built-in default
# ('fable' for refine/plan roles, 'opus' for everything else): a Claude
# orchestrator/agent_model_resolution.py can spawn agents on ('opus', the
# built-in default; 'fable', opt-in selectable per #3197): a Claude
# Code build whose alias table lacks the family rejects the bare alias at
# session init and the agent crash-loops at spawn (#3136). No auth is
# mounted at build time, so this is a strings-level heuristic — a build
Expand All @@ -295,13 +296,12 @@ RUN su - egg -c "curl -fsSL https://claude.ai/install.sh | bash -s -- $CLAUDE_CO
for family in fable opus; do \
if ! grep -aq "claude-${family}-" /home/egg/.local/bin/claude; then \
echo "ERROR: installed Claude Code ($(cat /home/egg/.local/VERSION 2>/dev/null)) does not know the '${family}' model family."; \
echo "agent_model_resolution.py uses '${family}' as a built-in default — agents would crash-loop at spawn (#3136)."; \
echo "agent_model_resolution.py can spawn agents on '${family}' — agents would crash-loop at spawn (#3136)."; \
echo "Pass a newer build: docker build --build-arg CLAUDE_CODE_VERSION=<version|latest> ..."; \
echo "If pinning to an older CC build is intentional (and every"; \
echo "agent_models entry has been pinned away from '${family}'),"; \
echo "drop '${family}' from the 'for family in ...' list above to"; \
echo "skip the gate for that family (matches the bypass guidance"; \
echo "in docs/guides/per-agent-models.md)."; \
echo "skip the gate for that family."; \
exit 1; \
fi; \
done && \
Expand Down
Loading