Skip to content
Merged
490 changes: 490 additions & 0 deletions devlog/_plan/260804_router_intelligence/000_master_plan.md

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions devlog/_plan/260804_router_intelligence/001_pr_stack_status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 001 - PR stack status ledger

Continuously updated during the programme. Every branch records: base SHA,
head SHA, PR number/URL, verification result, and review state.

## Programme facts

- Stack base (dev): `e44d234f08e03dd4dbf0c4aa13af43046d86b0a6` (`upstream/dev`)
- `origin/dev` (fork, stale ancestor): `be177ea501e5007f4a56d19d069ef5cd76ea24b9`
- Bun: `1.3.14`; package version: `2.10.0`
- Worktree: `D:\codex-worktrees\ocx-router-intelligence`
- Push remote: `origin` (Wibias/opencodex); PR target: `lidge-jun/opencodex:dev`
- All PRs opened as DRAFT; nothing merged by this programme.

## Related in-flight PRs (not superseded by this stack)

| PR | Branch | Note |
|---|---|---|
| #922 | `fix/914-account-neutral-network` | #914 alternative; consumed as health evidence input by RI-06 |
| #966 | `codex/260804-issue914-transport-attribution` | #914 alternative; consumed as health evidence input by RI-06 |
| #715 | `feat/priority-levels` | Pool selection order; out of scope |
| #988 | `codex/providers-copy-doctor` | GUI providers/combos; conflict-checked at RI-10 |
| #998 | `codex/260803-integration-switches` | Write substrate; rebase watch on request-log.ts |

No open PR found that implements the same vertical as any PR in this stack,
so no stale PR is closed by this programme. Both #914 drafts overlap each
other; closing one is a maintainer decision and neither is stale.

## Baseline

- Full-suite baseline on clean `upstream/dev` (worktree
`D:\codex-worktrees\ocx-typecheck-base`, head `e44d234f0`): running in
background; exact pass/fail counts appended here when done.
- `bun x tsc --noEmit` on clean `upstream/dev`: **PASSED** (0 errors, verified
in the pristine base worktree).
- `bun run privacy:scan`: passed per-PR (see RI-01 below).

## Stack status

| RI | Branch | Base | Head SHA | PR | URL | Status |
|---|---|---|---|---|---|---|
| RI-01 | `feat/ri-01-route-decision-traces` | `e44d234f0` | pending | pending | pending | in progress |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| RI-02 | `feat/ri-02-request-history-index` | `feat/ri-01` head | pending | pending | pending | queued |
| RI-03 | `feat/ri-03-routing-analytics` | `feat/ri-02` head | pending | pending | pending | queued |
| RI-04 | `feat/ri-04-policy-profile-core` | `feat/ri-03` head | pending | pending | pending | queued |
| RI-05 | `feat/ri-05-capability-aware-routing` | `feat/ri-04` head | pending | pending | pending | queued |
| RI-06 | `feat/ri-06-health-aware-routing` | `feat/ri-05` head | pending | pending | pending | queued |
| RI-07 | `feat/ri-07-quota-aware-routing` | `feat/ri-06` head | pending | pending | pending | queued |
| RI-08 | `feat/ri-08-cost-aware-routing` | `feat/ri-07` head | pending | pending | pending | queued |
| RI-09 | `feat/ri-09-route-explainability-api` | `feat/ri-08` head | pending | pending | pending | queued |
| RI-10 | `feat/ri-10-routing-intelligence-ui` | `feat/ri-09` head | pending | pending | pending | queued |

## Per-PR acceptance log

### RI-01 - feat/ri-01-route-decision-traces

- Base SHA: `e44d234f08e03dd4dbf0c4aa13af43046d86b0a6`
- Reviewed commits:
- `b5a8e7c4c` (implementation; author self-review + CodeRabbit review)
- `2e0522b2` (privacy-scan fix after CI `gates` failure)
- `pending` (CodeRabbit findings round; recorded after commit)
- Findings (self-review): 3 test failures caught pre-push - (1) missing value
import for `normalizeRouteDecisionTrace` in request-log hydration,
(2) selected combo target marked ineligible because `ComboPick.attempted`
includes the winner, (3) account-namespace fixture missing the canonical
ChatGPT forward `baseUrl` (test-fixture bug, not product code).
- Fixes: import fixed; `comboRouteCandidates` now excludes the selected target
from `already-attempted`; fixture uses `https://chatgpt.com/backend-api/codex`.
- Regression tests: all three cases are covered by the final
`tests/route-decision-trace.test.ts` (14 tests, 75 assertions).
- Findings (CodeRabbit, verified against code): 12 comments - 9 accepted
(locale/plan docs, requestedModel bound doc, ledger SHA, combo tieBreak +
duplicate getCombo, `truncated.requirements` flag, byte-accurate budget,
parse-once evidence, hydration guard drops invalid traces, 2 regression
tests, credential-test assertion hardening); 2 design-judgment comments
(persist trace on every row - kept: bounded ~200 B single-candidate traces,
plan mandates one trace per decision; docstring coverage - docstrings
added to trace helpers); the privacy-scan finding was already fixed in
`2e0522b2`.
- Final commit: recorded after commit (round applies CodeRabbit + simplify
fixes; new head pushes to #1003)
- Verification:
- `bun x tsc --noEmit`: PASSED (0 errors)
- `bun run test tests/route-decision-trace.test.ts`: 14/14 pass
- Focused regression suites: 253/253 pass across combos, codex-routing,
usage-log, request-log, combo-management-api, codex-account-namespaces
- `tests/server-combo-failover-e2e.test.ts`: 44/44 pass
- `bun run privacy:scan`: passed
- Remaining Low findings: none

### RI-02..RI-10

Appended as each PR is implemented.
151 changes: 139 additions & 12 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
import { preservesPhysicalComboProvider, tryPickComboModel, type ComboPick } from "./combos";
import {
getCombo,
isComboTargetInCooldown,
preservesPhysicalComboProvider,
targetKey,
tryPickComboModel,
type ComboPick,
} from "./combos";
import type { NormalizedComboConfig } from "./combos/types";
import { hasOwnProvider, resolveEnvValue } from "./config";
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
import { redactSecretString, redactUrlForLog } from "./lib/redact";
Expand All @@ -14,17 +22,29 @@ import {
import { decodeRoutedModelId, encodeRoutedModelId } from "./providers/slug-codec";
import { getStaleCached } from "./codex/model-cache";
import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
import {
buildRouteDecisionTrace,
type RouteDecisionKind,
type RouteDecisionTraceV1,
type TraceCandidateInput,
} from "./routing/trace";

export interface RouteResult {
providerName: string;
provider: OcxProviderConfig;
modelId: string;
/** Which deterministic routing path produced this route (RI-01). */
routeKind: RouteDecisionKind;
/** Stable wire reason code for the selected route (RI-01). */
routeReason: string;
codexAccountMode?: CodexAccountMode;
/** Exact account selected by an account-qualified native model. */
codexAccountId?: string;
/** Public namespace used by the account-qualified selector. */
codexAccountNamespace?: string;
combo?: ComboPick;
/** Bounded route-decision trace (RI-01); never contains secrets. */
routeDecision?: RouteDecisionTraceV1;
}

const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
Expand Down Expand Up @@ -335,6 +355,34 @@ export class NoEnabledOpenAiProviderError extends Error {
}
}

/**
* One immutable selection trace for a combo request: built once from the
* initial pick, before any child dispatch. Fallback execution stays in the
* usage entry's `attempts[]`; the trace never changes after selection.
*/
export function comboRouteDecisionTrace(
config: OcxConfig,
comboId: string,
pick: ComboPick,
requestedModel: string,
): RouteDecisionTraceV1 {
const combo = getCombo(config, comboId);
return buildRouteDecisionTrace({
requestedModel,
routeKind: "combo",
selected: {
provider: pick.target.provider,
model: pick.target.model,
reason: "combo-pick",
candidateIndex: pick.targetIndex,
...(combo
? { tieBreak: combo.strategy === "round-robin" ? "round-robin" : "failover" }
: {}),
},
candidates: combo ? comboRouteCandidates(config, pick, combo) : undefined,
});
}

// Codex uses a small number of control-plane model ids that are not part of the public GPT/o
// naming families. Keep this exact: a broad `codex-*` rule could capture a third-party model.
const CODEX_INTERNAL_OPENAI_MODELS = new Set(["codex-auto-review"]);
Expand All @@ -344,16 +392,63 @@ function isBareOpenAiFamilyModel(modelId: string): boolean {
&& (/^(?:gpt-|o1-|o3-|o4-)/.test(modelId) || CODEX_INTERNAL_OPENAI_MODELS.has(modelId));
}

function routeResult(providerName: string, provider: OcxProviderConfig, modelId: string): RouteResult {
function routeResult(
providerName: string,
provider: OcxProviderConfig,
modelId: string,
routeKind: RouteDecisionKind,
routeReason: string,
): RouteResult {
const codexAccountMode = providerCodexAccountMode(providerName, provider);
return {
providerName,
provider: routedProviderConfig(providerName, provider),
modelId,
routeKind,
routeReason,
...(codexAccountMode ? { codexAccountMode } : {}),
};
}

/**
* Candidate evidence for a combo route: every configured target with its
* selection-time eligibility and exclusion reasons. Purely observational; the
* pick already happened and this never re-selects.
*/
function comboRouteCandidates(
config: OcxConfig,
pick: NonNullable<RouteResult["combo"]>,
combo: NormalizedComboConfig,
): TraceCandidateInput[] {
const now = Date.now();
return combo.targets.map((target, index) => {
const key = targetKey(target);
const provider = config.providers[target.provider];
const configured = provider !== undefined;
const enabled = configured && provider.disabled !== true;
const inCooldown = isComboTargetInCooldown(pick.comboId, target, now);
const isSelected = index === pick.targetIndex;
// The pick's `attempted` list includes the winner itself; only non-selected
// targets can be "already-attempted" (fallback picks exclude earlier tries).
const alreadyAttempted = !isSelected && pick.attempted.includes(key);
const exclusions: TraceCandidateInput["exclusions"] = [];
if (!configured) exclusions.push({ code: "unconfigured" });
if (configured && !enabled) exclusions.push({ code: "disabled" });
if (inCooldown) exclusions.push({ code: "cooldown" });
if (isSelected && inCooldown) exclusions.push({ code: "selected-despite-cooldown" });
if (!isSelected && alreadyAttempted && exclusions.length === 0) {
exclusions.push({ code: "already-attempted" });
}
if (!isSelected && exclusions.length === 0) exclusions.push({ code: "not-selected" });
return {
provider: target.provider,
model: target.model,
eligible: enabled && !inCooldown && !alreadyAttempted,
exclusions,
};
});
}

function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: boolean): RouteResult {
const slash = modelId.indexOf("/");
if (slash > 0) {
Expand All @@ -378,7 +473,7 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
throw new NoEnabledOpenAiProviderError(nativeModelId);
}
return {
...routeResult(OPENAI_CODEX_PROVIDER_ID, provider, nativeModelId),
...routeResult(OPENAI_CODEX_PROVIDER_ID, provider, nativeModelId, "explicit-account", "account-namespace"),
// Exact account injection uses the pool credential machinery even when the canonical
// provider is globally Direct. The fixed id bypasses pool selection entirely.
codexAccountMode: "pool",
Expand All @@ -395,7 +490,7 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
// The selected target is already a concrete provider/model reference. Resolve it without
// consulting combo aliases again, otherwise an alias that shadows the target can recurse.
const routed = routeModelInternal(config, concrete, true);
return { ...routed, combo };
return { ...routed, combo, routeKind: "combo" as const, routeReason: "combo-pick" };
}
}

Expand All @@ -415,23 +510,33 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
// Self-namespaced native id — the vendor segment equals the provider id, so the FULL ref is
// itself a known model (e.g. orcarouter/auto). Route it whole instead of stripping to the
// remainder, which would send a bare `auto` the upstream cannot resolve.
if (known.includes(modelId)) return routeResult(provName, prov, modelId);
if (known.includes(modelId)) {
return routeResult(provName, prov, modelId, "explicit-provider", "explicit-provider-namespace");
}
// Codex-facing alias ids (`provider/vendor-model`) decode back to the native
// slash id via an exact known-id lookup; raw full-slash selectors keep working.
return routeResult(provName, prov, decodeRoutedModelId(modelId.slice(slash + 1), known));
return routeResult(
provName,
prov,
decodeRoutedModelId(modelId.slice(slash + 1), known),
"explicit-provider",
"explicit-provider-namespace",
);
}
}

if (isBareOpenAiFamilyModel(modelId)) {
const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
if (provider && provider.disabled !== true) return routeResult(OPENAI_CODEX_PROVIDER_ID, provider, modelId);
if (provider && provider.disabled !== true) {
return routeResult(OPENAI_CODEX_PROVIDER_ID, provider, modelId, "native", "native-family");
}
throw new NoEnabledOpenAiProviderError(modelId);
}

for (const [provName, prov] of activeProviderEntries(config)) {
if (prov.defaultModel === modelId
|| (typeof prov.defaultModel === "string" && encodeRoutedModelId(prov.defaultModel) === modelId)) {
return routeResult(provName, prov, prov.defaultModel as string);
return routeResult(provName, prov, prov.defaultModel as string, "explicit-provider", "configured-default-model");
}
}

Expand All @@ -441,7 +546,9 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
for (const [provName, prov] of activeProviderEntries(config)) {
if (prov.models && Array.isArray(prov.models)) {
const hit = (prov.models as string[]).find(id => id === modelId || encodeRoutedModelId(id) === modelId);
if (hit !== undefined) return routeResult(provName, prov, hit);
if (hit !== undefined) {
return routeResult(provName, prov, hit, "explicit-provider", "configured-model-list");
}
}
}

Expand All @@ -451,14 +558,34 @@ function routeModelInternal(config: OcxConfig, modelId: string, bypassCombos: bo
if (hasOwnProvider(config.providers, config.defaultProvider)) {
const defaultProv = config.providers[config.defaultProvider];
if (defaultProv.disabled === true) throw new Error(`Default provider is disabled: ${config.defaultProvider}`);
return routeResult(config.defaultProvider, defaultProv, modelId);
return routeResult(config.defaultProvider, defaultProv, modelId, "default-provider", "default-provider");
}

throw new Error(`No provider configured for model: ${modelId}`);
}

export function routeModel(config: OcxConfig, modelId: string): RouteResult {
return routeModelInternal(config, modelId, false);
const route = routeModelInternal(config, modelId, false);
const accountRef = route.codexAccountNamespace;
const combo = route.combo ? getCombo(config, route.combo.comboId) : undefined;
route.routeDecision = buildRouteDecisionTrace({
requestedModel: modelId,
routeKind: route.routeKind,
selected: {
provider: route.providerName,
model: route.modelId,
...(accountRef ? { accountRef } : {}),
reason: route.routeReason,
...(route.combo ? { candidateIndex: route.combo.targetIndex } : {}),
...(combo
? { tieBreak: combo.strategy === "round-robin" ? "round-robin" : "failover" }
: {}),
},
candidates: route.routeKind === "combo" && route.combo && combo
? comboRouteCandidates(config, route.combo, combo)
: undefined,
});
return route;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

function routeByKnownModelPattern(config: OcxConfig, modelId: string): RouteResult | undefined {
Expand All @@ -469,7 +596,7 @@ function routeByKnownModelPattern(config: OcxConfig, modelId: string): RouteResu
);
if (matchingProvider) {
const [provName, prov] = matchingProvider;
return routeResult(provName, prov, modelId);
return routeResult(provName, prov, modelId, "explicit-provider", "model-pattern");
}
}
}
Expand Down
Loading
Loading