pi-smart-router 0.12.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/extensions/smart-router/extension-setup.ts +1 -0
- package/.pi/extensions/smart-router/fleet-bootstrap.ts +51 -1
- package/.pi/extensions/smart-router/planning-delegate.ts +124 -4
- package/.pi/extensions/smart-router/types.ts +3 -0
- package/README.md +80 -1
- package/config/benchmark-profiles.json +23 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/domain/pinning/loop-escalation.d.ts.map +1 -1
- package/dist/domain/pinning/loop-escalation.js +24 -1
- package/dist/domain/pinning/loop-escalation.js.map +1 -1
- package/dist/domain/pinning/session-pinner.d.ts +22 -2
- package/dist/domain/pinning/session-pinner.d.ts.map +1 -1
- package/dist/domain/pinning/session-pinner.js +43 -9
- package/dist/domain/pinning/session-pinner.js.map +1 -1
- package/dist/domain/pipeline/router-pipeline.d.ts +31 -0
- package/dist/domain/pipeline/router-pipeline.d.ts.map +1 -1
- package/dist/domain/pipeline/router-pipeline.js +191 -8
- package/dist/domain/pipeline/router-pipeline.js.map +1 -1
- package/dist/domain/pricing/quota-window-feed.d.ts +88 -0
- package/dist/domain/pricing/quota-window-feed.d.ts.map +1 -0
- package/dist/domain/pricing/quota-window-feed.js +159 -0
- package/dist/domain/pricing/quota-window-feed.js.map +1 -0
- package/dist/domain/routing/degraded-route-sandwich.d.ts +161 -0
- package/dist/domain/routing/degraded-route-sandwich.d.ts.map +1 -0
- package/dist/domain/routing/degraded-route-sandwich.js +309 -0
- package/dist/domain/routing/degraded-route-sandwich.js.map +1 -0
- package/dist/domain/types/entities.d.ts +40 -0
- package/dist/domain/types/entities.d.ts.map +1 -1
- package/dist/domain/types/index.d.ts +1 -1
- package/dist/domain/types/index.d.ts.map +1 -1
- package/dist/domain/types/schemas.d.ts +29 -2
- package/dist/domain/types/schemas.d.ts.map +1 -1
- package/dist/domain/types/schemas.js +47 -2
- package/dist/domain/types/schemas.js.map +1 -1
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts +15 -4
- package/dist/infrastructure/telemetry/routing-telemetry.d.ts.map +1 -1
- package/dist/infrastructure/telemetry/routing-telemetry.js +21 -6
- package/dist/infrastructure/telemetry/routing-telemetry.js.map +1 -1
- package/package.json +3 -3
- package/src/config/defaults.ts +2 -0
- package/src/domain/pinning/loop-escalation.ts +25 -1
- package/src/domain/pinning/session-pinner.ts +59 -11
- package/src/domain/pipeline/router-pipeline.ts +229 -9
- package/src/domain/pricing/quota-window-feed.ts +240 -0
- package/src/domain/routing/degraded-route-sandwich.ts +478 -0
- package/src/domain/types/entities.ts +41 -0
- package/src/domain/types/index.ts +1 -0
- package/src/domain/types/schemas.ts +55 -2
- package/src/infrastructure/telemetry/routing-telemetry.ts +39 -3
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
createOnnxEmbeddingProvider,
|
|
16
16
|
} from '../../../src/domain/matching/hydra-matcher.js';
|
|
17
17
|
import { SessionPinner } from '../../../src/domain/pinning/session-pinner.js';
|
|
18
|
+
import {
|
|
19
|
+
collectPoolModelIds,
|
|
20
|
+
resolveQuotaWindowEstimateConfigFromEnv,
|
|
21
|
+
resolveQuotaWindowPosition,
|
|
22
|
+
type QuotaWindowAdapter,
|
|
23
|
+
type QuotaWindowEstimateConfig,
|
|
24
|
+
} from '../../../src/domain/pricing/quota-window-feed.js';
|
|
18
25
|
import type { ModelProfile, PriceCatalog } from '../../../src/domain/types/index.js';
|
|
19
26
|
import type { QuotaWindowPosition } from '../../../src/domain/types/entities.js';
|
|
20
27
|
import type { OperatorConfig } from '../../../src/domain/types/schemas.js';
|
|
@@ -38,10 +45,51 @@ export interface CreateDispatchOptionsExtras {
|
|
|
38
45
|
readonly operatorConfig?: OperatorConfig;
|
|
39
46
|
/** Live price catalog when fleet discovery has loaded one. */
|
|
40
47
|
readonly priceCatalog?: PriceCatalog | null;
|
|
41
|
-
/** Rolling subscription quota position when available. */
|
|
48
|
+
/** Rolling subscription quota position when available (see resolveQuotaWindowFeedPosition, SP-214). */
|
|
42
49
|
readonly quotaWindowPosition?: QuotaWindowPosition;
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
/** Max telemetry rows scanned for the quota-window burn estimate (SP-214). */
|
|
53
|
+
const QUOTA_FEED_TELEMETRY_LIMIT = 5000;
|
|
54
|
+
|
|
55
|
+
export interface ResolveQuotaWindowFeedDeps {
|
|
56
|
+
/** Optional provider adapter (degrade chain step 1). */
|
|
57
|
+
readonly adapter?: QuotaWindowAdapter;
|
|
58
|
+
/** Estimate config override; defaults to env-resolved config. */
|
|
59
|
+
readonly estimateConfig?: QuotaWindowEstimateConfig;
|
|
60
|
+
readonly now?: Date;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the pool-level `QuotaWindowPosition` for the fleet via the SP-214
|
|
65
|
+
* degrade chain: adapter → telemetry burn estimate → omit. Returns `undefined`
|
|
66
|
+
* when the fleet has no subscription pool and no adapter, or when the feed is
|
|
67
|
+
* disabled — callers then fall back to flat virtual cost + SP-097 failover.
|
|
68
|
+
*/
|
|
69
|
+
export async function resolveQuotaWindowFeedPosition(
|
|
70
|
+
store: StorePort,
|
|
71
|
+
fleet: readonly ModelProfile[],
|
|
72
|
+
deps?: ResolveQuotaWindowFeedDeps,
|
|
73
|
+
): Promise<QuotaWindowPosition | undefined> {
|
|
74
|
+
const poolModelIds = collectPoolModelIds(fleet);
|
|
75
|
+
if (poolModelIds.size === 0 && !deps?.adapter) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const estimateConfig =
|
|
79
|
+
deps?.estimateConfig ?? resolveQuotaWindowEstimateConfigFromEnv();
|
|
80
|
+
const entries =
|
|
81
|
+
poolModelIds.size > 0
|
|
82
|
+
? await store.listTelemetry({ limit: QUOTA_FEED_TELEMETRY_LIMIT })
|
|
83
|
+
: [];
|
|
84
|
+
return resolveQuotaWindowPosition({
|
|
85
|
+
adapter: deps?.adapter,
|
|
86
|
+
entries,
|
|
87
|
+
poolModelIds,
|
|
88
|
+
estimateConfig,
|
|
89
|
+
...(deps?.now !== undefined ? { now: deps.now } : {}),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
45
93
|
/** Minimal settings surface used for scoped fleet discovery. */
|
|
46
94
|
export interface ScopedSettingsReader {
|
|
47
95
|
getEnabledModels(): string[] | null | undefined;
|
|
@@ -235,9 +283,11 @@ export async function rebuildFleet(
|
|
|
235
283
|
);
|
|
236
284
|
runtime.priceCatalog = catalog;
|
|
237
285
|
runtime.fleetScopeFingerprint = fingerprint;
|
|
286
|
+
const quotaWindowPosition = await resolveQuotaWindowFeedPosition(runtime.store, fleet);
|
|
238
287
|
const router = createRouterFromFleet(fleet, {
|
|
239
288
|
...createDispatchOptions(runtime.store, runtime.sessionPinner, runtime.hydraMatcher, {
|
|
240
289
|
priceCatalog: catalog,
|
|
290
|
+
...(quotaWindowPosition !== undefined ? { quotaWindowPosition } : {}),
|
|
241
291
|
}),
|
|
242
292
|
lifecycleHookState: runtime.lifecycleHookState,
|
|
243
293
|
});
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* on compressed context, inject the result as an observation, and keep primary
|
|
6
6
|
* inference on the pinned economical model. Falls back to direct frontier routing
|
|
7
7
|
* when sub-agent spawn is unavailable (pi has no native sub-agent API yet).
|
|
8
|
+
*
|
|
9
|
+
* SP-213 / #120: sub-calls are bounded by a global stage timeout and a per-call
|
|
10
|
+
* worker timeout (llm-use WORKER_GLOBAL_TIMEOUT / WORKER_CALL_TIMEOUT pattern).
|
|
11
|
+
* On timeout the worker is cancelled/abandoned, `planning_delegate_timeout` is
|
|
12
|
+
* recorded, and routing falls back to the direct frontier path — never a hang.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
import {
|
|
@@ -22,10 +27,12 @@ import type {
|
|
|
22
27
|
PlanningDelegateObservability,
|
|
23
28
|
RoutingDecision,
|
|
24
29
|
} from '../../../src/domain/types/index.js';
|
|
30
|
+
import { DEFAULT_PLANNING_DELEGATE_CONFIG } from '../../../src/domain/types/schemas.js';
|
|
25
31
|
import {
|
|
26
32
|
createPlanningDelegateObservability,
|
|
27
33
|
enrichRoutingDecisionWithPlanningDelegate,
|
|
28
34
|
PLANNING_DELEGATE,
|
|
35
|
+
PLANNING_DELEGATE_TIMEOUT,
|
|
29
36
|
PLANNING_DELEGATE_UNAVAILABLE,
|
|
30
37
|
PLANNING_DIRECT_FRONTIER,
|
|
31
38
|
} from '../../../src/infrastructure/telemetry/routing-telemetry.js';
|
|
@@ -222,6 +229,65 @@ export interface PlanningDelegateResolution {
|
|
|
222
229
|
readonly usedDelegatePath: boolean;
|
|
223
230
|
}
|
|
224
231
|
|
|
232
|
+
/** Worker telemetry analogs for one planning turn (SP-213, #120). */
|
|
233
|
+
interface DelegateWorkerTelemetry {
|
|
234
|
+
readonly workers_spawned: number;
|
|
235
|
+
readonly workers_succeeded: number;
|
|
236
|
+
readonly worker_timeout_count: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Race a delegate sub-call against a bounded timeout (SP-213, #120).
|
|
241
|
+
*
|
|
242
|
+
* On expiry the worker is signalled for cancellation (AbortSignal forwarded to
|
|
243
|
+
* the sub-call options) and abandoned — the race resolves immediately so a
|
|
244
|
+
* stalled worker can never hang TTFT. An outer caller abort is forwarded to
|
|
245
|
+
* the worker as well. No retries and no queue: exactly one worker per call.
|
|
246
|
+
*/
|
|
247
|
+
async function spawnPlanningDelegateWithTimeout(
|
|
248
|
+
spawnFn: PlanningDelegateSpawnFn,
|
|
249
|
+
frontierModel: Model<Api>,
|
|
250
|
+
compressedContext: Context,
|
|
251
|
+
options: SimpleStreamOptions | undefined,
|
|
252
|
+
deps: StreamDelegationDeps,
|
|
253
|
+
timeoutMs: number,
|
|
254
|
+
): Promise<PlanningDelegateSpawnResult> {
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const outerSignal = options?.signal;
|
|
257
|
+
const forwardOuterAbort = (): void => controller.abort();
|
|
258
|
+
if (outerSignal) {
|
|
259
|
+
if (outerSignal.aborted) {
|
|
260
|
+
controller.abort();
|
|
261
|
+
} else {
|
|
262
|
+
outerSignal.addEventListener('abort', forwardOuterAbort, { once: true });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
267
|
+
try {
|
|
268
|
+
const spawnOptions: SimpleStreamOptions = {
|
|
269
|
+
...(options ?? {}),
|
|
270
|
+
signal: controller.signal,
|
|
271
|
+
};
|
|
272
|
+
const spawnPromise = spawnFn(frontierModel, compressedContext, spawnOptions, deps);
|
|
273
|
+
// Swallow late rejections from an abandoned worker so a timeout never
|
|
274
|
+
// surfaces as an unhandled rejection after the fallback already routed.
|
|
275
|
+
spawnPromise.catch(() => {});
|
|
276
|
+
const timeoutPromise = new Promise<PlanningDelegateSpawnResult>((resolve) => {
|
|
277
|
+
timer = setTimeout(() => {
|
|
278
|
+
controller.abort();
|
|
279
|
+
resolve({ ok: false, reason: PLANNING_DELEGATE_TIMEOUT });
|
|
280
|
+
}, timeoutMs);
|
|
281
|
+
});
|
|
282
|
+
return await Promise.race([spawnPromise, timeoutPromise]);
|
|
283
|
+
} finally {
|
|
284
|
+
if (timer) {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
}
|
|
287
|
+
outerSignal?.removeEventListener('abort', forwardOuterAbort);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
225
291
|
/**
|
|
226
292
|
* Resolve planning delegate path: sub-call + observation injection, or direct frontier fallback.
|
|
227
293
|
*/
|
|
@@ -237,6 +303,10 @@ export async function resolvePlanningDelegatePath(
|
|
|
237
303
|
const observability = decision.features!.planning_delegate!;
|
|
238
304
|
const delegateModelId = observability.delegate_model_id!;
|
|
239
305
|
const primaryModelId = decision.selected_model_id;
|
|
306
|
+
const delegateConfig =
|
|
307
|
+
deps.planningDelegateConfig ?? DEFAULT_PLANNING_DELEGATE_CONFIG;
|
|
308
|
+
// Global stage deadline: bounds compression + sub-call wall-clock (SP-213, #120).
|
|
309
|
+
const globalDeadlineMs = Date.now() + delegateConfig.global_timeout_ms;
|
|
240
310
|
|
|
241
311
|
const frontierProfile = findFleetProfile(deps.fleet, delegateModelId);
|
|
242
312
|
const frontierModel = frontierProfile
|
|
@@ -254,6 +324,7 @@ export async function resolvePlanningDelegatePath(
|
|
|
254
324
|
delegateModelId,
|
|
255
325
|
PLANNING_DELEGATE_UNAVAILABLE,
|
|
256
326
|
deps,
|
|
327
|
+
{ workers_spawned: 0, workers_succeeded: 0, worker_timeout_count: 0 },
|
|
257
328
|
);
|
|
258
329
|
}
|
|
259
330
|
|
|
@@ -262,20 +333,60 @@ export async function resolvePlanningDelegatePath(
|
|
|
262
333
|
observability.compressed_context,
|
|
263
334
|
);
|
|
264
335
|
throwIfAborted(options);
|
|
336
|
+
|
|
337
|
+
const remainingGlobalMs = globalDeadlineMs - Date.now();
|
|
338
|
+
if (remainingGlobalMs <= 0) {
|
|
339
|
+
console.warn(
|
|
340
|
+
'[smart-router] planning delegate global timeout exhausted before sub-call, falling back to direct frontier route',
|
|
341
|
+
delegateModelId,
|
|
342
|
+
);
|
|
343
|
+
return applyPlanningDelegateDirectFallback(
|
|
344
|
+
context,
|
|
345
|
+
decision,
|
|
346
|
+
delegateModelId,
|
|
347
|
+
PLANNING_DELEGATE_TIMEOUT,
|
|
348
|
+
deps,
|
|
349
|
+
{ workers_spawned: 0, workers_succeeded: 0, worker_timeout_count: 1 },
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
265
353
|
const spawnFn = deps.spawnPlanningDelegate ?? defaultSpawnPlanningDelegate;
|
|
266
|
-
|
|
354
|
+
// Per-call cap, further bounded by the remaining global budget.
|
|
355
|
+
const subCallTimeoutMs = Math.min(
|
|
356
|
+
delegateConfig.sub_call_timeout_ms,
|
|
357
|
+
remainingGlobalMs,
|
|
358
|
+
);
|
|
359
|
+
const spawnResult = await spawnPlanningDelegateWithTimeout(
|
|
360
|
+
spawnFn,
|
|
361
|
+
frontierModel,
|
|
362
|
+
compressedContext,
|
|
363
|
+
options,
|
|
364
|
+
deps,
|
|
365
|
+
subCallTimeoutMs,
|
|
366
|
+
);
|
|
267
367
|
|
|
268
368
|
if (!spawnResult.ok) {
|
|
369
|
+
const timedOut = spawnResult.reason === PLANNING_DELEGATE_TIMEOUT;
|
|
370
|
+
const fallbackReason = timedOut
|
|
371
|
+
? PLANNING_DELEGATE_TIMEOUT
|
|
372
|
+
: PLANNING_DELEGATE_UNAVAILABLE;
|
|
269
373
|
console.warn(
|
|
270
|
-
|
|
374
|
+
timedOut
|
|
375
|
+
? '[smart-router] planning delegate sub-call timed out, falling back to direct frontier route'
|
|
376
|
+
: '[smart-router] planning delegate sub-call failed, falling back to direct frontier route',
|
|
271
377
|
spawnResult.reason,
|
|
272
378
|
);
|
|
273
379
|
return applyPlanningDelegateDirectFallback(
|
|
274
380
|
context,
|
|
275
381
|
decision,
|
|
276
382
|
delegateModelId,
|
|
277
|
-
|
|
383
|
+
fallbackReason,
|
|
278
384
|
deps,
|
|
385
|
+
{
|
|
386
|
+
workers_spawned: 1,
|
|
387
|
+
workers_succeeded: 0,
|
|
388
|
+
worker_timeout_count: timedOut ? 1 : 0,
|
|
389
|
+
},
|
|
279
390
|
);
|
|
280
391
|
}
|
|
281
392
|
|
|
@@ -290,7 +401,12 @@ export async function resolvePlanningDelegatePath(
|
|
|
290
401
|
|
|
291
402
|
return {
|
|
292
403
|
context: injectPlanningDelegateObservation(context, spawnResult.observationText),
|
|
293
|
-
decision,
|
|
404
|
+
decision: enrichRoutingDecisionWithPlanningDelegate(decision, {
|
|
405
|
+
...observability,
|
|
406
|
+
workers_spawned: 1,
|
|
407
|
+
workers_succeeded: 1,
|
|
408
|
+
worker_timeout_count: 0,
|
|
409
|
+
}),
|
|
294
410
|
targetModelId: primaryModelId,
|
|
295
411
|
usedDelegatePath: true,
|
|
296
412
|
};
|
|
@@ -302,6 +418,7 @@ function applyPlanningDelegateDirectFallback(
|
|
|
302
418
|
delegateModelId: string,
|
|
303
419
|
fallbackReason: string,
|
|
304
420
|
deps: StreamDelegationDeps,
|
|
421
|
+
workerTelemetry?: DelegateWorkerTelemetry,
|
|
305
422
|
): PlanningDelegateResolution {
|
|
306
423
|
const profile = findFleetProfile(deps.fleet, delegateModelId);
|
|
307
424
|
const fallbackDecision = enrichRoutingDecisionWithPlanningDelegate(
|
|
@@ -316,6 +433,9 @@ function applyPlanningDelegateDirectFallback(
|
|
|
316
433
|
delegate_model_id: delegateModelId,
|
|
317
434
|
planning_delegate_reason_code: PLANNING_DIRECT_FRONTIER,
|
|
318
435
|
fallback_reason: fallbackReason,
|
|
436
|
+
workers_spawned: workerTelemetry?.workers_spawned ?? null,
|
|
437
|
+
workers_succeeded: workerTelemetry?.workers_succeeded ?? null,
|
|
438
|
+
worker_timeout_count: workerTelemetry?.worker_timeout_count ?? null,
|
|
319
439
|
}),
|
|
320
440
|
);
|
|
321
441
|
|
|
@@ -12,6 +12,7 @@ import { ExecutionLedger } from '../../../src/domain/delegation/execution-ledger
|
|
|
12
12
|
import { SessionPinner } from '../../../src/domain/pinning/session-pinner.js';
|
|
13
13
|
import type {
|
|
14
14
|
ModelProfile,
|
|
15
|
+
PlanningDelegateConfig,
|
|
15
16
|
PriceCatalog,
|
|
16
17
|
RoutingDecision,
|
|
17
18
|
} from '../../../src/domain/types/index.js';
|
|
@@ -60,6 +61,8 @@ export interface StreamDelegationDeps {
|
|
|
60
61
|
delegateStream?: DelegateStreamFn;
|
|
61
62
|
/** Injectable planning delegate sub-call; production uses frontier stream delegate. */
|
|
62
63
|
spawnPlanningDelegate?: PlanningDelegateSpawnFn;
|
|
64
|
+
/** Planning delegate knobs incl. global + per-call timeout bounds (SP-213, #120). */
|
|
65
|
+
readonly planningDelegateConfig?: PlanningDelegateConfig;
|
|
63
66
|
readonly lifecycleHookState?: LifecycleHookState;
|
|
64
67
|
readonly datasetRecorder?: DatasetRecorder;
|
|
65
68
|
readonly outcomeRecorder?: OutcomeRecorder;
|
package/README.md
CHANGED
|
@@ -246,7 +246,7 @@ When you use `smart-router/auto`, the extension does **not** read `config/models
|
|
|
246
246
|
4. **Route** — `createRouterFromFleet()` runs the 12-stage pipeline on each request.
|
|
247
247
|
5. **Delegate** — The extension resolves the chosen model in the registry and forwards the stream via pi-ai's built-in provider APIs.
|
|
248
248
|
|
|
249
|
-
Unknown models receive conservative economical-cloud defaults. Local providers (`lmstudio`, `ollama`) map to `zero-tier`. Cursor provider models (`cursor/*`, `composer-*`, opaque id `default`) map to `frontier-cloud` with explicit capability defaults (SP-086, SP-098).
|
|
249
|
+
Unknown models receive conservative economical-cloud defaults. Local providers (`lmstudio`, `ollama`) map to `zero-tier`. Cursor provider models (`cursor/*`, `composer-*`, opaque id `default`) map to `frontier-cloud` with explicit capability defaults (SP-086, SP-098). Benchmark-grounded capability vectors (including multi-fleet `github-copilot/*`, Gemini, and Anthropic dogfood IDs, aliased family-by-family) are documented in the [capability profile coverage report](docs/capability-profile-coverage.md) ([#108](https://github.com/beettlle/pi-smart-router/issues/108) / [#124](https://github.com/beettlle/pi-smart-router/issues/124)).
|
|
250
250
|
|
|
251
251
|
To refresh after auth or settings changes, restart pi or `/reload` extensions.
|
|
252
252
|
|
|
@@ -368,6 +368,8 @@ Cluster IDs are stable reason-code prefixes (`cluster_low_stakes_general`, `clus
|
|
|
368
368
|
| `SMART_ROUTER_PLANNING_DELEGATE_MAX_MESSAGES` | `12` | Compressed-context message cap for frontier sub-call |
|
|
369
369
|
| `SMART_ROUTER_PLANNING_DELEGATE_MAX_TOKENS` | `16384` | Compressed-context token cap for frontier sub-call |
|
|
370
370
|
| `SMART_ROUTER_PLANNING_DELEGATE_EXCLUDE_EXECUTION_HISTORY` | `true` | Exclude tool execution history from delegate payload |
|
|
371
|
+
| `SMART_ROUTER_PLANNING_DELEGATE_GLOBAL_TIMEOUT_MS` | `120000` | Global cap (ms) on the whole planning-delegate stage per planning turn — bounds fan-out wall-clock so a stalled worker cannot hang TTFT ([#120](https://github.com/beettlle/pi-smart-router/issues/120)) |
|
|
372
|
+
| `SMART_ROUTER_PLANNING_DELEGATE_SUB_CALL_TIMEOUT_MS` | `30000` | Per-call cap (ms) on each delegate sub-call worker; on expiry the worker is cancelled/abandoned and routing falls back to direct frontier with `planning_delegate_timeout` ([#120](https://github.com/beettlle/pi-smart-router/issues/120)) |
|
|
371
373
|
| `SMART_ROUTER_PREFIX_CACHE_WEIGHT` | `0.20` | SAAR weight on warm prefix value in cache breakeven math (0–1; [#73](https://github.com/beettlle/pi-smart-router/issues/73)) |
|
|
372
374
|
| `SMART_ROUTER_IDLE_TIMEOUT_SECONDS` | `300` | SAAR idle seconds before pin reopens for full re-route |
|
|
373
375
|
| `SMART_ROUTER_SWITCH_THRESHOLD` | `0.5` | SAAR switch score gate (0–1) for tier upgrades during hard-lock |
|
|
@@ -442,6 +444,25 @@ When a **planning** turn would route primary inference to frontier while a warm
|
|
|
442
444
|
|
|
443
445
|
See [routing-roadmap.md](docs/routing-roadmap.md) §2 P0 and GitHub [#71](https://github.com/beettlle/pi-smart-router/issues/71) for acceptance criteria.
|
|
444
446
|
|
|
447
|
+
### Degraded neural failover sandwich (#119)
|
|
448
|
+
|
|
449
|
+
When the encoder/neural stage (HyDRA) **fails, is misconfigured, or exceeds its latency budget without a selection**, routing fails open through a cheap chain instead of crashing the host agent ([#119](https://github.com/beettlle/pi-smart-router/issues/119)):
|
|
450
|
+
|
|
451
|
+
1. **learned** — optional privacy-safe map keyed by requirement fingerprint (SHA-256 of the rounded requirement vector) or cluster id → preferred tier. **Raw prompt text is never stored.** Exact-key policy: fingerprint match first, cluster id second (no fuzzy matching). Writes are validated (bounded floats, snake_case cluster ids, tier enum) and capped (FIFO eviction) so confounder attacks cannot poison routing memory.
|
|
452
|
+
2. **heuristic** — optional operator **pattern pack** (router_rules-style regex overlay) for known-simple intents. Deny-by-default (no match → no decision) and **fail closed on invalid regex** (the rule is rejected at load and never applies).
|
|
453
|
+
3. **safe_default** — context-fit aware safe economical/frontier default (`degraded_safe_default`).
|
|
454
|
+
|
|
455
|
+
Explain/telemetry expose `route_path` (`neural` \| `learned` \| `heuristic` \| `safe_default`) plus `route_path_confidence` on every decision; degraded decisions carry reason codes `degraded_learned_route`, `degraded_pattern_<rule_id>`, or `degraded_safe_default`. A learned/pattern suggestion toward a cheaper tier is only honored when the cheap tool-use cue estimate is below `pattern_tool_use_ceiling` — a cheap overlay **never alone overrides a predicted capability shortfall**.
|
|
456
|
+
|
|
457
|
+
| Knob (`degraded_route` operator config) | Default | Effect |
|
|
458
|
+
|------|---------|--------|
|
|
459
|
+
| `enabled` | `true` | When `false`, neural failures use the legacy `safe_default` stage pass-through |
|
|
460
|
+
| `learned_min_confidence` | `0.6` | Minimum learned-entry confidence to honor a tier suggestion |
|
|
461
|
+
| `learned_max_entries` | `512` | Learned-map cap per key space (FIFO eviction) |
|
|
462
|
+
| `pattern_tool_use_ceiling` | `0.3` | Tool-use cue ceiling for honoring cheaper-tier learned/pattern suggestions |
|
|
463
|
+
|
|
464
|
+
Distinct from soft heat affinity (healthy-path bias): this is failover / skip-expensive-stage only. Routing remains **pre-generation** — no FrugalGPT-style cascades (see [routing-roadmap.md](docs/routing-roadmap.md) §1).
|
|
465
|
+
|
|
445
466
|
### Virtual cost v2 (v0.5.0 subscription economics)
|
|
446
467
|
|
|
447
468
|
**Virtual cost v2** extends SP-096 flat `quota_cost_per_1m` with deterministic subscription-window economics ([#78](https://github.com/beettlle/pi-smart-router/issues/78)). It inflates effective frontier cost late in a rolling quota window and credits warm prefix-cache value on active pins — without MDP or reinforcement-learning quota policy (SeqRoute HBR+CQL is deferred).
|
|
@@ -462,6 +483,10 @@ See [routing-roadmap.md](docs/routing-roadmap.md) §2 P0 and GitHub [#71](https:
|
|
|
462
483
|
|
|
463
484
|
Rolling-window position is supplied to the router pipeline as `quotaWindowPosition` (library API / telemetry integration). Use `remaining_window_fraction` in `[0, 1]` (1 = full budget). Optionally derive it from elapsed time and consumed quota via `deriveRemainingWindowFraction(elapsed_seconds, consumed_fraction)` in `virtual-cost-v2.ts` (defaults assume a Cursor-style **5h** window).
|
|
464
485
|
|
|
486
|
+
**Quota window feed (producer, [#125](https://github.com/beettlle/pi-smart-router/issues/125))**
|
|
487
|
+
|
|
488
|
+
There is no universal cross-provider "remaining quota" API, so `src/domain/pricing/quota-window-feed.ts` produces the position via an adapter + degrade chain: (1) a provider `QuotaWindowAdapter` when a trustworthy signal exists, (2) a telemetry-derived **pool-level** burn estimate over the rolling window (subscription-pool models = fleet entries with `quota_cost_per_1m`; enabled via `SMART_ROUTER_QUOTA_POOL_BUDGET_TOKENS` + `SMART_ROUTER_QUOTA_WINDOW_SECONDS`), (3) omit → flat virtual cost + SP-097 exhaustion failover. The smart-router extension resolves the feed at fleet rebuild and passes it through `createDispatchOptions` (SP-173 wiring gap closed). Soft bias only — no hard ban at any threshold; SP-097 reactive failover remains the safety net when the feed is missing or stale. Per-model fractions for shared pools are never invented.
|
|
489
|
+
|
|
465
490
|
When `quotaWindowPosition` is omitted, λ stays at 1 and quota premiums are zero — behavior matches SP-096 flat virtual cost.
|
|
466
491
|
|
|
467
492
|
**Operator knobs** (`VirtualCostV2Config` — wire through `RouterPipeline` options today; defaults in `DEFAULT_VIRTUAL_COST_V2_CONFIG`):
|
|
@@ -1202,6 +1227,60 @@ Confirm https://pi.dev/packages/pi-smart-router shows the new version (may lag n
|
|
|
1202
1227
|
| [config/models.yaml.example](config/models.yaml.example) | Fleet catalog template (library API) |
|
|
1203
1228
|
| [config/routing-clusters.yaml.example](config/routing-clusters.yaml.example) | Routing cluster reference-prompt catalog (library API) |
|
|
1204
1229
|
|
|
1230
|
+
## Develop with pi-spine (agent models)
|
|
1231
|
+
|
|
1232
|
+
This repo is developed with [pi-spine](https://github.com/beettlle/pi-spine) batches. Agent model pins live in [`.spine/spine-config.json`](.spine/spine-config.json) under `agents.*`. Use **canonical** `provider/model` ids from `pi --list-models` (not TUI labels like `glm-5.2 [zai]`). Run `spine doctor` before real-pi batches.
|
|
1233
|
+
|
|
1234
|
+
Named profiles (`agents.profiles` + `agents.activeProfile`) and `agents.escalatePolicy` are configured in spine-config ([pi-spine#216](https://github.com/beettlle/pi-spine/issues/216) / SP-664). Live stack resolves from `activeProfile` over the base `agents` block. Hybrid cost/quality recipes are documented upstream in [pi-spine#210](https://github.com/beettlle/pi-spine/issues/210).
|
|
1235
|
+
|
|
1236
|
+
### Default profile (`activeProfile: "default"`)
|
|
1237
|
+
|
|
1238
|
+
| Role | Model | Thinking |
|
|
1239
|
+
|------|--------|----------|
|
|
1240
|
+
| Worker | `zai/glm-5.2` | `high` |
|
|
1241
|
+
| Plan review | `google/gemini-flash-latest` | `low` |
|
|
1242
|
+
| Code review | `kimi-coding/kimi-k2-thinking` | `high` |
|
|
1243
|
+
| Final review | `google/gemini-3.1-pro-preview` | `high` |
|
|
1244
|
+
| Supervisor | `google/gemini-flash-lite-latest` | `off` |
|
|
1245
|
+
|
|
1246
|
+
### When to escalate (hard packets / sticky failures)
|
|
1247
|
+
|
|
1248
|
+
Escalate when:
|
|
1249
|
+
|
|
1250
|
+
- The same SP fails final or code review **2+** times with substantive `REVISE` (not broad `testCommand` / lane noise)
|
|
1251
|
+
- The worker stalls or oscillates on multi-file design
|
|
1252
|
+
- The packet needs deeper reasoning than the default stack delivered
|
|
1253
|
+
|
|
1254
|
+
### Tier 1 — mid escalate
|
|
1255
|
+
|
|
1256
|
+
Switches the worker to `kimi-coding/kimi-for-coding` (reviewer pins inherit default).
|
|
1257
|
+
|
|
1258
|
+
```bash
|
|
1259
|
+
spine settings set agents.activeProfile mid
|
|
1260
|
+
spine batch retry <SP-ID>
|
|
1261
|
+
# restore:
|
|
1262
|
+
spine settings set agents.activeProfile default
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
### Tier 2 — hard escalate
|
|
1266
|
+
|
|
1267
|
+
| Role | Model | Thinking |
|
|
1268
|
+
|------|--------|----------|
|
|
1269
|
+
| Worker | `kimi-coding/k3` | `high` |
|
|
1270
|
+
| Plan | `kimi-coding/kimi-k2-thinking` | `medium` |
|
|
1271
|
+
| Code | `google/gemini-3.1-pro-preview` | `high` |
|
|
1272
|
+
| Final | `google/gemini-3.1-pro-preview` | `high` |
|
|
1273
|
+
|
|
1274
|
+
`escalatePolicy.toProfile` is `hard`. Switch explicitly when a packet is sticky:
|
|
1275
|
+
|
|
1276
|
+
```bash
|
|
1277
|
+
spine settings set agents.activeProfile hard
|
|
1278
|
+
spine doctor
|
|
1279
|
+
spine batch retry <SP-ID>
|
|
1280
|
+
# restore:
|
|
1281
|
+
spine settings set agents.activeProfile default
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1205
1284
|
## Built with
|
|
1206
1285
|
|
|
1207
1286
|
- [pi](https://pi.dev) — Coding agent harness (extension host)
|
|
@@ -7,26 +7,47 @@
|
|
|
7
7
|
"livecodebench": "https://livecodebench.github.io/leaderboard.html",
|
|
8
8
|
"bfcl": "https://gorilla.cs.berkeley.edu/leaderboard.html"
|
|
9
9
|
},
|
|
10
|
-
"scrape_date": "2026-07-
|
|
11
|
-
"catalog_freeze_date": "2026-07-
|
|
10
|
+
"scrape_date": "2026-07-20",
|
|
11
|
+
"catalog_freeze_date": "2026-07-20"
|
|
12
12
|
},
|
|
13
13
|
"aliases": {
|
|
14
|
+
"anthropic/claude-opus-4": "claude-opus-4-5",
|
|
15
|
+
"anthropic/claude-sonnet-4": "claude-sonnet-4-6",
|
|
14
16
|
"claude-3-5-sonnet": "claude-sonnet-4-6",
|
|
17
|
+
"claude-3-7-sonnet": "claude-sonnet-4-6",
|
|
18
|
+
"claude-3-7-sonnet-latest": "claude-sonnet-4-6",
|
|
15
19
|
"claude-3.5-sonnet": "claude-sonnet-4-6",
|
|
16
20
|
"claude-3.5-sonnet-latest": "claude-sonnet-4-6",
|
|
21
|
+
"claude-3.7-sonnet": "claude-sonnet-4-6",
|
|
17
22
|
"claude-opus-4": "claude-opus-4-5",
|
|
23
|
+
"claude-opus-4-1": "claude-opus-4-5",
|
|
18
24
|
"claude-opus-4-20250514": "claude-opus-4-5",
|
|
25
|
+
"claude-opus-4.1": "claude-opus-4-5",
|
|
19
26
|
"claude-sonnet-4": "claude-sonnet-4-6",
|
|
20
27
|
"claude-sonnet-4-20250514": "claude-sonnet-4-6",
|
|
28
|
+
"claude-sonnet-4-5": "claude-sonnet-4-6",
|
|
29
|
+
"claude-sonnet-4.5": "claude-sonnet-4-6",
|
|
21
30
|
"composer-1": "gpt-5.3-codex",
|
|
22
31
|
"composer-latest": "gpt-5.3-codex",
|
|
23
32
|
"cursor/auto": "gpt-5.3-codex",
|
|
24
33
|
"cursor/composer-latest": "gpt-5.3-codex",
|
|
25
34
|
"default": "gpt-5.3-codex",
|
|
35
|
+
"gemini-1.5-flash": "gemini-2.5-flash",
|
|
26
36
|
"gemini-2.0-flash": "gemini-2.5-flash",
|
|
37
|
+
"gemini-2.0-flash-001": "gemini-2.5-flash",
|
|
38
|
+
"gemini-2.5-flash-002": "gemini-2.5-flash",
|
|
27
39
|
"gemini-2.5-flash-lite": "gemini-2.5-flash",
|
|
28
40
|
"gemini-2.5-flash-preview": "gemini-2.5-flash",
|
|
41
|
+
"gemini-flash-8b": "gemini-2.5-flash",
|
|
29
42
|
"gemini-flash-latest": "gemini-2.5-flash",
|
|
43
|
+
"github-copilot/claude-3.5-sonnet": "claude-sonnet-4-6",
|
|
44
|
+
"github-copilot/claude-sonnet-4": "claude-sonnet-4-6",
|
|
45
|
+
"github-copilot/claude-sonnet-4.5": "claude-sonnet-4-6",
|
|
46
|
+
"github-copilot/gemini-2.0-flash": "gemini-2.5-flash",
|
|
47
|
+
"github-copilot/gemini-2.5-flash": "gemini-2.5-flash",
|
|
48
|
+
"github-copilot/gpt-5": "gpt-5.3-codex",
|
|
49
|
+
"github-copilot/gpt-5-codex": "gpt-5.3-codex",
|
|
50
|
+
"google/gemini-2.5-flash": "gemini-2.5-flash",
|
|
30
51
|
"gpt-5": "gpt-5.3-codex",
|
|
31
52
|
"gpt-5-codex": "gpt-5.3-codex",
|
|
32
53
|
"gpt-5.3": "gpt-5.3-codex",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,wFAAwF;AACxF,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE,cAAwC,GAC7C,cAAc,CAMhB;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,cAAc,CAiCnD,CAAC"}
|
package/dist/config/defaults.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Operator configuration defaults (FR-021).
|
|
3
3
|
* Values sourced from specs/001-build-smart-router/data-model.md § Configuration (Operator).
|
|
4
4
|
*/
|
|
5
|
-
import { DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
5
|
+
import { DEFAULT_DEGRADED_ROUTE_CONFIG, DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
6
6
|
import { DEFAULT_LOW_INTENSITY_WEIGHTS } from '../domain/routing/tier-features.js';
|
|
7
7
|
export { DEFAULT_LOCAL_ZERO_CONFIG, DEFAULT_PLANNING_DELEGATE_CONFIG, DEFAULT_SAAR_CONFIG, resolvePlanningDelegateConfigFromEnv, resolveSaarConfigFromEnv, } from '../domain/types/schemas.js';
|
|
8
8
|
/** Merge operator env overrides onto defaults (SAAR and planning delegate sections). */
|
|
@@ -44,6 +44,7 @@ export const DEFAULT_OPERATOR_CONFIG = {
|
|
|
44
44
|
saar: DEFAULT_SAAR_CONFIG,
|
|
45
45
|
planning_delegate: DEFAULT_PLANNING_DELEGATE_CONFIG,
|
|
46
46
|
local_zero: DEFAULT_LOCAL_ZERO_CONFIG,
|
|
47
|
+
degraded_route: DEFAULT_DEGRADED_ROUTE_CONFIG,
|
|
47
48
|
pin_only_fallback: false,
|
|
48
49
|
};
|
|
49
50
|
//# sourceMappingURL=defaults.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,wFAAwF;AACxF,MAAM,UAAU,4BAA4B,CAC1C,OAAuB,uBAAuB;IAE9C,OAAO;QACL,GAAG,IAAI;QACP,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,iBAAiB,EAAE,oCAAoC,CAAC,IAAI,CAAC,iBAAiB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAA6B;IAC/D,SAAS,EAAE;QACT,WAAW,EAAE,GAAG;QAChB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;KACb;IACD,OAAO,EAAE;QACP,cAAc,EAAE,EAAE;KACnB;IACD,KAAK,EAAE;QACL,kBAAkB,EAAE,EAAE;QACtB,4BAA4B,EAAE,CAAC;QAC/B,qBAAqB,EAAE,EAAE;KAC1B;IACD,KAAK,EAAE;QACL,mBAAmB,EAAE,0BAA0B;QAC/C,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,6BAA6B;QACtC,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,GAAG;KACrB;IACD,IAAI,EAAE,mBAAmB;IACzB,iBAAiB,EAAE,gCAAgC;IACnD,UAAU,EAAE,yBAAyB;IACrC,iBAAiB,EAAE,KAAK;CAChB,CAAC"}
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GAEzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAEnF,OAAO,EACL,yBAAyB,EACzB,gCAAgC,EAChC,mBAAmB,EACnB,oCAAoC,EACpC,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AAEpC,wFAAwF;AACxF,MAAM,UAAU,4BAA4B,CAC1C,OAAuB,uBAAuB;IAE9C,OAAO;QACL,GAAG,IAAI;QACP,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,iBAAiB,EAAE,oCAAoC,CAAC,IAAI,CAAC,iBAAiB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAA6B;IAC/D,SAAS,EAAE;QACT,WAAW,EAAE,GAAG;QAChB,cAAc,EAAE,GAAG;QACnB,gBAAgB,EAAE,IAAI;KACvB;IACD,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;KACb;IACD,OAAO,EAAE;QACP,cAAc,EAAE,EAAE;KACnB;IACD,KAAK,EAAE;QACL,kBAAkB,EAAE,EAAE;QACtB,4BAA4B,EAAE,CAAC;QAC/B,qBAAqB,EAAE,EAAE;KAC1B;IACD,KAAK,EAAE;QACL,mBAAmB,EAAE,0BAA0B;QAC/C,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,oBAAoB;KAClC;IACD,aAAa,EAAE;QACb,OAAO,EAAE,6BAA6B;QACtC,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,IAAI;QACnB,eAAe,EAAE,GAAG;KACrB;IACD,IAAI,EAAE,mBAAmB;IACzB,iBAAiB,EAAE,gCAAgC;IACnD,UAAU,EAAE,yBAAyB;IACrC,cAAc,EAAE,6BAA6B;IAC7C,iBAAiB,EAAE,KAAK;CAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-escalation.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIlF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,EAAG,eAAwB,CAAC;AA4BvE;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAOlF;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAKjF;AA+GD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,UAAU,GAAG,IAAI,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,
|
|
1
|
+
{"version":3,"file":"loop-escalation.d.ts","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIlF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,6BAA6B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,eAAO,MAAM,8BAA8B,EAAG,eAAwB,CAAC;AA4BvE;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAOlF;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAKjF;AA+GD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,UAAU,GAAG,IAAI,EACtB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,oBAAoB,GAC3B,oBAAoB,CAgFtB"}
|
|
@@ -195,8 +195,31 @@ export function evaluateLoopEscalation(pin, request, fleet, config) {
|
|
|
195
195
|
}
|
|
196
196
|
return noEscalation('no_failure');
|
|
197
197
|
}
|
|
198
|
+
//
|
|
199
|
+
// Economical pin — observational churn (SP-210, #122):
|
|
200
|
+
//
|
|
201
|
+
// A hard multi-step / tool-failure session pinned economical must not stay
|
|
202
|
+
// stuck when quality recovery needs a higher tier. Unlike the zero-tier
|
|
203
|
+
// path (which counts every tool_result turn) economical models CAN complete
|
|
204
|
+
// most tool loops, so we only count actual tool *failures*. But unlike the
|
|
205
|
+
// frontier identical-failure gate (FR-014), distinct/varied failures are
|
|
206
|
+
// still evidence the economical tier cannot recover the loop — a session
|
|
207
|
+
// failing repeatedly with different errors (ENOENT → ECONNREFUSED → timeout)
|
|
208
|
+
// is just as stuck as one hitting the same error 3×. We therefore count
|
|
209
|
+
// consecutive failures of ANY signature toward the escalation threshold.
|
|
210
|
+
//
|
|
211
|
+
// Break/upgrade conditions while pinned economical:
|
|
212
|
+
// • N consecutive tool failures (any signature) → escalate to frontier
|
|
213
|
+
// • a successful tool result → reset the failure streak
|
|
214
|
+
// • escalation fires once per session (pin_reason === 'loop_escalation')
|
|
215
|
+
// History surfaces the break via pin_reason='loop_escalation' plus the new
|
|
216
|
+
// selected_model_id on the subsequent session_pin stage (see router-pipeline).
|
|
217
|
+
//
|
|
218
|
+
const isEconomical = resolvePinnedTier(pin, fleet) === 'economical-cloud';
|
|
198
219
|
const isIdentical = pin.last_tool_failure_signature === signature;
|
|
199
|
-
const newCount =
|
|
220
|
+
const newCount = isEconomical || isIdentical
|
|
221
|
+
? pin.consecutive_tool_failures + 1
|
|
222
|
+
: 1;
|
|
200
223
|
const updated = {
|
|
201
224
|
...pin,
|
|
202
225
|
consecutive_tool_failures: newCount,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-escalation.js","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"loop-escalation.js","sourceRoot":"","sources":["../../../src/domain/pinning/loop-escalation.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,9 +21,16 @@ import type { StorePort } from '../types/store-port.js';
|
|
|
21
21
|
import { type CacheEconomicsConfig } from './cache-economics.js';
|
|
22
22
|
import { type CacheBreakevenResult } from './cache-breakeven.js';
|
|
23
23
|
import { FlipFlopGuard, type FlipFlopObservation, type FlipFlopSessionState } from './flip-flop-guard.js';
|
|
24
|
-
export type PinAction = 'use_pin' | 'sub_route' | 'saar_route' | 'break' | 'no_pin';
|
|
24
|
+
export type PinAction = 'use_pin' | 'sub_route' | 'saar_route' | 'break' | 'no_pin' | 'force_rejected';
|
|
25
25
|
export type PinSaarReason = 'saar_buffer_active' | 'saar_hard_lock' | 'saar_idle_reopen' | 'saar_tier_upgrade';
|
|
26
26
|
export type PinFlipFlopReason = 'flip_flop_tier_pinned';
|
|
27
|
+
/**
|
|
28
|
+
* SP-209 / #121 — force_model_id could not be honored. Surfaced as the routing
|
|
29
|
+
* decision `reason_code` so explain / `SMART_ROUTER_LOG_ROUTING=1` makes the
|
|
30
|
+
* rejection explicit instead of silently remapping across provider families.
|
|
31
|
+
*/
|
|
32
|
+
export declare const FORCE_REJECTED_NOT_IN_FLEET: "force_rejected_not_in_fleet";
|
|
33
|
+
export declare const FORCE_REJECTED_UNHEALTHY: "force_rejected_unhealthy";
|
|
27
34
|
export interface PinLookupResult {
|
|
28
35
|
readonly action: PinAction;
|
|
29
36
|
readonly pinnedModel?: ModelProfile;
|
|
@@ -32,6 +39,10 @@ export interface PinLookupResult {
|
|
|
32
39
|
readonly saarReason?: PinSaarReason;
|
|
33
40
|
readonly flipFlopReason?: PinFlipFlopReason;
|
|
34
41
|
readonly breakReason?: PinReason;
|
|
42
|
+
/** SP-209 / #121: rejection reason code when force_model_id cannot be honored. */
|
|
43
|
+
readonly forceRejectionReason?: string;
|
|
44
|
+
/** SP-209 / #121: the force_model_id value that was rejected. */
|
|
45
|
+
readonly forceModelId?: string;
|
|
35
46
|
}
|
|
36
47
|
export interface SessionPinnerConfig {
|
|
37
48
|
/** FR-024: max payload size (bytes or token estimate) for sub-routing. Default 2048. */
|
|
@@ -141,7 +152,16 @@ export declare class SessionPinner {
|
|
|
141
152
|
private evaluateBreakRules;
|
|
142
153
|
private evaluateContextOverflowBreak;
|
|
143
154
|
private evaluateCacheEconomicsBreak;
|
|
144
|
-
|
|
155
|
+
/**
|
|
156
|
+
* SP-209 / #121: resolve an explicit force_model_id override.
|
|
157
|
+
*
|
|
158
|
+
* Healthy in-fleet target → pin to it (`use_pin`). Unavailable target → fail
|
|
159
|
+
* closed (`force_rejected`) carrying an explicit reason code so the pipeline
|
|
160
|
+
* can surface the rejection in explain / SMART_ROUTER_LOG_ROUTING instead of
|
|
161
|
+
* silently remapping to a different provider family. A rejected force is a
|
|
162
|
+
* no-op on pin state — the existing pin (if any) is preserved.
|
|
163
|
+
*/
|
|
164
|
+
private resolveForceOverride;
|
|
145
165
|
private persistPin;
|
|
146
166
|
private deletePersistedPin;
|
|
147
167
|
private evaluateSubRouting;
|