Repository navigation
[Feature]: Codiv (api.codiv.ai) as a JEV-compatible decision provider with pacing, retry and fallback #7083
Copy link
Copy link
Open
Labels
enhancementNew feature or requestNew feature or requestneeds-design설계 필요. Implementation waits on a written design.설계 필요. Implementation waits on a written design.proxyHTTP proxy, routing, reverse-proxy / management authHTTP proxy, routing, reverse-proxy / management auth
Description
Area
Proxy and routing
What are you trying to accomplish?
Codiv exposes a JEV/TypeSafe-compatible System One decision API (POST /v1/systemone, model openjev-0.1). We want to use it through OpenCodex in two ways:
- as a decision provider for
jevcombos, and as a fallback for the TypeSafejevprovider: if TypeSafe errors or times out, Codiv makes the decision; - as a shared, rate-limited path for our own services that call Choice/Noul directly, so that several clients sharing one Codiv key stay under its limit.
What prevents this today?
We checked 2.81.0:
jev-decisionis only called internally by thejevcombo strategy (src/combos/jev.ts~743-800). There is no client-facing System One route, only/api/combos/decision-testand/api/combos/decision-discovery.jev-decisionrequests go throughproviderOutboundPost(src/lib/provider-outbound.ts). That path bypassesrequestPacingandretryOn429, and does not forward the provider's configuredheaders; the User-Agent defaults toopencodex.- A combo has one decision provider. There is no ordered fallback chain between decision backends.
529(overloaded) is not retried (src/lib/upstream-retry.ts~435).
What should OpenCodex do?
All opt-in, current behaviour unchanged when unset:
- Apply
requestPacing(requestsPerMinute,maxConcurrentRequests) andretryOn429withRetry-Aftersupport tojev-decisiontraffic, and allow529to be treated as retryable for such providers. - Forward the provider's
headerson decision requests. Codiv requires aUser-Agentheader; without it Cloudflare returns 403/1010. - Allow an ordered list of decision backends, each with its own model, e.g.
jev(TypeSafe) followed bycodiv(openjev-0.1), with the existing fail-open behaviour after the last one. - Optional: a data-plane passthrough route (e.g.
/v1/systemone) authenticated with a data key, so external clients share the same pacing and fallback.
Example usage or interface
"providers": {
"codiv": {
"adapter": "jev-decision",
"baseUrl": "https://api.codiv.ai/v1/systemone",
"apiKey": "${CODIV_API_KEY}",
"headers": { "User-Agent": "my-client/1.0" }, // proposal: forwarded
"requestPacing": { "enabled": true, "requestsPerMinute": 250, "maxConcurrentRequests": 8 }, // proposal: applied
"retryOn429": { "enabled": true, "maxAttempts": 3, "intervalMs": 5000, "maxIntervalMs": 30000 }
}
},
"combos": { "jev-auto": {
"strategy": "jev",
"decisionProviders": [ // proposal
{ "provider": "jev" },
{ "provider": "codiv", "model": "openjev-0.1" }
],
"targets": [ … ]
}}Alternatives or workarounds
- Point the single
jevprovider at Codiv instead of TypeSafe. This loses TypeSafe and still has no pacing or retry. - Rate-limit in each client separately. This does not coordinate clients that share one key.
Additional context
Codiv's limit is per key (1200 rpm). With several services on one key we already see 429s at about 300 rpm, which is why central pacing matters.
Related: #6268 (self-hosted JEV-compatible decision endpoints), #6384 (configurable decision endpoints), #6665 (demand-level routing for JEV combos).
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestneeds-design설계 필요. Implementation waits on a written design.설계 필요. Implementation waits on a written design.proxyHTTP proxy, routing, reverse-proxy / management authHTTP proxy, routing, reverse-proxy / management auth
Activity
github-actions commentedon Oct 13, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
[-]Add Codiv (api.codiv.ai) as a Jev-compatible decision provider with pacing, retry and fallback[/-][+][Feature]: Codiv (api.codiv.ai) as a JEV-compatible decision provider with pacing, retry and fallback[/+]nordz0r commentedon Oct 13, 2026
Clarifying the fallback requirement (item 3):
jev(TypeSafe) followed bycodiv, orcodivfollowed byjev.429,529,5xx, or an exhausted quota (insufficient_quota). Do this only after the per-providerretryOn429budget is spent, and don't retryinsufficient_quotaagainst the same backend.jev-latest/openjev-0.1), key, headers and pacing.jevDecision.backend, plusfallbackFromwhen one was skipped).Ingwannu commentedon Oct 16, 2026
Current
dev@4ae49360b7already supports selecting one System One decision provider throughdecisionProvider, with destination-owned credentials, model, timeout and fail-open routing; seestructure/providers/jev-decision.md. That is not the ordered multi-backend chain, per-backend pacing/retry or telemetry proposed here.A focused design should extend that decision-service boundary rather than retarget the canonical jev credential. Please specify the Codiv protocol/endpoint and authenticated capability evidence, credential ownership, a single total deadline and send budget across backends, cancellation semantics, and the exact fail-open result after all backends fail. Caller history and credentials must not become fallback payloads.
Keeping this as needs-design; no canonical provider promotion or live request is approved by this triage.
nordz0r commentedon Oct 16, 2026
Design: ordered decision-backend chain (implements this proposal)
(animated PR Lens diagrams: architecture — chain runner dispatching TypeSafe → Codiv with
per-row pacing/retry/headers; data flow — fallback after a 429/529 budget exhaustion and the
fail-open result after all backends fail)
Design drafted for the four items in the issue. Summary below; it addresses the triage
requirements: protocol/capability evidence, credential ownership, a single total deadline and
send budget, cancellation semantics, and the exact fail-open result.
Protocol verification (Codiv)
POST https://api.codiv.ai/v1/systemoneis wire-compatible with TypeSafe JEV: samerequest/response fields, noul/choice/score answer shapes with
legend/confidence, the sameerror shapes (422 validation lists;
{"detail": {"error_type", "message"}}otherwise), and thex-typesafe-request-idheader — per codiv.ai/docs/guides/jev-compatibility.md (checked2026-10-17). The existing question builder and answer parser therefore work unchanged: Codiv is
just a
jev-decisionrow withbaseUrl: https://api.codiv.ai/v1/systemone. No new adapter, noregistry entry.
openjev-latest(canonical) oropenjev-0.1(pinned);jev-latestis accepted as analias, so a row may even keep the default model.
criteriaaccepts{option: description-or-null}, so the self-hosted (descriptive-criteria)question path works as-is. Codiv extensions (
images,steps,samples,think,sequential) are purely additive; opencodex never sends them, requests stay Jev-identical.1. Config shape
{ "providers": { "codiv": { "adapter": "jev-decision", "baseUrl": "https://api.codiv.ai/v1/systemone", "apiKey": "${CODIV_API_KEY}", "headers": { "User-Agent": "my-client/1.0" }, "requestPacing": { "enabled": true, "requestsPerMinute": 250, "maxConcurrentRequests": 8 }, "retryOn429": { "enabled": true, "attempts": 3, "intervalMs": 5000, "maxIntervalMs": 30000 } } }, "combos": { "jev-auto": { "strategy": "jev", "decisionProviders": [ { "provider": "jev" }, { "provider": "codiv", "model": "openjev-0.1" } ], "decisionTimeoutMs": 4000, "targets": [ "..." ] } } }New optional combo field
decisionProviders: [{ provider, model? }, …](1–4 entries, noduplicate provider ids), mutually exclusive with
decisionProvider/decisionModel. Everynon-
jeventry must pass exactly today'sdecisionProvidervalidation (adapter: "jev-decision", HTTPS System One endpoint or local HTTP/systemone, own-key ownership,disabled/model-less refusal under
requireUsableDecisionService) — shared helper so the twoboundaries cannot drift. Without the field, behavior is byte-identical to today (existing
tests, including the TypeSafe request golden, stay authoritative).
2. Chain semantics
flowchart TD A[decision request] --> B{decisionModel?} B -- yes --> M[model backend, unchanged] B -- no --> C[chain: decisionProviders or implicit single] C --> E[exchange + pacing slot] E -->|apply| D[decision: backend + provider + fallbackFrom] E -->|no_choices / no_state / invalid| F[fail open, gate reported] E -->|missing_key| G{more backends?} G -- yes --> C G -- no --> F E -->|http 429/529| H[retryOn429 budget: Retry-After or backoff] H --> E H -->|budget spent| G E -->|network / timeout / redirect / malformed| GGate classification:
no_choices,no_state,invalid—properties of the question, not the service; another backend cannot answer them.
missing_key— a disabled or keyless row is skipped, which is thepoint of the chain. On a degenerate single-entry chain this keeps today's
missing_keyfail-open result.
http,network,timeout,redirect,malformed.with the last gate recorded.
3. Retry, pacing, 529, headers
retryOn429(the existing policy block; presence = opt-in, defaults attempts=3 /intervalMs=5000 / maxIntervalMs=60000 / Retry-After respected) retries 429 and 529. The
exchange gains an optional
statuson itshttpgate; the retry loop lives in a chain runner,not inside the exchange, so the exchange's load-bearing cancellation contract stays untouched.
Waits honor
Retry-Afterup to the 60 s ceiling, else capped exponential backoff; a wait isbounded by the remaining chain deadline and aborts with the caller's reason.
requestPacing(requestsPerMinute,maxConcurrentRequests, per-model overrides) applies todecision sends through the same slot primitives the inference path uses; an absent/disabled
block is a zero-overhead no-op. A pacing-queue overload advances the chain (same as a 429),
it does not fail the decision.
headersare forwarded on decision requests (Codiv's edge requires a User-Agent;without it Cloudflare answers 403/1010). Credential-shaped configured headers
(
authorization,x-api-key,x-goog-api-key) are filtered before merge; the row's ownapiKeyremains the only credential, andAuthorization/Content-Typeare applied last.4. Deadline and send budget
decisionTimeoutMs(default 4 s, bounds 1000..120000 unchanged) budgetsthe whole chain; each backend's attempts draw from the remainder. A
timeoutgate isdeadline expiry during that backend's turn.
against a 4×20-attempt config with a 120 s deadline.
post-response, parse); retry waits re-check it. Caller history and caller credentials never
become fallback payloads — each backend sends only its own bounded question state.
5. Telemetry
jevDecision.provider(the id of the backend that decided) andjevDecision.fallbackFrom(ids tried and skipped before it), alongside the existingjevDecision.backend. Ids only — no extracted state, no credentials;privacy:scanstaysgreen. The per-backend usage aggregate is unchanged; per-provider detail lives in history.
Out of scope here
The data-plane passthrough route (item 4 of the issue) is a separate unit: a new authenticated
ingress with its own admission scope and spend accounting. The chain runner is built
question-agnostic so the passthrough can reuse it later.
Related: #6268, #6384, #6665.