Skip to content

fix: read the model tiers from the published policy instead of restating them - #76

Merged
stxkxs merged 1 commit into
mainfrom
fix/invoke-current-model-tier
Jul 30, 2026
Merged

fix: read the model tiers from the published policy instead of restating them#76
stxkxs merged 1 commit into
mainfrom
fix/invoke-current-model-tier

Conversation

@stxkxs

@stxkxs stxkxs commented Jul 30, 2026

Copy link
Copy Markdown
Member

Three copies of the Claude model id lived in this repo — every role's model, the advisor escalation, and the LLM policy prose fed to every factory agent — with nothing comparing them. The prose copy had drifted to anthropic.claude-sonnet-4-6.

That id cannot be invoked. Bedrock reports inferenceTypesSupported: [INFERENCE_PROFILE] for the whole current Claude family, so a bare anthropic.-prefixed id is refused with a ValidationException on the first call. Every factory agent was being instructed to build against a form that does not work.

One source

llm-policy.json joins language-toolchain.json as a vendored, digest-pinned copy of the published standard. LLM_MODELS, MODEL_TIERS, and the LLM_POLICY prose are all read from it, and ADVISOR_MODEL reads MODEL_TIERS.escalation.

Rendering the standard's requirements gained one the hand-written blob never had: inference-profile-required — the rule the blob was breaking. Nothing in fab's own prose had said the profile form was mandatory, which is why the drift went unremarked.

The current tier

The roster moves to the Claude 5 family. A role default is an invocation one level of indirection away, so a roster on a previous generation means every factory run infers on a lower tier than the policy names.

BEDROCK_MODEL_IDS gains the two Claude 5 base ids — without them, FAB_INFERENCE=bedrock throws at session start. The suffixes there are irregular and no rule produces them (Opus 5 bare, Opus 4.6 -v1, Haiku 4.5 dated and -v1:0); every value was read off aws bedrock list-foundation-models.

The gate

__tests__/model-policy.test.ts holds the three representations to each other, including that canonicalising a tier then resolving it returns the policy's exact id — the only check that catches a wrong suffix in either table.

Mutation-tested seven ways; each fails the case meant to catch it and no other.

Verification

npm test 484 pass · npm run test:coverage exit 0 · npm run lint exit 0 · npm run format:check clean · npm run standards:check both files match the pin · npm run build bundles both standards into dist/.

…ing them

Every role, the advisor escalation, and the LLM policy prose fed to every
factory agent each carried their own copy of a Claude model id. Three copies,
no comparison between them, and the prose copy had gone wrong: it named
`anthropic.claude-sonnet-4-6` as the default.

That id cannot be invoked. Bedrock reports
`inferenceTypesSupported: [INFERENCE_PROFILE]` for the whole current Claude
family — there is no on-demand path — so a bare `anthropic.`-prefixed id is
refused with a ValidationException on the first call. Every factory agent was
being instructed to build against a form that does not work, and an app built
to that instruction fails on its first Bedrock request.

─────────────────────────── One source ───────────────────────────

`llm-policy.json` joins `language-toolchain.json` as a vendored, digest-pinned
copy of the published standard, and the tiers are read from it:

- `LLM_MODELS` — the standard's `models`, as inference-profile ids.
- `MODEL_TIERS` — the canonical short ids the roster declares, derived from
  `LLM_MODELS` by stripping the profile geo prefix, the `anthropic.` vendor
  prefix, and Bedrock's date/version suffix.
- `LLM_POLICY` — assembled from the standard's own `models`, `regions_preferred`,
  `sdk_by_language` and `requirements` rather than restated.

Rendering the requirements gained one the hand-written blob never had:
`inference-profile-required`, which is the rule the blob was breaking. Nothing
in fab's own prose had said the profile form was mandatory, which is why the
drift went unremarked.

`ADVISOR_MODEL` now reads `MODEL_TIERS.escalation`. It is the escalation tier by
definition, and it was the copy most likely to be missed on a bump — the advisor
is consulted mid-session rather than at deploy time, so a stale id there surfaces
as a failed escalation inside a workflow rather than at startup.

──────────────────────── The current tier ────────────────────────

The roster moves to the Claude 5 family: 78 roles to `claude-sonnet-5`, 2 lab
roles and the advisor to `claude-opus-5`. A role default is an invocation one
level of indirection away, so a roster on a previous generation means every
factory run infers on a lower tier than the policy names.

`BEDROCK_MODEL_IDS` gains the two Claude 5 base ids. Without them a role on
`claude-sonnet-5` throws "No AWS Bedrock model id is mapped" at session start
under `FAB_INFERENCE=bedrock` — the regulated path. The suffixes in that table
are irregular and no rule produces them: Opus 5 and Sonnet 5 are bare, Opus 4.6
carries `-v1`, Haiku 4.5 carries a release date and `-v1:0`. Every value was read
off `aws bedrock list-foundation-models --by-provider anthropic`, and the comment
now says so.

Older entries stay. The table maps a canonical id to a Bedrock id; a caller may
legitimately pin a role to an earlier model, and removing a mapping would break
that rather than protect anything.

──────────────────────────── The gate ────────────────────────────

`__tests__/model-policy.test.ts` holds the three representations to each other:
every role's model is a tier the policy names, no role sits on a bare
foundation-model id, the advisor is the escalation tier, each tier resolves to a
`us.anthropic.` profile id on bedrock and passes through unchanged on the api
backend, and canonicalising a tier then resolving it returns the policy's exact
id — which is the only check that catches a wrong suffix in either table.

Mutation-tested seven ways: a role reverted to the previous generation, a role on
a bare id, a policy bump with the Bedrock mapping forgotten, a wrong `-v1` suffix,
the canonical-id derivation dropping the date suffix, the advisor pinned to a
literal, and the prose reverting to a bare id. Each fails the case meant to catch
it and no other.

A model id is a plain string, so no compiler, linter, or type checker has an
opinion about it. A test is the only thing that can.

Also corrects the bare ids in the kagent-curation skill's ModelConfig example —
invoke position, and the skill is what agents follow to author those configs.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
@stxkxs
stxkxs merged commit 7f2db19 into main Jul 30, 2026
10 checks passed
@stxkxs
stxkxs deleted the fix/invoke-current-model-tier branch July 30, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant