Skip to content

feat(models): category-based cost policy + live catalog/pricing refresh (#1080/#1183)#1445

Open
primetimetank21 wants to merge 5 commits into
bradygaster:devfrom
primetimetank21:primetimetank21/1080-cost-policy
Open

feat(models): category-based cost policy + live catalog/pricing refresh (#1080/#1183)#1445
primetimetank21 wants to merge 5 commits into
bradygaster:devfrom
primetimetank21:primetimetank21/1080-cost-policy

Conversation

@primetimetank21

@primetimetank21 primetimetank21 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What & why

Adds a category-based cost policy for model selection (GitHub AI Credits billing) and a squad models refresh diagnostic that reconciles the committed seed catalog against live sources — now carrying up-to-date pricing alongside the live catalog.

Refs #1080
Refs #1183

Changes

  • Category-based cost policy (packages/squad-sdk/src/agents/model-selector.ts): buildEffectiveCostPolicy + finalizeResolvedModel (downgrade / prune / fail-closed). Two independent axes kept separate — quality tier (premium/standard/fast) vs. GitHub billing category (lightweight/versatile/powerful). No hardcoded pricing.
  • Wired through the agent lifecycle (packages/squad-sdk/src/agents/lifecycle.ts): threads config + session cost policy into resolveModel, and surfaces the outcome loud (EventBus + console.warn) — fail-closed when no compliant model exists. (Addresses the "dead code" gap from the prior feat: add cost policy + refresh model catalog for GitHub AI Credits billing #1089 approach.)
  • squad models refresh (packages/squad-cli/src/cli/commands/models.ts): auth-free-first hybrid sourcing — canonical Copilot models API (via gh auth token, Copilot-Integration-Id: copilot-cli) for the live catalog + category, enriched on the API path with pricing/release_status from the public github/docs models-and-pricing.yml. Display names are normalized algorithmically to catalog ids via normalizeDisplayName — no hand-maintained allow-list, so new models enrich automatically. Parenthetical qualifiers (e.g. (fast mode) (preview)) are kept in the normalized id to prevent pricing collisions between distinct SKUs sharing a base name (e.g. claude-opus-4.8 vs. claude-opus-4.8-fast-mode-preview). tier: Long context threshold rows in the docs YAML are skipped so the Default tier (lower base price) is always used — prevents the long-context row overwriting the canonical per-call pricing. Models with no docs pricing entry emit a visible ⚠ N catalog model(s) have no pricing from docs: <ids> warning rather than silently vanishing; the warning is suppressed when the docs fetch itself fails (avoids misleading "all models unpriced" noise from a network error). Best-effort / fail-open (a docs failure never hard-fails the refresh). Discovered ids are written only to the gitignored .squad/.cache/models.json — never to committed files.

Testing

TDD throughout (red → green). Targeted suites: test/models-refresh.test.ts (27, including normalization unit tests, parenthetical SKU collision-protection tests, Default-tier-wins tests, and docs-fetch-failure suppression tests), test/model-selector-policy.test.ts (11, covering AC5 implicit downgrade, AC6 explicit warn-allow, AC7 chain pruning, AC8 fail-closed, AC9 empty-pruned-chain, AC10 out-of-catalog passthrough, AC11 lifecycle wiring), test/catalog-refresh.test.ts (7). CLI package typecheck clean.

Verified against live endpoints: source: api, 19 models discovered, 17/19 enriched with current pricing; gemini-3.1-pro-preview and mai-code-1-flash-picker are new API models not yet in the docs pricing YAML — correctly surfaced by the unpriced warning rather than silently dropped. claude-opus-4.8 correctly shows $5.00/$25.00 (Default tier). Two-row models (gpt-5.4/5.5/5.6-*) now show Default-tier base pricing — no longer inadvertently showing the higher Long context row price.

📊 Live catalog + pricing (19 models, Default-tier)
model category input / 1M output / 1M status
claude-opus-4.8 powerful $5.00 $25.00 GA
claude-opus-4.7 powerful $5.00 $25.00 GA
claude-opus-4.6 powerful $5.00 $25.00 GA
claude-sonnet-5 versatile $2.00 $10.00 GA
claude-sonnet-4.6 versatile $3.00 $15.00 GA
claude-sonnet-4.5 versatile $3.00 $15.00 GA
claude-haiku-4.5 lightweight $1.00 $5.00 GA
gpt-5.6-sol powerful $5.00 $30.00 GA
gpt-5.6-terra versatile $2.50 $15.00 GA
gpt-5.6-luna lightweight $1.00 $6.00 GA
gpt-5.5 powerful $5.00 $30.00 GA
gpt-5.4 powerful $2.50 $15.00 GA
gpt-5.4-mini lightweight $0.75 $4.50 GA
gpt-5.3-codex powerful $1.75 $14.00 GA
gpt-5-mini lightweight $0.25 $2.00 GA
gemini-2.5-pro powerful $1.25 $10.00 GA
gemini-3.5-flash lightweight $1.50 $9.00 GA
gemini-3.1-pro-preview powerful
mai-code-1-flash-picker versatile

Categories come from the Copilot models API (canonical); pricing + release_status are joined by id from the public github/docs models-and-pricing.yml (Default tier only — Long context rows are skipped). Pricing is diagnostic/reconciliation data written to the gitignored cache — it is not wired into policy enforcement. gemini-3.1-pro-preview and mai-code-1-flash-picker are correctly reported as unpriced (no docs entry); no warning fires on network/fetch failure.

Changeset included (.changeset/feat-cost-policy-1080.md — covers both features, minor bump for @bradygaster/squad-sdk and @bradygaster/squad-cli).

🧪 How to test locally (manual + agent)

How to test locally

All commands are PowerShell-safe. Run from the repo root (C:\...\squad).

Feature 1 — squad models refresh

Step 1: Build both packages

npm run build -w packages/squad-sdk
npm run build -w packages/squad-cli

Step 2: Run the refresh (requires gh auth status to show an active login for the Copilot API path; falls back to public docs YAML automatically if unauthenticated)

node packages/squad-cli/dist/cli-entry.js models refresh

Expected output: Source: Copilot models API (canonical), Discovered: 19 model(s), gpt-5.6-luna/sol/terra in the Added list (first run). gemini-3.1-pro-preview and mai-code-1-flash-picker are new API models with no docs pricing yet — the refresh reports ⚠ 2 catalog model(s) have no pricing from docs: gemini-3.1-pro-preview, mai-code-1-flash-picker. If the docs YAML fetch itself fails, no unpriced warning fires.

Step 3: Verify the key prices (writes/reads/removes a temp .cjs — avoids ESM import complexity and PS backtick escaping)

@'
const a = require("./.squad/.cache/models.json");
const arr = Array.isArray(a) ? a : a.models;
const p = (id) => { const x = arr.find((y) => y.id === id); const inp = x ? (x.input_price_per_million || (x.pricing && x.pricing.input)) : null; const out = x ? (x.output_price_per_million || (x.pricing && x.pricing.output)) : null; console.log(id.padEnd(22), x ? inp + " / " + out : "MISSING"); };
["claude-opus-4.8","gpt-5.5","gpt-5.6-luna","gpt-5.6-sol","gpt-5.6-terra"].forEach(p);
'@ | Set-Content check-prices.cjs; node check-prices.cjs; Remove-Item check-prices.cjs

Expected (Default-tier base prices):

claude-opus-4.8        $5.00 / $25.00
gpt-5.5                $5.00 / $30.00
gpt-5.6-luna           $1.00 / $6.00
gpt-5.6-sol            $5.00 / $30.00
gpt-5.6-terra          $2.50 / $15.00

Step 4: Confirm the working tree stays clean

git status --short

.squad/.cache/models.json is gitignored and will not appear. Any pre-existing untracked noise (.gitattributes, .mcp.json) is unrelated to this PR.

Step 5: Run the models-refresh unit tests

npx vitest run test/models-refresh.test.ts

Expected: Tests 27 passed (27) — includes normalization unit tests, parenthetical SKU collision-protection tests, Default-tier-wins tests, and docs-fetch-failure suppression tests.

Feature 2 — Category-based cost policy

The cost policy is an SDK feature (resolveModel + buildEffectiveCostPolicy) with no CLI surface yet — there is no squad config costPolicy subcommand. The most direct exercise path is the vitest suite:

npx vitest run test/model-selector-policy.test.ts test/lifecycle.test.ts

Expected: Tests 44 passed (44). The suite covers: AC5 implicit downgrade, AC6 explicit override warned-and-allowed, AC7 chain pruning to ceiling, AC8 fail-closed (no compliant model), AC9 empty-pruned-chain graceful degradation, AC10 out-of-catalog passthrough, AC11 end-to-end lifecycle wiring.

For a runnable SDK demonstration (after building), write and run this temp file:

@'
import { resolveModel } from "./packages/squad-sdk/dist/agents/model-selector.js";

// AC5: implicit over-ceiling pick is deterministically downgraded
const downgraded = resolveModel({ taskType: "visual", config: { costPolicy: { maxCategory: "versatile" } } });
console.log("A downgrade action:", downgraded.policy?.action);
console.log("  warning:", downgraded.policy?.warning);

// AC6: explicit user override above ceiling — honored but warned
const explicit = resolveModel({ taskType: "code", userOverride: "claude-opus-4.8", config: { costPolicy: { maxCategory: "lightweight" } } });
console.log("B explicit action:", explicit.policy?.action);
console.log("  model kept:", explicit.model);
console.log("  warning:", explicit.policy?.warning);

// no policy — unchanged behavior
const nopolicy = resolveModel({ taskType: "code" });
console.log("C no policy:", nopolicy.model, "| policy:", nopolicy.policy ?? "none");
'@ | Set-Content check-policy.mjs; node check-policy.mjs; Remove-Item check-policy.mjs

Expected output:

A downgrade action: downgraded-to-ceiling
  warning: Model '...' (powerful) exceeds the cost ceiling 'versatile'; automatically downgraded to '...' (versatile).
B explicit action: warn-allow-explicit
  model kept: claude-opus-4.8
  warning: Explicit model 'claude-opus-4.8' (powerful) exceeds the configured cost ceiling 'lightweight'. Honoring the explicit selection; consider lowering the model or raising the ceiling.
C no policy: claude-sonnet-4.6 | policy: none

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 2611ade

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 4 item(s) to address before review

Status Check Details
Single commit 5 commits — consider squashing before review
Not in draft Ready for review
Branch up to date dev is 8 commit(s) ahead — rebase recommended
Copilot review No Copilot review yet — it may still be processing
Changeset present Changeset file found
Scope clean No .squad/ or docs/proposals/ files
No merge conflicts No merge conflicts
Copilot threads resolved 0 active Copilot thread(s) resolved (2 outdated skipped)
CI passing 6 check(s) still running

Files Changed (13 files, +1632 −7)

File +/−
.changeset/feat-cost-policy-1080.md +27 −0
packages/squad-cli/src/cli-entry.ts +6 −0
packages/squad-cli/src/cli/commands/models.ts +364 −0
packages/squad-sdk/src/agents/index.ts +5 −0
packages/squad-sdk/src/agents/lifecycle.ts +69 −1
packages/squad-sdk/src/agents/model-selector.ts +219 −4
packages/squad-sdk/src/config/models.ts +59 −0
packages/squad-sdk/src/config/schema.ts +4 −0
packages/squad-sdk/src/runtime/config.ts +22 −0
test/config-schema.test.ts +36 −0
test/lifecycle.test.ts +109 −2
test/model-selector-policy.test.ts +161 −0
test/models-refresh.test.ts +551 −0

Total: +1632 −7


This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1445

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 13
Files added 4
Files modified 9
Files deleted 0
Modules touched 4
Critical files 1

🎯 Risk Factors

  • 13 files changed (6-20 → MEDIUM)
  • 4 modules touched (2-4 → MEDIUM)
  • Critical files touched: packages/squad-sdk/src/agents/index.ts

📦 Modules Affected

root (1 file)
  • .changeset/feat-cost-policy-1080.md
squad-cli (2 files)
  • packages/squad-cli/src/cli-entry.ts
  • packages/squad-cli/src/cli/commands/models.ts
squad-sdk (6 files)
  • packages/squad-sdk/src/agents/index.ts
  • packages/squad-sdk/src/agents/lifecycle.ts
  • packages/squad-sdk/src/agents/model-selector.ts
  • packages/squad-sdk/src/config/models.ts
  • packages/squad-sdk/src/config/schema.ts
  • packages/squad-sdk/src/runtime/config.ts
tests (4 files)
  • test/config-schema.test.ts
  • test/lifecycle.test.ts
  • test/model-selector-policy.test.ts
  • test/models-refresh.test.ts

⚠️ Critical Files

  • packages/squad-sdk/src/agents/index.ts

This report is generated automatically for every PR. See #733 for details.

@primetimetank21 primetimetank21 force-pushed the primetimetank21/1080-cost-policy branch from 3d61719 to 66f1c68 Compare July 4, 2026 19:59
@tamirdresher

Copy link
Copy Markdown
Collaborator

@primetimetank21 — flagging a synergy with your catalog refresh: PR #1448 (per-agent context tier: default vs long_context/1M) just merged to dev. Tier support is inferred dynamically per-model from billing.tokenPrices.longContext, so once your refresh lands, models like claude-opus-4.8 (264K default vs 1M long-context) light up automatically. When you get a chance, could you try squad config context-tier long_context --agent <name> against the refreshed catalog and confirm it resolves tiers as expected? Want to make sure the two efforts compose cleanly.

@primetimetank21

Copy link
Copy Markdown
Contributor Author

@primetimetank21 — flagging a synergy with your catalog refresh: PR #1448 (per-agent context tier: default vs long_context/1M) just merged to dev. Tier support is inferred dynamically per-model from billing.tokenPrices.longContext, so once your refresh lands, models like claude-opus-4.8 (264K default vs 1M long-context) light up automatically. When you get a chance, could you try squad config context-tier long_context --agent <name> against the refreshed catalog and confirm it resolves tiers as expected? Want to make sure the two efforts compose cleanly.

@tamirdresher -- I tried the command locally and it worked! Here's what I ran (path scrubbed):

PS C:\Coding\squad> squad config context-tier long_context --agent
✗ --agent requires a name argument.
PS C:\Coding\squad> squad config context-tier long_context --agent ralph
✓ Context tier for ralph set to long_context
PS C:\Coding\squad> squad config context-tier long_context --agent agent_name
✗ Unknown agent "agent_name".
       Known agents: booster, capcom, control, dsky, eecom, fact-checker, fido, flight, gnc, guido, handbook, inco, network, pao, procedures, Rai, ralph, retro, scribe, sims, surgeon, telemetry, vox, _alumni
PS C:\Coding\squad> squad config context-tier long_context --agent capcom
✓ Context tier for capcom set to long_context

I'll also try this on #1444.

@primetimetank21

Copy link
Copy Markdown
Contributor Author

From Earl's Squad:

@tamirdresher — tried it on #1444 (the catalog-refresh branch).

Merged current dev (which carries the context-tier work, #1448) into the catalog-refresh branch locally: clean merge, no conflicts, and it compiles. squad config context-tier long_context --agent <name> behaves as expected against the refreshed catalog:

  • --agent with no name → ✗ --agent requires a name argument.
  • unknown agent → ✗ Unknown agent "..." (lists known agents)
  • --agent ralph / --agent capcom✓ Context tier for <agent> set to long_context
  • squad config context-tier shows the persisted overrides

Tier resolution stays dynamic (inferred per-model from billing.tokenPrices.longContext), so the two efforts compose cleanly.

…nrichment

Introduce an opt-in GitHub billing cost-ceiling policy (lightweight/versatile/
powerful) as a separate axis from the quality ModelTier. Resolution is
finalized against the effective policy: implicit over-ceiling picks are
downgraded deterministically, explicit overrides are honored with a loud
warning, and the outcome is surfaced via the event bus + logs instead of being
swallowed (the defect that stalled the prior attempt). When no in-ceiling model
exists the policy fails closed and loud; unknown/out-of-catalog IDs pass through
unchanged.

Wire the policy end-to-end through AgentLifecycleManager.spawnAgent and add a
squad models refresh diagnostic that reconciles the committed seed catalog
against live sources: the canonical Copilot models API (optional, via gh auth
token) with a graceful auth-free fallback to the public github/docs pricing
YAML, writing to the gitignored local cache.

On the authenticated happy path, refresh also fetches the docs YAML and
enriches the API-discovered catalog with pricing (and releaseStatus) joined by
model id. The API stays authoritative for id/category/reachability; docs only
supplies pricing for ids the API already returned (no ids added). Enrichment is
best-effort and fail-open, keeping source: 'api'. DOCS_NAME_TO_ID is keyed by
lowercased spaced display names and trailing footnote markers are stripped so
all 13 seed ids join their pricing when present. Pricing remains
diagnostic-only and is not wired into policy enforcement.

Refs bradygaster#1080
Refs bradygaster#1183

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@primetimetank21 primetimetank21 force-pushed the primetimetank21/1080-cost-policy branch from 66f1c68 to 48e25d2 Compare July 13, 2026 06:32
primetimetank21 and others added 3 commits July 13, 2026 03:10
…r pricing enrichment

DOCS_NAME_TO_ID had no entries for the three new gpt-5.6 models, so the docs
YAML pricing join silently skipped them (unmatched names are intentionally
skipped by design). The display names 'GPT-5.6 Luna/Sol/Terra' (proper-noun
word suffixes) do not follow the same pattern as 'GPT-5.5'/'GPT-5.4 mini',
so adding them requires explicit map entries.

Added entries:
  'gpt-5.6 luna' -> 'gpt-5.6-luna'
  'gpt-5.6 sol'  -> 'gpt-5.6-sol'
  'gpt-5.6 terra'-> 'gpt-5.6-terra'

TDD: new parseDocsYaml test in test/models-refresh.test.ts exercises the full
two-row YAML fixture (Default + Long context rows) for all three IDs and asserts
correct id mapping, githubCategory, pricing, and releaseStatus. RED -> GREEN.

Verified live: squad models refresh now enriches all three with pricing and
releaseStatus from the docs YAML.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orithmic normalization + unpriced warning

PROBLEM: DOCS_NAME_TO_ID was a 16-entry hand-maintained allow-list that required
per-model edits for every new catalog entry. Silent skip (if (!id) continue) meant
brand-new models from the API would silently get no pricing without any signal.

SOLUTION:
- Export normalizeDisplayName(name): strips footnote markers [^...], strips
  parentheticals (...), lowercases, collapses whitespace to hyphens. Handles
  all current docs display names algorithmically with zero overrides needed:
    'GPT-5.6 Luna'            -> 'gpt-5.6-luna'
    'Claude Sonnet 5[^promo]' -> 'claude-sonnet-5'
    'Claude Opus 4.8 (fast)'  -> 'claude-opus-4.8'
    'Gemini 2.5 Pro'          -> 'gemini-2.5-pro'
- Replace DOCS_NAME_TO_ID with empty DOCS_NAME_OVERRIDES escape hatch (zero
  current entries; mechanism retained for genuine edge cases).
- docs-fallback path now filters by seedIdSet so extra docs rows (Fable 5,
  Kimi, etc.) remain harmlessly ignored — catalog-driven behavior preserved.
- Add unpricedIds: string[] to RefreshResult: catalog models discovered from
  API/docs but with no pricing. Printed as a loud warning in refresh output.
  New/unpriced models are visible signal, not silent drift.

TDD: 4 new normalizeDisplayName unit tests + 3 unpricedIds tests -> RED then GREEN.
21/21 models-refresh tests pass. 178/178 targeted tests pass. Full suite: same
5 pre-existing EBUSY failures as baseline, no regressions.

Live: gpt-5.6-luna/sol/terra all enrich with pricing; warning correctly surfaces
gemini-3.1-pro-preview and mai-code-1-flash-picker as not-yet-in-docs models.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vent SKU collision

REGRESSION introduced in previous commit: normalizeDisplayName stripped entire
parenthetical groups (e.g. '(fast mode) (preview)'), so 'Claude Opus 4.8' and
'Claude Opus 4.8 (fast mode) (preview)' both normalized to 'claude-opus-4.8'.
Last-wins in the enrichment Map picked the fast-mode row (/\) instead of
the real model row (/\).

ROOT INSIGHT: a parenthetical qualifier denotes a DIFFERENT product SKU. Stripping
it causes a pricing collision. Correct fix: strip the '(' and ')' chars but KEEP
the words inside, so the fast-mode row normalizes to
'claude-opus-4.8-fast-mode-preview' — a non-catalog id that enrichWithPricing
ignores, leaving the base-model pricing intact.

Footnote stripping ([^...]) is kept: footnotes are promo markers on the SAME
model ('Claude Sonnet 5[^promo]' -> 'claude-sonnet-5'), not distinct SKUs.

Scan confirmed: only ONE parenthetical row in the live docs YAML ('Claude Opus 4.8
(fast mode) (preview)'). All other models are unaffected.

Live: claude-opus-4.8 = \.00/\.00 (correct), all 16 models enriched, no
unpriced warning.

TDD: updated normalizeDisplayName unit test (parens-kept behavior) + 2 new
collision-protection tests (parseDocsYaml + enrichWithPricing). RED -> GREEN.
180/180 targeted tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tamirdresher pushed a commit that referenced this pull request Jul 13, 2026
…x Ralph free-model wording (#1469)

* docs/models: prefer newest per series (Opus 4.8 + Sonnet 5), add GPT-5.6 IDs, fix Ralph free-model wording

Follow-up to #1444 (merged), addressing tamirdresher's
three explicit requests in #1444 (comment)
(refs #1080, #1183).

Item 1 — Prefer newest model per series (fallback ordering):
- packages/squad-sdk/src/config/models.ts: DEFAULT_FALLBACK_CHAINS.standard
  now leads with claude-sonnet-5 (was claude-sonnet-4.6).
- packages/squad-sdk/src/runtime/constants.ts: MODELS.FALLBACK_CHAINS.standard
  same reorder. Premium chains already led with claude-opus-4.8 post-#1444.
- model-selection-reference.md, SKILL.md files: examples now show
  claude-opus-4.8 first in Premium, claude-sonnet-5 first in Standard.

Item 2 — Add GPT-5.6 model IDs (gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna):
- Added to MODEL_CATALOG in models.ts (tier: standard, githubCategory: powerful
  — mirrors gpt-5.5 sibling entry).
- Added to DEFAULT_FALLBACK_CHAINS.standard and MODELS.FALLBACK_CHAINS.standard
  (after claude-sonnet-4.6, before gpt-5.4).
- Reachability: CLI-observed as Standard-tier reachable models (2026-07-13).
- NOTE: gpt-5.6 entries in DOCS_NAME_TO_ID (cli/commands/models.ts) are
  deferred until draft PR #1445 merges (that file is in #1445's diff).
- Updated Valid Models in all model-selection-reference.md + SKILL.md copies.

Item 3 — Ralph circuit-breaker free-model wording:
- .squad-templates/ralph-circuit-breaker.md: replaced multiplier table
  (0x / "Free — unlimited") with usage-based framing (lightweight category —
  lowest-cost, still billed). Updated preferredModel example to claude-sonnet-5.
- Synced to all 3 template targets via sync-templates.mjs.

NOTE: MODELS.DEFAULT (claude-sonnet-4.6) is intentionally left unchanged.
That global default is out of scope for this PR — can be a separate decision.

TDD: test/catalog-refresh.test.ts — wrote failing tests first (8 RED), then
implemented changes. Final result: 16/16 tests GREEN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(models): sync full Standard chain in doc/skill examples; add changeset

Address Copilot review comments on PR #1469:

- 3 doc/skill files had abbreviated Standard fallback chain (omitting
  gpt-5.6-terra, gpt-5.6-luna, gemini-2.5-pro). Now matches the full
  SDK runtime chain exactly:
    claude-sonnet-5 → claude-sonnet-4.6 → gpt-5.6-sol → gpt-5.6-terra
      → gpt-5.6-luna → gpt-5.4 → gpt-5.3-codex → claude-sonnet-4.5
      → gemini-2.5-pro → (omit model param)
  Files: .squad-templates/model-selection-reference.md,
         .copilot/skills/model-selection/SKILL.md (manual),
         .squad/skills/model-selection/SKILL.md (sync canonical)
  Synced to all 3 template targets + 2 skill package targets.

- Premium chain confirmed correct (claude-opus-4.8 first) — no change
  needed.

- Add .changeset/models-gpt56-fallback-ordering.md (@bradygaster/squad-sdk
  patch) to satisfy Changelog Gate CI check.

16/16 catalog-refresh tests remain GREEN.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test: update stale standard-chain[0] assertions to claude-sonnet-5

Three test files hard-coded the old fallback chain head (claude-sonnet-4.6)
that was intentionally reordered in the parent commit (newest-per-series
first, tamirdresher PR #1444 follow-up). The tests remain logically correct
— they now assert the NEW intended behavior.

- test/compat-v041.test.ts: standard[0] → 'claude-sonnet-5'
- test/models.test.ts: standard[0] → 'claude-sonnet-5'
- test/agents.test.ts: standard fallback chain toEqual updated
  (reordered + added gpt-5.6-sol/terra/luna)

Inline comment added to each noting the deliberate reorder.
Pre-existing failures (cli-packaging-smoke, init-scaffolding) are EBUSY
file-lock errors unrelated to these changes — confirmed on baseline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(models): correct gpt-5.6 billing categories to match live Copilot API

Live API (canonical) returns: sol=powerful, terra=versatile, luna=lightweight.
Original seed incorrectly set all three to 'powerful' (mirroring gpt-5.5).
Tier (standard) and fallback-chain membership are unchanged; only githubCategory
(the cost-policy billing axis) is corrected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@primetimetank21 primetimetank21 marked this pull request as ready for review July 13, 2026 10:54
Copilot AI review requested due to automatic review settings July 13, 2026 10:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a category-based cost ceiling policy to SDK model selection (wired through lifecycle with surfaced outcomes) and introduces a squad models refresh CLI diagnostic that reconciles the seed model catalog against live sources and enriches with docs pricing (cached locally).

Changes:

  • Implement cost-ceiling policy finalization in model selection (downgrade/prune/warn) and expose the outcome on the resolved model.
  • Wire cost policy through AgentLifecycleManager.spawnAgent and surface policy actions via console.warn + EventBus.
  • Add squad models refresh command (API-first with docs fallback), cache output to .squad/.cache/models.json, and add comprehensive tests.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/models-refresh.test.ts Adds unit/integration coverage for refresh sourcing, enrichment, normalization, and warnings.
test/model-selector-policy.test.ts Adds cost policy behavior tests (downgrade, warn-allow, pruning, fail-closed signaling).
test/lifecycle.test.ts Adds end-to-end lifecycle wiring tests for cost policy + event/warn surfacing.
test/config-schema.test.ts Adds runtime config validation tests for models.costPolicy.maxCategory.
packages/squad-sdk/src/runtime/config.ts Extends runtime config model selection schema + validation for costPolicy.
packages/squad-sdk/src/config/schema.ts Adds costPolicy to typed config surface.
packages/squad-sdk/src/config/models.ts Introduces cost policy types/outcome and category ordering helpers.
packages/squad-sdk/src/agents/model-selector.ts Adds effective policy merge, chain pruning, deterministic downgrade, and policy outcomes.
packages/squad-sdk/src/agents/lifecycle.ts Threads persistent + session policy through resolution and emits policy outcomes.
packages/squad-sdk/src/agents/index.ts Exports new policy helpers/types for external consumption/tests.
packages/squad-cli/src/cli/commands/models.ts Implements squad models refresh (API + docs YAML parsing/enrichment) and caching.
packages/squad-cli/src/cli-entry.ts Adds CLI routing for the new models command.
.changeset/feat-cost-policy-1080.md Declares minor bumps for SDK/CLI with a combined changelog entry.

Comment thread packages/squad-cli/src/cli/commands/models.ts
Comment thread packages/squad-cli/src/cli/commands/models.ts Outdated
…tier pricing, suppress spurious unpriced warning

Comment 1 (parseDocsYaml): skip `tier: Long context` rows so enrichWithPricing
always attaches the Default-tier (base) price per model, not the higher Long-context
threshold price.  Two-row models (gpt-5.5, gpt-5.4, gpt-5.6-*) now correctly show
their Default-tier pricing.

Comment 2 (unpricedIds): track whether docs enrichment actually produced any priced
result (enrichmentRan); only compute/emit unpricedIds when enrichmentRan is true.
Suppresses the misleading "all models unpriced" warning when the docs YAML fetch
itself fails — distinguishes enrichment failure from genuinely new unpriced models.

Tests: 4 new RED-first tests (Default-tier-wins, enrichWithPricing Default price,
docs-fetch-throws suppression, empty-yaml suppression) → 27/27 GREEN.

Refs bradygaster#1080 / bradygaster#1183

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@primetimetank21

primetimetank21 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

From Earl's Squad:

@tamirdresher — requesting your review on the category-based cost policy + squad models refresh work (linked above, open for review against bradygaster/squad).

This is a separate feature continuing the broader cost-policy direction: it adds a maxCategory billing ceiling to model selection (lightweight / versatile / powerful) with deterministic downgrade, explicit-override warn-and-allow, and fail-closed behavior, wired through the agent lifecycle. It also ships squad models refresh — a diagnostic command that reconciles the seed catalog against live sources with pricing enrichment.

The PR partially advances issues #1080 and #1183 (tracked in both issue threads with a breakdown of what's proposed vs. what remains). It does not close either issue — follow-up work is noted.

Happy to address any feedback.

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.

3 participants