Skip to content

feat(core): per-target context limits for model routes - #811

Merged
dezren39 merged 4 commits into
mainfrom
queue/2913-710-per-target-limits
Oct 29, 2026
Merged

dezren39 merged 4 commits into
mainfrom
queue/2913-710-per-target-limits

Conversation

@dezren39

@dezren39 dezren39 commented Oct 29, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #710

Type of change

  • New feature

What does this PR do?

Implements per-element (per-target) context limits for model routes. Closes #710.

Route targets can now declare limit: { context } in config. The limit composes down the reach path (smallest ceiling wins, including through nested-route references), the route presents min(route cap, leaf ceilings) so compaction cannot bypass a cap, and selection skips over-limit targets with the reason attributed as over-limit — checked last so existing exclusions (cooldown, provider-limit, user-skip, soft-budget) keep their precedence and reasons. route_stats surfaces declared per-target ceilings (configured.limits, configuredLimit, ceiling N in text) and the skip reason.

The flat motivating case works: small as one route with 100k/100k/200k per-element limits presents 100k with per-leaf ceilings in the dump — no nested small-100k sub-route needed. Nested routes carrying their own limits are unchanged.

Wiring note: ModelResolver.resolveModel takes an optional requestTokens size (unknown sizes never filter, preserving current behavior and precedence). Threading live session prompt sizes through the runner is follow-up work; the eligibility mechanism is complete and covered by tests.

Tests: schema parsing/expansion, mixed-ceiling flat pool, nested regression, ceiling composition, resolver-level skip + dump attribution, route_stats ceilings, location-layer presented-limit fixture. CI runs them (never run in the work environment per task discipline).

Follow-up commit addressing automated review findings: the resolver's last-resort fallback no longer restores over-limit targets (context ceilings are hard limits, unlike soft budgets/skips); a duplicate target attached to the same route node now still tightens the leaf ceiling; and the generated client type ConfigModelRouteTarget now includes limit (surgical update — a full regeneration on this base produces unrelated churn, left to a base-wide regen).

How did you verify your code works?

  • packages/core: bun test over model-route, model-route-review, model-routes-expand and model-resolver suites — 157 pass, 0 fail, including new regression tests for the last-resort ceiling and duplicate-target ceiling merge.
  • packages/core and packages/client typecheck (tsgo --noEmit) pass.
  • CI on the previous head was still queued at the time of the follow-up push; the new head re-runs it.

Screenshots / recordings

Not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 48 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6e39be37-0eb0-4a29-b2b5-17326b2e8d18
Commits

Reviewing files that changed from the base of the PR and between 2bb9cc9 and 2bfb0a6.

Files ignored due to path filters: 1
  • packages/client/src/promise/generated/types.ts is excluded by !**/generated/**
Files selected for processing: 18
  • packages/core/src/config/plugin/model-routes.ts
  • packages/core/src/model-resolver.ts
  • packages/core/src/model-route-review.ts
  • packages/core/src/model-route.ts
  • packages/core/src/plugin/skill/routing-review.md
  • packages/core/src/tool/plugin/route-stats.ts
  • packages/core/test/location-layer.test.ts
  • packages/core/test/model-resolver.test.ts
  • packages/core/test/model-route-review.test.ts
  • packages/core/test/model-route-stats-tool.test.ts
  • packages/core/test/model-route.test.ts
  • packages/core/test/model-routes-expand.test.ts
  • packages/protocol/openapi.json
  • packages/schema/src/config/model-routes.ts
  • packages/schema/test/config.test.ts
  • services/www/openapi.json
  • services/www/public/openapi.json
  • services/www/src/docs/content/config.mdx
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-29T11:06:21.861948Z 2bfb0a6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a48eb2dab1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +499 to +500
else if (isOverLimit) unavailable.set(index, "over-limit")
return until <= now && (limited <= now || kept) && !isSkipped && !isOverBudget && !isOverLimit

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep context ceilings enforced during last-resort selection

When every target is unavailable, the initial resolution (allowCooling: true) restores all indexes as eligible at line 504, including targets rejected by this new context check. For example, a request of 130,000 tokens against a route capped at 120,000 still resolves to a target instead of returning RouteUnavailableError. The same bypass occurs when the only target large enough is cooling down or soft-excluded. Filter over-limit targets out of the last-resort candidates as well, so relaxing soft exclusions cannot bypass a hard context ceiling.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Disposition (Lane A, task 2913): already fixed in head 2bfb0a6 — the last-resort path filters over-limit targets: packages/core/src/model-resolver.ts:508 computes eligible as indexes.filter((index) => !overLimit.has(index)) when allowCooling, with the hard-ceiling comment at :504-507, so relaxing soft exclusions cannot restore a target whose context ceiling the request exceeds.

Comment on lines +426 to +430
if (
ceiling !== undefined &&
(result[existing].contextLimit === undefined || ceiling < result[existing].contextLimit)
)
result[existing] = { ...result[existing], contextLimit: ceiling }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Merge ceilings before skipping same-node duplicate targets

When two targets in the same route resolve to the same leaf, the earlier if (attached.has(key)) continue bypasses this ceiling merge entirely. For example, targets: ["p/*", { model: "p/expensive", limit: { context: 100000 } }] leaves p/expensive uncapped, whereas reaching that same leaf through separate nested routes correctly retains the smallest ceiling. This also leaves the presented route limit uncapped, allowing compaction to exceed the explicitly configured ceiling. Apply the ceiling merge before the same-node duplicate check while still deduplicating the tree attachment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Disposition (Lane A, task 2913): already fixed in head 2bfb0a6 — the same-node duplicate branch in packages/core/src/config/plugin/model-routes.ts:451-462 merges the ceiling into the already-attached leaf (tightening contextLimit when the duplicate declares a smaller one) before continue, so a cap declared on a duplicate target is retained.

Comment on lines +34 to +38
limit: Schema.Struct({
context: PositiveInt.annotate({
description:
"Caps the context this target serves within the route, below its own limit — e.g. 100000 keeps this target on requests inside a pricing tier that ends at 100k context. A request that does not fit this ceiling and the route's own cap skips this target like any other ineligible target.",
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Regenerate the client types for the expanded public contract

The public config contract and OpenAPI documents now expose target-level limit, but packages/client/src/promise/generated/types.ts still defines ConfigModelRouteTarget without that property. Since ConfigGetOutput uses these generated config types, Promise-client consumers cannot inspect the newly returned ceilings through the typed API without casts. Run bun run generate from packages/client and commit the generated changes, as required by the root AGENTS.md for public Protocol changes.

AGENTS.md reference: AGENTS.md:L1-L1

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Disposition (Lane A, task 2913): already done in head 2bfb0a6 — the client was regenerated during the conflict resolution (bun run generate from packages/client) and packages/client/src/promise/generated/types.ts:763 now defines limit?: { context: number } on ConfigModelRouteTarget (also :2211), so Promise-client consumers see target ceilings through the typed API.

@github-actions

Copy link
Copy Markdown

The following comment was made by an LLM, it may be inaccurate:

- model-routes-expand: the flat mixed-ceiling test expanded route id
  "small" but the route() helper keys its map as "r", so expand
  returned no leaves.
- model-resolver: decision-row labels use the target ref id
  (test-model for these leaves), not the modelID (luna); the
  over-limit decision assertion now matches the recorded labels.
- model-route-review: configured() crashed on a shorthand string
  target ("providerID" in a string); handle string targets the way
  targetSpec does in model-routes.ts.
… reaches

- model-resolver: the last-resort (allowCooling) fallback no longer restores targets excluded as over-limit; context ceilings are hard limits, unlike soft budgets/skips. Regression test: a single over-limit target now fails resolution instead of serving the request.
- model-routes expand: a duplicate target attached to the same node now still tightens the leaf ceiling (smallest ceiling wins), matching the cross-reference merge. Regression test included.
- client: add limit to the generated ConfigModelRouteTarget type (surgical one-line update; a full regeneration on this base produces unrelated type-relocation and session-interrupt churn, so it is left to a base-wide regen).
@github-actions

Copy link
Copy Markdown

Thanks for updating your PR! It now meets our contributing guidelines. 👍

…rget-limits

# Conflicts:
#	packages/client/src/promise/generated/types.ts
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.

feat(routes): per-element (per-target) context limits

1 participant