Skip to content

Add jev: Jev decides which model, Cerberus executes it - #29

Merged
Deep-Sixed merged 1 commit into
mainfrom
claude/zealous-edison-ik9ez1
Oct 5, 2026
Merged

Deep-Sixed merged 1 commit into
mainfrom
claude/zealous-edison-ik9ez1

Conversation

@Deep-Sixed

Copy link
Copy Markdown
Owner

Summary

Phase 2 of Jev routing: native jev aliases. Cerberus asks Jev, through OpenRouter's Decisions API, which pool model should serve a request, then runs that model through its own failover loop. The pool may include local hosts, Cloudflare, OpenRouter, or any other registered provider.

CERBERUS  "what may run?"    registry policy → cost → health → cooldown → credential
JEV       "what should run?" one choice question over what survived
PROVIDER  "run it."          Cerberus's dispatch loop, chosen model first
cerberus/smart:
  mode: jev
  candidates:                       # configured order = the fallback order
    - { provider: thanatos,   credential: none,    model: gemma-4-12b }
    - { provider: openrouter, credential: primary, model: anthropic/claude-sonnet }
  jev:
    decider: { provider: openrouter, credential: primary }
    model: typesafe/jev-1.13        # pinned; bump deliberately after testing
    input: last_user_message        # | full_conversation | metadata
    max_input_chars: 4000
    timeout_seconds: 5
    allow_paid_pool: true

What Jev sees

  • Candidates are the models that pass router/pool.py (the filter added in Deep-Sixed/Cerberus#28). Each distinct model is offered under an opaque id (m1, m2, …).
  • Each model is described only by registry facts: model id, strength (new registry field, required for jev pool models), cost tier, context window, capabilities and description. Provider and credential names never leave.
  • jev.input controls what request text is sent: the latest user message by default, the full conversation if opted in, or metadata only (no prompt text). Text is always truncated, and Jev always gets the request's shape: turns, size, tools, response format and requested output length.
  • When only one model (or none) is left, no decision is requested, so no text leaves Cerberus.

What Jev can change

  • Order only. The chosen model runs first. The rest of the cost-admitted pool follows in configured order.
  • dispatch() takes a new DispatchPlan and runs it through the normal loop: health, cooldown and credential checks, 429 cooldowns, failover, streaming and telemetry.
  • A plan that names a target the alias doesn't configure is refused.
  • An answer that isn't exactly one of the offered ids is never acted on.

When Jev can't answer: configured order (your choice)

Any of these runs the request in configured order: missing backend or credential, the decider's provider marked down, timeout, transport or HTTP error, an unreadable response, or a missing or unknown choice. The reason is recorded. A decision outage never takes the alias down.

Telemetry and console

  • Telemetry schema 7 adds a jev record: options offered, chosen / skipped / fallback, reason, choice, probability, decision latency, status and usage. It never includes request text.
  • /admin/routes and the console report the pool's readiness and the decider's readiness separately.
  • The control plane stores binding_type = 'jev' with pool members. No migration is needed.

Decisions API contract

Confirmed (from the material you sent):

  • POST https://openrouter.ai/api/alpha/decisions with a bearer key
  • a model / state / questions body
  • choice / noul / score question types
  • answers keyed by question id, plus usage
  • model typesafe/jev-1.13

Not yet confirmed: the field names inside a single question and a single answer.

  • The request uses the plainest form: {"id": "route", "type": "choice", "question": …, "options": [ids]}.
  • The answer reader accepts choice / result / value / answer / selected, or the option with the highest probability.
  • Whatever it reads must exactly match an id Cerberus offered. If the guess is wrong, requests run in configured order and telemetry says why; they never reach a model outside the pool.

Test plan

  • ./scripts/ci.sh: ruff clean, 418 passed. New tests: 35 in tests/test_jev_dispatch.py and 7 jev cases in tests/test_schema.py.
  • Mutation checks: accepting any answer, ignoring jev.input, and ignoring the choice each fail 3 tests.
  • uv build --no-build-isolation; the wheel includes cerberus/jev.
  • node --check src/cerberus/ui/static/app.js
  • python scripts/check-public-history.py: 0 findings.
  • Live probe against the Decisions API with a real key, to confirm the question and answer field names. If they differ, jev.reason will read http_error (request shape) or no_choice (answer shape), and the fix stays inside src/cerberus/jev/backend.py.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P4Nm4MVSMmG6JyZi8WumtK


Generated by Claude Code

Phase 2 of Jev routing. A jev alias asks Jev, through OpenRouter's
Decisions API, which pool model should serve a request, then runs that
model through Cerberus's own failover loop. Cerberus answers "what may
run?", Jev answers "what should run?", the provider runs it.

- Schema: `jev` mode with a `jev` block (decider credential, endpoint,
  model pinned to typesafe/jev-1.13, input policy, max_input_chars,
  timeout, allow_paid_pool). Registry models gain `strength` and
  `description`; a jev pool model must declare a strength. Unlike
  jev-router, the pool may span any providers, local hosts included.
- The pool is filtered by router/pool.py; the surviving distinct models
  are offered under opaque ids, described by registry facts only.
  Provider and credential names never leave.
- What the decision reads is policy: the latest user message (default),
  the whole conversation, or request metadata with no prompt text,
  always truncated. With one model or none left, no decision is asked.
- The answer can only reorder: the chosen model goes first, the rest of
  the cost-admitted pool follows in configured order, and dispatch()
  executes it through a DispatchPlan, so jev aliases stream, fail over
  and cool down like any dispatch alias. A plan that names a target the
  alias does not configure is refused.
- No usable answer (no backend or credential, decider down, timeout,
  transport/HTTP error, unreadable response, absent or unknown choice)
  runs the request in configured order and records why.
- Telemetry schema 7 adds a `jev` record (options, chosen/skipped/
  fallback, reason, choice, probability, decision latency, status,
  usage) and never any request text. /admin/routes and the console show
  the pool and the decider's readiness separately.

The Decisions API fields inside one question and one answer are not yet
confirmed; the request uses the plainest form and the answer is read
tolerantly, so a misread degrades to configured order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4Nm4MVSMmG6JyZi8WumtK
@Deep-Sixed
Deep-Sixed marked this pull request as ready for review October 5, 2026 17:21
@Deep-Sixed
Deep-Sixed merged commit 86356b0 into main Oct 5, 2026
2 checks passed
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.

2 participants