Skip to content

[Feature]: Demand-level routing for JEV combos — orchestrator-chosen levels, per-session pinning, probability escalation (opt-in series on #6364) #6665

Open

Description

@geunwoojun99

Area

Proxy and routing

What are you trying to accomplish?

We route Claude Code subagents through one JEV combo (CLAUDE_CODE_SUBAGENT_MODEL=ocx-claude-combo--<combo> with ..._FORCE=1). The goal is to give each subagent a model and effort that match how demanding its assignment is, while keeping three properties:

  • The orchestrating agent can state that level when it knows it, for example "this delegate does a cross-module refactor".
  • The provider prompt cache survives for the whole subagent conversation.
  • Operators keep owning the candidate lists and quota policy.

What prevents this today?

On current dev a JEV combo decides a target and effort per request. When we ran a level-mode build (the closed #6423 work) with Claude Code for about a week, we saw the following:

What should OpenCodex do?

Every item is opt-in and leaves today's behavior and payloads unchanged when unset.

  1. Demand-level routing through the existing dispatcher. decisionMode: "level" with decisionLevels holds per-level ordered candidates, each with an effort. decisionFallbackLevel sets the fallback. Selection goes through resolveJevComboDecision for the TypeSafe, self-hosted System One and model backends.
  2. Quota signals with configurable tier boundaries. The defaults stay at 70% / 90%. An optional extra boundary (for example 40%) lets a combo prefer a provider with more headroom.
  3. Fixed level (decisionFixedLevel). The combo uses one level and never calls the classifier.
  4. Described custom level ids. A combo can use its own vocabulary (for example light/routine/heavy/deep), in config order from least to most demanding. Each custom id must have a description, and the orchestrator and classifier share the same definitions.
  5. Richer classification evidence (decisionRichState). Instead of only the last 500 characters, the classifier gets:
    • the assignment, meaning the first user message with reminders removed;
    • the head of the agent role;
    • tool names and capability flags (edit / shell / web / spawn_agents).
      No schemas, arguments, reminders or credentials are sent.
  6. Session pinning (decisionSessionPin). Decide the level, target and effort once per client conversation, then reuse them. Re-pin within the level, then the fallback level, only when the pinned target becomes ineligible or nearly exhausted, and record the level actually used. The model and effort move together, because changing either costs the cache anyway.
  7. Orchestrator hints (decisionLevelHints). A system-prompt line ocx-level: <level> selects the level without a classifier call. The precedence is fixed level, then hint, then classifier.
  8. Probability escalation (decisionLevelEscalation: {minProbability, maxLevel?}). When the answer carries per-level probabilities, pick the most demanding level at or above minProbability, capped at maxLevel. Without probabilities, nothing changes.

Example usage or interface

"combos": { "subagents": {
  "strategy": "jev", "decisionProvider": "<system-one or model backend>",
  "decisionMode": "level",                       // proposal
  "decisionLevels": {                            // custom ids: proposal
    "light":   { "description": "single-pass, checkable work …", "candidates": [{ "provider": "anthropic", "model": "claude-sonnet-5-5", "effort": "low" }, { "provider": "openai", "model": "gpt-6-luna", "effort": "medium" }] },
    "routine": { "description": "bounded implementation …",      "candidates": [{ "provider": "anthropic", "model": "claude-sonnet-5-5", "effort": "high" }] },
    "heavy":   { "description": "non-trivial engineering …",     "candidates": [{ "provider": "anthropic", "model": "claude-sonnet-5-5", "effort": "xhigh" }] },
    "deep":    { "description": "design/analysis, no implementation …", "candidates": [{ "provider": "anthropic", "model": "claude-opus-5-5", "effort": "xhigh" }] }
  },
  "decisionFallbackLevel": "routine",
  "decisionQuotaSignals": true, "decisionQuotaTiers": { "moderate": 40, "limited": 70, "nearlyExhausted": 90 },  // names: proposal
  "decisionSessionPin": true, "decisionRichState": true,
  "decisionLevelEscalation": { "minProbability": 0.3, "maxLevel": "heavy" },
  "targets": [ … ]
}}

A Claude Code agent definition opts in with one line in its prompt body: ocx-level: heavy. With none of these fields set, the combo behaves exactly as it does today.

Alternatives or workarounds

  • Per-level combos with failover and per-agent model: + effort:. This works on stock dev, but loses per-candidate efforts, quota-tier preference, and classification for agents that carry no hint, such as built-ins.
  • Per-request route mode. This is the cache and stability problem described above.
  • Keeping a private build. This is what we do now, and it breaks on ocx update.

Additional context

Evidence. These numbers come from one self-hosted tev1:4b System One classifier and our own labelled tasks, so treat them as indicative:

  • Setup: 85 subagent assignments in the exact shape Claude Code sends, no hint, 3 runs each.
  • Plain argmax: 76.5% exact. Misses were mostly one level too low (54 vs 6 runs).
  • Escalation (minProbability: 0.3, capped below the design level), computed on the same stored probabilities: correct answers rose from 65 to 73 of 85, and from 180 to 199 of 255 when the agent role was varied. Too-low errors fell from 18 to 8 and too-high errors rose from 2 to 4.
  • Session pinning, observed live: a 16-request subagent stayed on one model and effort with about 60k cached input tokens per turn.
  • Not tested: we have no TypeSafe key, so hosted jev-latest is untested with custom ids, rich state, and probabilities.

Proposed series. Each PR is about 100–400 production lines, default-off, with tests, docs-site and structure updates. Stacked PRs retarget dev after their parent lands. The list follows the port plan:

  • P0: share the bounded System One exchange
  • P1: quota signals and configurable boundaries
  • P2: level definitions
  • P3: level routing through all backends
  • P4: read-only GUI
  • P5: fixed level
  • P6: custom ids
  • P7: rich evidence
  • P8: session pinning
  • P9: hints
  • P10: escalation
  • P11: refresh quota only while quota-aware combos are active

Separate bug fixes are opened independently: #6662 (Claude Code <system-reminder> in the decision task), #6663 (the forced-effort usage label) and #6664 (Cursor redirect wording for Claude-family models).

Questions before coding, in priority order:

  1. Is resolveJevComboDecision the right owner for mode and policy dispatch? Should level policy travel as fields or as a nested, versioned policy object?
  2. Should the feat(combos): JEV quota signals and level decision mode #6423 field names be kept? Is the fixed → hint → classifier precedence right, and should hints require pinning?
  3. Are custom ids and descriptions acceptable on hosted TypeSafe, or only on self-hosted and model backends? Should config order define demand order?
  4. Pin identity and state. Should the server own a request-scoped session key, with the hash of system prompt plus first assignment only as a fallback? Is in-memory, bounded state with no restart survival acceptable? What invalidates a pin when config, credentials or targets change?
  5. What is pinned (provider, model and effort, but not the pool account), and is it committed on classification or on first successful inference?
  6. Should quota boundaries be per combo or global? May four tiers appear in the hosted route payload?
  7. Which backends return validated per-level probabilities? Should escalation wait until that contract is agreed?
  8. Telemetry: what should backend mean for fixed, hint and pinned decisions that never call a backend? Pin hashes would be left out of records unless needed.
  9. Scope: is a read-only GUI enough? We do not plan endpoint changes, which overlaps with Allow arbitrary HTTPS JEV decision endpoints #6384.

Related.

Credit. The level-mode and quota-signal pieces build on @SeongwoongCho's #6348 / #6423 and will carry Co-authored-by: SeongwoongCho <35558061+SeongwoongCho@users.noreply.github.com>.

Checks

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Activity

  1. added
    enhancementNew feature or request
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Oct 6, 2026
  2. Ingwannu commented on Oct 7, 2026

    @Ingwannu
    Owner

    This is a substantial opt-in routing design, not a decision-free regression fix. The classifier percentages and cached-token example are useful author measurements, but do not validate hosted TypeSafe compatibility or establish the default policy for other workloads. #6662/#6663/#6664 and the P0 refactor #6698 should remain independently reviewable; accepting a refactor would not approve the later level/hint/pinning features.

    @lidge-jun the first owner choices appear to be the versioned policy/config shape and session-pin identity/commit boundary. Before implementing pinning, specify bounded retention, concurrent requests for the same session, configuration/target/credential changes, failed first inference, ineligible pinned targets and unknown session identity. Hints/classifier output must select only among operator-authorized candidates; fixed/hint/classifier precedence and hosted capability fallback need explicit tests rather than an inferred endpoint contract. Please keep the proposed GUI slice and probability semantics as separate decisions. No new mode, schema, external decision call, or rollout was approved or implemented in this pass.

  3. geunwoojun99 commented on Oct 8, 2026

    @geunwoojun99
    ContributorAuthor

    Thanks for clarifying. Scope: P0 #6698 remains an independent refactor plus cancellation bugfix; accepting it approves nothing later.

    Proposed contract

    • Config: Opt-in decisionMode: "level", decisionLevels (IDs/descriptions and ordered {provider, model, effort?} candidates), decisionFallbackLevel, decisionFixedLevel, decisionSessionPin, decisionLevelHints, decisionQuotaSignals and decisionQuotaTiers. Features default off; unset request bytes unchanged. Shape/versioning remains your decision.
    • Identity/authority: Server-owned opaque namespace scoped by config root, trusted admission principal, combo, and specific child/conversation identity. No trustworthy identity or complete owner authority means no pin; headers or prompt hashes alone are not authentication.
    • Retention: 5,000 entries, 24-hour touch TTL, 8 MiB tracked-entry budget, LRU eviction; not a heap-size guarantee.
    • Concurrency: At most 128 shared in-flight classification keys, existing deadlines, flight-owned cancellation, per-waiter abort reasons; last waiter leaving aborts shared work. Saturation uses independent bounded decisions under existing admission limits. Selection/authorization remain per caller.
    • Commit/failure: Commit provider/model/effort, never pool account, after valid level selection and final synchronous current mapping, before initial dispatch—not after first-inference success. Failed inference alone does not discard the pin; existing eligibility/cooldowns apply. Canceled, stale, or global fail-open results never commit.
    • Changes: Owner-generation and policy-fingerprint fences invalidate relevant config/target/effort/credential changes and reject late results; unrelated changes preserve pins. This is not physical-send revocation.
    • Repin: Only ineligible or valid nearly-exhausted quota permits repinning: same level, then fallback. Keep an eligible old pin without a better alternative; moderate/limited quota does not move it. Record actual level/effort.
    • Compaction/restart: Strong-identity eligible pins permit read-only compaction reuse: no touch/create/repin. No restoration after restart.

    Selection and required tests

    Candidates always come from operator-configured targets/effort allowlists. Hints/classifiers only choose configured levels; neither expands authority. Precedence: fixed > eligible existing pin > hint > classifier. Fixed never mutates pins; hints require pinning.

    Require explicit tests for precedence/zero-classifier paths; disabled, unknown, ambiguous and injected hints; target/effort rejection; retention/concurrency/cancellation/invalidation/failure/compaction; unset-byte goldens; and hosted rejection/unsupported/malformed responses selecting configured fallback, then existing authorized global fail-open. No inferred endpoint capability contract or hosted-validation claim.

    The GUI slice and probability escalation remain separate later decisions.

    Owner questions

    1. Keep these sparse opt-in fields, or use a nested versioned policy object?
    2. Is the proposed trusted identity and selection-time commit boundary acceptable?
    3. Should the next PR be a separate Draft P1 for cached advisory quota evidence only?

    🤖 Generated with Claude Code

  4. geunwoojun99 commented on Oct 9, 2026

    @geunwoojun99
    ContributorAuthor

    Thanks for carrying #6791, #6790 and #6798.

    Answering our own Q3 by doing it as a small piece: #6814 is a draft PR for the separate P1, opt-in cached advisory quota evidence for JEV decisions. It is dormant by default, touches nothing in the shared exchange, and adds no level or pin behavior. Its description lists what was and was not validated, including a red --changed=dev run caused by failures outside the diff.

    The config-shape and pin-identity questions remain open for the owner; #6814 does not decide either. Happy to close it if you would rather carry or reshape it.

    🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions