Config surface + fallback ladder + docs alignment overhaul - #106
Merged
Conversation
Sonnet's _get_capabilities branch never set max_output_tokens, silently inheriting the ModelCapabilities dataclass default of 64000. Per Anthropic (platform.claude.com/en/docs/build-with-claude/context-windows, verified 2026-08-29): "A single request to any model with a 1M-token context window can generate up to 128k output tokens." Sonnet 4.6+ has 1M context (supports_1m=is_46_plus) and is therefore entitled to 128K output -- the owner's live 'sonnet' instance (claude-sonnet-5) was clamped to half its real output capacity by this bug. Fix: max_output_tokens=128000 if is_46_plus else 64000, mirroring the opus branch's existing pattern. Test: T-C03 (test_sonnet_46_plus_max_output_tokens_is_128k) -- fails before (asserts 64000 today), passes after. Part of the config-surface + fallback-ladder + docs-alignment overhaul. Spec: anthropic-surface-spec.md C-03 (commit 1/9). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Seven numeric config keys (max_tokens, priority, timeout,
overloaded_delay_multiplier, throttle_threshold, throttle_delay,
max_concurrent_requests) were read straight off self.config.get() with
no coercion. The warn-and-default helpers (_config_int/_config_float)
already existed and were already used elsewhere in this same
constructor -- these seven were simply missed:
- max_tokens/priority/timeout stayed silent strings ('8192', '1',
'600') all the way toward the wire.
- overloaded_delay_multiplier/throttle_threshold/throttle_delay/
max_concurrent_requests RAISED ValueError at mount on a typo'd
string ('ten', '2%', 'fast', 'many'), killing the whole provider
instance instead of warning and using a safe default.
Also coerces temperature (D-06, owner-adjudicated Q-4 = yes): it
was the 8th numeric key, left uncoerced, flowing to the wire via
extra_body where Anthropic's JSON parser would reject a string.
Leaving exactly one numeric key uncoerced was flagged as the kind of
inconsistency someone "fixes" later without knowing it was deliberate
-- so it is coerced with the same helper as the signed seven.
New tests (tests/test_config_surface.py): T-D08 (invalid numeric
string warns + defaults, never raises -- fails before on 4 of 7 keys),
T-D09 (numeric string coerces to real int/float -- fails before on all
7, all staying str), plus max_tokens- and passthrough-specific
variants. 18 new tests total.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md D-05/D-06 (commit 2/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… messages
Ports the config-surface pattern from provider-openai's v2 overhaul
(config-surface-v2-openai): a module-level allowlist of every config key
this provider actually reads (_CONSUMED_CONFIG_KEYS, 41 keys -- audited
against every self.config.get() call site in BOTH the constructor and
the deferred request path, since 8 keys are read only in _build_params /
_build_web_search_tool and a constructor-only audit would false-positive
on every extended-thinking or web-search user), plus:
- _INERT_CONFIG_KEY_MESSAGES: targeted, actionable warnings for keys
that are recognized but currently do nothing. `debug` and `raw_debug`
are ghosts -- README.md's "Debug Configuration" section documented
both for as long as that section has existed, but neither was ever
implemented (grep for them across __init__.py returns zero matches).
Anyone who followed the README has them set and got silence; now
they get a warning naming the real key (`raw`).
- _DEPRECATED_ALIAS_CONFIG_KEYS + a two-case warning block: `effort`
(the legacy alias for `reasoning_effort`) now warns even when used
ALONE, not just when both keys are set -- previously an `effort`-only
config never learned it should migrate.
- _warn_unknown_config_keys(): the generic sweep, with a difflib
did-you-mean suggestion, run after all config is read.
- EXTRA_KNOWN_CONFIG_KEYS (ClassVar, empty by default): a zero-cost
subclass extension point. provider-openai needed to retrofit this
after the fact (its own sweep shipped without it first) -- adding it
now avoids that repeat.
No config keys are removed in this commit (per the release's revert
safety: fallback_sonnet_model/fallback_haiku_model/refusal_fallback_model
remain fully consumed until the fallback-ladder commits that replace
them land, later in this series).
New tests (tests/test_config_surface.py): T-D01 (deferred keys present
in the allowlist), T-D02 (the sweep produces zero false positives on
the owner's 5 real settings.yaml instances -- shared OWNER_LIVE_CONFIGS
fixture reused by the full migration acceptance test later in this
series), T-D03 (did-you-mean), T-D04/T-D05 (targeted inert-key
messages, single and double), T-D06 (effort alias: alone -> deprecated,
with reasoning_effort -> both-set, never both), T-D07 (subclass
extension point). 12 new tests.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md D-01..D-04 (commit 3/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… routing Ports the extra_request_params pattern from provider-openai's v2 overhaul, adapted for Anthropic's typed SDK surface. Settings-only (never a ConfigField), merged into params LAST so it overrides every provider-computed value -- deliberately, since whoever sets this owns the consequences. The Anthropic wrinkle: unlike provider-openai (whose Responses API client tolerates extra kwargs), an unrecognized keyword here raises "got an unexpected keyword argument", which _do_complete's catch-all translates into a RETRYABLE KernelLLMError -- so a permanent config typo would be retried max_retries times before surfacing. This module already carries the scar (see the _WIRE_ONLY_PARAMS comment for `temperature`/`speed`). So extra_request_params routes PER KEY: known typed params land on the typed surface, everything else goes to extra_body. The merge runs BEFORE _route_wire_only_params, so a user-supplied `temperature` still ends up relocated to extra_body by the same router that handles the provider's own -- merging after it would reintroduce the exact bug this avoids. Deviation from the draft spec, found by this commit's own SDK-contract test: the spec's _TYPED_REQUEST_PARAMS list included `top_k`, `top_p`, and `betas`, none of which are present on the installed SDK's typed surface (anthropic==1.0.0, this repo's floor pin) -- verified via inspect.signature(AsyncMessages.create), not assumed. They met the same fate as `temperature`: dropped from the typed Messages surface in the 1.0.0 major bump. Listing them as "typed" would have routed a config value onto the typed surface and reproduced the exact unexpected-keyword-argument retry bug this allowlist exists to prevent. Replaced with the four typed params the installed SDK actually has that the draft list missed: `cache_control`, `container`, `inference_geo`, `user_profile_id`. Guarded going forward by test_sdk_contract.py::TestTypedRequestParamsMatchSdkSignature, which fails loud on any future SDK drift instead of silently trusting the list. Also adds `extra_request_params` to _CONSUMED_CONFIG_KEYS (D-01/D-04) -- omitting it would have made every user of this new key see a false "Unknown config key" warning from the sweep landed in commit 3. New tests (tests/test_config_surface.py): T-D10 (typed key lands on typed surface), T-D11 (unknown key routes to extra_body), T-D12 (temperature still routes to extra_body even via the escape hatch), T-D13 (override warns exactly once across primary + continuation calls, not once per call), T-D14 (non-mapping value raises ValueError at mount, naming settings.yaml). New test (tests/test_sdk_contract.py): T-D15 (SDK contract -- every _TYPED_REQUEST_PARAMS name binds to the installed SDK's create() signature). 6 new tests. Part of the config-surface + fallback-ladder + docs-alignment overhaul. Spec: anthropic-surface-spec.md D-07 (commit 4/9). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Replaces the hardcoded if/elif fallback-target resolution with an
explicit successor map (_FALLBACK_NEXT_FAMILY) walked downward from the
requested model's family, skipping any unusable rung instead of
terminating the whole ladder there. Fixes three defects, reproduced
against a fresh clone before this change:
- D-1: fable (and any non-opus/sonnet family) returned None --
completely dead. The owner's live `fable` instance has fallback
fully configured (fallback_on_overload, retry_count, cooldown,
both target models) and NONE of it did anything. This is the
release's headline fix: fable -> opus -> sonnet -> haiku all now
resolve.
- D-2: a blank or self-referential target terminated the ladder
instead of walking to the rung below it.
- D-3: targets were hardcoded config-default strings
(claude-sonnet-4-6) that go stale the moment a newer model ships.
Target resolution is now three-source: `fallback_models` config
override (settings-only, per-family map, replacing the two deleted
scalar keys) -> a live "newest model per family" cache warmed for
free as a side effect of any list_models() call (or once,
best-effort, healthy-path-only, via _warm_family_latest) ->
_STATIC_FALLBACK_MODELS backstop, which always resolves and can
never silently die the way `fable` did.
Also lands, in the same commit (interlocked with the ladder):
- B-02/X-2: `mythos` added to _detect_family and _get_capabilities.
claude-mythos-5 previously fell through to "sonnet" (wrong
capability row, wrong context window, wrong ladder position, wrong
fallback target) -- Anthropic ships Mythos 5 as a top-tier,
1M-context, 128K-output family priced identically to Fable 5.
Enters the ladder as fable's PEER (both step down to opus), not
between fable and opus.
- B-08: a mount-time LOUD warning when fallback_on_overload is
enabled on an instance whose default_model is already the ladder's
terminal rung (haiku) -- there is no lower tier, so the setting has
no effect. The wizard already hides this ConfigField for Haiku, so
this specifically catches hand-edited settings.yaml.
- B-09: corrected defaults. fallback_retry_count 1 -> 2,
fallback_cooldown_seconds 1800.0 -> 300.0 -- matching what 4 of the
owner's 5 real instances already set explicitly by hand (a
30-minute cooldown after a transient capacity blip stranded a whole
session on a downgraded model long after real capacity returned).
- C-04: `fallback_sonnet_model` / `fallback_haiku_model` retired.
Superseded by `fallback_models` (a per-family map: `{opus:
claude-opus-4-8, sonnet: ..., haiku: ...}`). Both keys move to
_INERT_CONFIG_KEY_MESSAGES with a targeted migration message naming
the replacement -- the four owner instances that set them get an
actionable warning at mount, not silence. Their ConfigField wizard
entries are removed later in this series (A-08), once the wizard
slims to 5 fields.
New tests (tests/test_fallback_ladder.py, new file): T-B01/T-B02 (fable
resolves and the full 4-rung walk), T-B03 (haiku terminal, regression
guard), T-B04 (an unusable/self-referential rung is skipped, not
fatal -- the actual D-2 fix; plus a companion test showing a truly
*blank* override falls through to the static backstop rather than being
treated as a disabled rung), T-B05/T-B06 (fallback_models override and
live-cache precedence), T-B07/T-B08 (_warm_family_latest error
swallowing and at-most-once), T-B12 (terminal-family mount warning),
T-B13/T-B14 (mythos family detection and capabilities), T-B17 (cycle
guard), T-B18 (corrected defaults). 20 new tests.
New test (tests/test_migration_owner_instances.py, new file): the
release's actual migration merge gate -- mounts each of the owner's 5
real settings.yaml instances verbatim and asserts ONLY the expected
targeted inert-key warnings fire (fallback_sonnet_model /
fallback_haiku_model where set) and zero generic unknown-key
warnings. Extended in a later commit once refusal_fallback_model also
retires.
Updated tests (tests/test_fallback.py): three existing tests asserted
the OLD hardcoded fallback target (`claude-sonnet-4-6`, the previous
config default). The ladder's static backstop is now
`claude-sonnet-5` (the newest GA sonnet as of 2026-08-29) --
test_529_opus_overload_falls_back_to_sonnet,
test_persisted_breaker_state_is_used_by_fresh_provider, and
test_429_overloaded_body_also_triggers_fallback (whose 429-triggers-fallback
*logic* is inverted in the next commit; only its expected model id is
fixed here) updated to match.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md B-01..B-04, B-08, B-09, C-04, B-02/X-2
(commit 5/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…fusal fallback follows the same ladder
**B-06 -- 429 is not overload.** `_is_overload_fallback_error` classified
a 429 as an overload trigger when its body happened to contain the
substring "overload"/"overloaded". Per Anthropic's error docs
(platform.claude.com/docs/en/api/errors, verified 2026-08-29): a 429 is
`rate_limit_error` -- "Your organization has hit a rate limit, reached
its usage tier's monthly spend cap, or reached a spend limit" -- i.e.
PER-ACCOUNT. A lower-tier model draws on the SAME org quota, so
downgrading cannot relieve it; it only hides the real fix (back off,
ramp up gradually, or raise the quota). The substring branch is deleted
entirely -- a 429 now always falls through to the full retry budget on
the SAME model (exponential backoff honoring retry-after), which is the
documented correct response. No new code path was required; deleting
the 429 branch is sufficient.
**B-07 -- refusal fallback: owner-adjudicated Q-1, option (a).**
Decision: the fable->opus->sonnet->haiku downgrade ladder now applies to
BOTH fallback types -- overload AND refusal. Rationale (owner's, recorded
here verbatim): (1) Anthropic's own guidance for a refusal is to retry
with a less-restrictive model, i.e. downward, not up to the most
expensive tier; (2) there is a product expectation that fallback never
lands on a model MORE expensive than the one the user selected -- which
the old hardcoded refusal-escalation target (always claude-opus-4-8)
actually violated for sonnet/haiku users. This is not a pure regression:
inspection shows both `opus` instances' refusal fallback was ALREADY
silently dead under the old chain (opus -> claude-opus-4-8 -> same
family -> None).
`refusal_fallback_model` (the explicit hardcoded-escalation override) is
RETIRED ENTIRELY -- not merely defaulted to None with an override path
retained. `_refusal_fallback_target` now delegates straight to
`_fallback_target_for_model`, using the EXACT SAME three-source
resolution as overload fallback (fallback_models override -> live
list_models cache -> static backstop, skipping unusable rungs).
`refusal_fallback_enabled` remains the gate, default unchanged. Haiku
refusal is now terminal -- the refusal surfaces normally, exactly as a
haiku response always has. Thinking-block stripping on the refusal
retry (`_strip_thinking_blocks`) is unchanged and still runs -- still
required cross-model. The key moves to _INERT_CONFIG_KEY_MESSAGES with
a targeted migration message naming the replacement mechanism.
Deviation from the draft spec, called out explicitly per task
instruction: the spec's own text for "option (a)" retained
`refusal_fallback_model` as a still-functional override that "wins" over
the ladder (so an owner could set it on sonnet/haiku to preserve the old
escalation behavior). The owner's adjudication supersedes that: the key
is removed entirely, with no override path -- refusal fallback follows
the ladder unconditionally once refusal_fallback_enabled is true. This
also means T-B16 as originally specified ("explicit refusal_fallback_model
still wins over the ladder") does not apply; it is replaced with tests
asserting the key is retired and inert, and that refusal fallback uses
the same fallback_models-based resolution as overload.
New/updated tests: tests/test_fallback_ladder.py gains T-B15 (refusal on
opus resolves to the ladder's claude-sonnet-5 target, not the old
claude-opus-4-8, and not None). tests/test_fallback.py's
`test_429_overloaded_body_also_triggers_fallback` is INVERTED to
`test_429_overloaded_body_does_not_trigger_fallback` -- every attempt now
stays on the same model. tests/test_refusal_fallback.py rewritten:
the fallback-target assertion updates from the hardcoded
claude-opus-4-8 to the ladder's claude-sonnet-5 (fable's actual next
rung); the same-family loop-guard test is rebuilt against a pathological
`fallback_models` override (since the old `refusal_fallback_model`
override no longer exists to construct the scenario with); a new
terminal-rung test confirms haiku refusals have no fallback target; a
new test confirms the refusal ladder shares fallback_models' override
precedence with overload fallback.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md B-06, B-07 (owner-adjudicated Q-1,
option (a) as amended) (commit 6/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Verified against platform.claude.com/en/docs/build-with-claude/context-windows
on 2026-08-29: "For every model with a 1M-token context window, 1M is the
default: you don't need a beta header, and long-context requests are
billed at standard pricing." The string "context-1m" appears zero times
on that page.
C-01: deletes `_should_add_context_1m_beta` entirely (it computed which
models still needed the header, on a version threshold that was already
wrong -- Sonnet 4.5 was included when Anthropic's own docs say it has a
200K window, not 1M), deletes the `BETA_HEADER_1M_CONTEXT` constant, and
deletes the call site in `_build_request_beta_headers`. The now-unused
`model_id` parameter is removed from `_build_request_beta_headers`'s
signature and its one call site (verified via grep that no other caller
depends on it).
C-02/A-03: `enable_1m_context`'s constructor default flips `True` ->
`False`, with the ConfigField's declared default flipped to match in the
same commit (both must change together or the wizard and the
constructor disagree; the ConfigField's prompt text reword is deferred
to the wizard-slimming commit later in this series). A comment at the
assignment site encodes the no-premium-tier invariant so cost modeling
never re-adds a long-context price tier: `enable_1m_context`'s only
remaining effect is the ADVERTISED context window handed to the context
manager (how much history is kept per request), which is a cost
decision, not a capability one -- it no longer changes what the API
will accept or send.
Test changes:
- tests/test_fallback.py: 3 of the 4 existing 1M-beta-header tests are
deleted (they asserted the header's conditional presence, which no
longer exists at all); the 4th
(test_enable_1m_context_does_not_become_global_beta_header) is
RE-POINTED to test_enable_1m_context_never_adds_a_beta_header --
asserting the real invariant (no 1M header, ever, for any value of
enable_1m_context) rather than the narrower "not a GLOBAL header"
claim. A new end-to-end variant confirms no header reaches a live
request either.
- tests/test_model_capabilities.py: TestContextBetaHeaderOpus48 (which
asserted the header's presence threshold) is replaced with
TestContextBetaHeaderNeverSent (the header is absent for every
version, opus included). TestFastModeBetaHeader and
TestSpeedConfigPlumbing updated to drop the now-removed `model_id=`
kwarg from `_build_request_beta_headers` call sites.
- tests/test_opus_47.py: TestBetaHeader1MFix (8 tests asserting the
now-deleted `_should_add_context_1m_beta`'s per-version threshold
behavior) is removed; a comment points to its replacement coverage.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md C-01, C-02, A-03 (default only) (commit 7/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…anual thinking on x.6, drop extended-cache-ttl header
All four items in this commit were gated on live merge-gate probes run
against the real Anthropic API before implementation, per the release
plan -- results recorded below and in the PR body.
**C-05 -- corrected xhigh/max effort gating.** Anthropic's own docs
(platform.claude.com/en/docs/build-with-claude/effort, verified
2026-08-29) place the xhigh/max split at 4.6/4.7, not 4.7/4.8 as the
pre-overhaul code assumed:
- xhigh: Fable 5, Mythos 5, Opus 5/4.8/4.7, Sonnet 5
- max: all of the above PLUS Opus 4.6 and Sonnet 4.6 (the doc's own
explanation: "xhigh is a newer level; some models that support max
don't support xhigh")
Opus branch: `is_47_plus` gets xhigh+max (was is_48_plus for max,
is_47_plus for xhigh-only); Opus 4.6 gains `max`. Sonnet branch:
Sonnet 4.6 gains `max` (previously neither).
**C-06 (scope expansion X-1, owner-adjudicated Q-2 = yes, GATED on
live probe) -- widened supports_output_config.** Was `is_47_plus`
(opus) / `is_5_plus` (sonnet); without this, C-05's `max` addition to
Opus 4.6/Sonnet 4.6 would be INERT (output_config.effort is only
emitted when supports_output_config is True). Doc: "Supported models:
... Opus 4.5, 4.6, 4.7, 4.8, and 5 ... Sonnet 4.6 and 5." Widened to
`is_45_plus` (opus) / `is_46_plus` (sonnet).
MERGE-GATE PROBE (T-C06-live, run against api.anthropic.com,
max_tokens=16): `output_config: {"effort": "max"}` on both
claude-sonnet-4-6 and claude-opus-4-6 -> HTTP 200 on both. PASS --
shipped as specified.
**C-07 -- manual-thinking deprecation tier (owner-adjudicated Q-3 =
CONSERVATIVE, GATED on live probe).** New `manual_thinking_deprecated`
capability field, True for Opus 4.6 and Sonnet 4.6 specifically (the
one version where `supports_manual_thinking` is still True but the API
already considers `type="enabled"` legacy -- 4.7+/5+ already hard-gate
to adaptive via the existing `supports_manual_thinking=False` path, so
the two flags never both apply on the same model). When
`thinking.type="enabled"` reaches the wire on a deprecated-tier model,
`_build_params` now logs a loud deprecation warning naming the adaptive
migration path -- the hard 400-on-4.7+/5+ gate is UNCHANGED (per the
conservative adjudication: the request still sends `type="enabled"` on
4.6, it is not silently coerced).
MERGE-GATE PROBE (T-C08-live, max_tokens=1040, budget_tokens=1024):
`thinking: {"type": "enabled", "budget_tokens": 1024}` on
claude-opus-4-6 -> HTTP 200 (thinking block returned). PASS --
confirms "deprecated but working"; shipped as specified.
**C-08 -- min_cacheable_tokens capability data (C-09: decline the
behavioral gate).** New field on ModelCapabilities, populated per family
from Anthropic's prompt-caching page (verified 2026-08-29): fable/mythos
512 (mythos preview 2048), opus non-monotonic 512(5+)/1024(4.8)/2048(4.7)/
4096(4.6, written as an explicit descending chain, not a >= threshold,
so a future "simplification" doesn't flatten a real non-monotonic API
constraint), sonnet 1024 flat, haiku 4096. Per C-09's recommendation,
deliberately NO client-side gate is added to suppress cache_control
placement below this threshold: the API's own failure mode is already
optimal ("processed without caching, and no error is returned" --
Anthropic docs) at zero cost, while a client-side gate would require
counting tokens before every request to avoid a cost that is already
zero. Data lands for diagnostics/README only.
**C-10 -- extended-cache-ttl-2025-04-11 beta header dropped (GATED on
live probe, two-part).** The header appears zero times on Anthropic's
current prompt-caching docs; the documented mechanism is the `ttl`
field on `cache_control` alone.
MERGE-GATE PROBE (T-C10-live, part A, max_tokens=16, ~8000-char
system block, ttl:"1h", NO beta header): HTTP 200,
usage.cache_creation.ephemeral_1h_input_tokens=4293 (>0),
ephemeral_5m_input_tokens=0 -> the 1h TTL is honored WITHOUT the
header -> header not required.
MERGE-GATE PROBE (part B, same request WITH the header): HTTP 200,
cache_read_input_tokens=4293 (reused the part-A write) -> the header
is also harmless to keep, but is undocumented and unnecessary. Per the
probe's own decision rule (part A PASS -> DROP), the header-append
logic for `cache_stable_region_ttl_1h` is removed entirely; the knob's
behavior (opt-in 1h TTL on system/tool cache breakpoints) is
unchanged.
Test changes (re-baselining the largest mechanical surface in the
release, as flagged in the spec's own risk table):
- tests/test_model_capabilities.py: test_opus_47_no_max_effort ->
test_opus_47_has_xhigh_and_max (inverted); test_opus_5_capabilities_
match_opus_48_gate updated for the one EXPECTED non-monotonic
exception (min_cacheable_tokens: Opus 5=512, Opus 4.8=1024) instead
of whole-object equality; test_sonnet_46_unchanged_by_sonnet5_gate
updated for Sonnet 4.6 now getting `max` + output_config + the
deprecation flag.
- tests/test_opus_47.py: test_opus_46_no_output_config inverted to
test_opus_46_output_config_now_supported; test_opus_47_supported_
efforts and test_opus_46_no_xhigh updated for the corrected xhigh/max
split; test_opus_47_invalid_effort_omits_output_config re-targeted at
a value outside ANY model's supported_efforts (since 'max' -- its old
example -- is now valid on Opus 4.7); test_task_budget_ignored_on_
unsupported_model updated to assert the task_budget sub-key is absent
rather than output_config as a whole (which now legitimately exists).
- tests/test_reasoning_effort.py: test_opus_47_max_still_logs_
original_gate_b_warning_only inverted to test_opus_47_max_now_
supported_no_gate_b_warning.
- tests/test_prompt_cache_breakpoints.py: updated to assert the
extended-cache-ttl header is ABSENT.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md C-05, C-06 (X-1/Q-2), C-07 (Q-3), C-08,
C-09, C-10 (commit 8/9).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Wizard slims from 12 ConfigFields to 5: api_key, base_url, enable_1m_context,
reasoning_effort, cache_stable_region_ttl_1h (A-01..A-08). "Demoted" fields
(enable_prompt_caching, fallback_on_overload, fallback_retry_count,
fallback_cooldown_seconds, persist_fallback_state) keep working exactly as
before -- settings-only, still in _CONSUMED_CONFIG_KEYS, never trigger an
unknown-key warning. fallback_sonnet_model/fallback_haiku_model were already
removed in the fallback-ladder commit (C-04, this only removes their
ConfigField declarations, A-08).
Reworded prompts (one-liners replacing multi-sentence explanations, with the
detail moved to README.md): api_key ("Anthropic API key"), enable_1m_context
("Use the full 1M-token context window (more context kept per request =
higher cost)"), reasoning_effort ("Reasoning effort -- higher is smarter,
slower, costlier"), cache_stable_region_ttl_1h ("1-hour cache TTL -- 2x write
cost, fewer writes"). cache_stable_region_ttl_1h's show_when is dropped (A-04):
enable_prompt_caching is no longer a ConfigField, so the condition could never
be satisfied in the wizard; the inert combination is already handled loudly by
the constructor guard.
tests/test_validation.py: TestFallbackConfigFields rewritten -- the ConfigField
presence assertions invert (fields are now absent, keys remain functional
settings-only); a new test confirms fallback_sonnet_model/fallback_haiku_model
produce their targeted removal messages. tests/test_prompt_cache_breakpoints.py
updated for the dropped show_when.
README overhaul (E-01..E-05): deleted the `debug`/`raw_debug` ghost keys from
the config example and their entire "Debug Configuration" section (neither was
ever implemented -- grep confirms zero matches in __init__.py; the README
documented them in error), replaced with the real `raw` key. Fixed the
retry_jitter documentation contradiction: the README recommended
`retry_jitter: 0.2`, but `_config_bool` parses `0.2` as `false` -- the exact
opposite of the documented intent. The code is correct (retry_jitter is a
boolean; a float form has never worked in any released version); the docs are
corrected. Deleted the stale manual-beta-header 1M section (it instructed
setting `beta_headers: "context-1m-2025-08-07"` on a model that per Anthropic's
own docs has a 200K window, and a header no longer in Anthropic's
documentation -- actively harmful advice), replaced with the GA/no-header
explanation. Added a house-style key-reference table covering every surviving
key (wizard vs settings-only), plus the two retired keys' migration note.
Part of the config-surface + fallback-ladder + docs-alignment overhaul.
Spec: anthropic-surface-spec.md A-01..A-08, E-01..E-05 (commit 9/9, final).
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
test_readme_retry_docs.py read README.md via Path.read_text() with no encoding, which uses the OS default (cp1252 on Windows) instead of UTF-8. The README's em-dash/multiplication-sign characters this PR adds (retry_jitter row, cache-TTL prompts) cannot be decoded as cp1252, failing the whole file's collection on windows-latest CI with UnicodeDecodeError. Per IMPLEMENTATION_PHILOSOPHY.md: always specify encoding="utf-8" for cross-platform file I/O. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Collaborator
Author
|
Self-authored PR, landing per maintainer admin pattern (author is repo maintainer). All CI checks green (license/cla + 6-way pytest matrix). Merging via squash. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the config-surface + fallback-ladder + docs-alignment overhaul per
anthropic-surface-spec.md(verified againstmain@46d4869), landed as 9 independently-revertible commits.Owner adjudications applied (override spec's open questions):
refusal_fallback_modelis removed entirely (not retained as an override, per owner direction) — refusal fallback resolves via the samefallback_models/live-cache/static-backstop precedence as overload. Rationale: Anthropic's guidance for a refusal is to retry with a less-restrictive (cheaper) model, and fallback should never land on something MORE expensive than the user's selection — which the old hardcoded Opus-escalation violated for sonnet/haiku users.refusal_fallback_enabledremains the gate; haiku refusal is terminal; thinking-block stripping is preserved.supports_output_configto Opus 4.5+/Sonnet 4.6+.thinking.type=enabled→ HTTP 200) — hard 400-gate unchanged (4.7+/5+ only); added a deprecation warning for the 4.6 "still works" tier.temperaturealso coerced via the numeric warn-and-default helper.mythosfamily added to_detect_family; min-cacheable-length behavioral gate declined (capability data + comment only).Live merge-gate probes (run against api.anthropic.com, tiny spend)
output_config.effort="max"onclaude-sonnet-4-6andclaude-opus-4-6, max_tokens=16thinking:{type:"enabled",budget_tokens:1024}onclaude-opus-4-6, max_tokens=1040cache_control:{type:"ephemeral",ttl:"1h"}on ~8000-char system block, no beta headerephemeral_1h_input_tokens=4293>0— header not requiredanthropic-beta: extended-cache-ttl-2025-04-11GET /v1/modelsDeviations from the draft spec (surfaced, not silently improvised)
refusal_fallback_modelas a still-functional override. Per explicit owner instruction for this task, the key is removed entirely with no override path. T-B16 (as literally specified: "explicit override still wins") does not apply under this adjudication; replaced with tests asserting the key is retired/inert and that refusal fallback shares overload's target resolution._TYPED_REQUEST_PARAMScomposition — the draft spec's list includedtop_k,top_p,betas, none of which are present on the installed SDK's typedcreate()signature (anthropic==1.0.0, this repo's floor pin) — verified viainspect.signature, not assumed. Replaced with the four typed params the installed SDK actually has that the draft list missed (cache_control,container,inference_geo,user_profile_id). Guarded going forward by a new SDK-contract test that fails loud on future SDK drift.list_models()on this account, so live ID-shape confirmation forclaude-mythos-5could not be completed. Family classification and capabilities are covered by unit tests and the spec's own Anthropic-doc citations (platform.claude.com/en/docs/build-with-claude/effort and /context-windows, 2026-08-29) but not a fresh live listing.Test suite
main@46d4869): 695 passedpython_checkon touched files: no new errors/warnings vs. baseline (same 15 pre-existing errors / 18 warnings, unrelated to this change — repo CI does not gate on ruff/pyright, onlypytest -q)Migration impact (owner's 5 live settings.yaml instances)
Covered by
tests/test_migration_owner_instances.py(parametrized over the 5 real config shapes from the spec's §7): zero false-positive unknown-key warnings; only the expected targetedfallback_sonnet_model/fallback_haiku_modelremoval messages fire where those instances set them (opus-4.8, opus, sonnet, fable). Thefableinstance's fallback goes from completely dead to fully functional (fable→opus→sonnet→haiku) — the release's headline fix. Thesonnetinstance'smax_output_tokensgoes from 64K to 128K (was silently clamped to half its real ceiling).🤖 Generated with Amplifier