pi-smart-router 0.16.2 → 0.17.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.
@@ -52,6 +52,11 @@ function isPipedResult(
52
52
  return 'heldTerminal' in result;
53
53
  }
54
54
 
55
+ /** Fail-open reason codes (SP-226) — emitted in telemetry and SMART_ROUTER_LOG_ROUTING=1. */
56
+ export const NO_REGISTRY_MODEL = 'no_registry_model';
57
+ export const FAILOVER_EXHAUSTED = 'failover_exhausted';
58
+ export const DELEGATION_ABORTED = 'delegation_aborted';
59
+
55
60
  function isRoutingLogEnabled(): boolean {
56
61
  return process.env.SMART_ROUTER_LOG_ROUTING === '1';
57
62
  }
@@ -152,6 +157,68 @@ function isZeroOutputLengthStop(message: AssistantMessage): boolean {
152
157
  return message.stopReason === 'length' && message.usage.output === 0;
153
158
  }
154
159
 
160
+ /** Minimal model identity for degraded terminal messages when no Model resolved. */
161
+ interface DegradedModelRef {
162
+ readonly api: Api;
163
+ readonly provider: string;
164
+ readonly id: string;
165
+ }
166
+
167
+ function createDegradedErrorMessage(
168
+ model: DegradedModelRef,
169
+ reasonCode: string,
170
+ detail: string,
171
+ ): AssistantMessage {
172
+ return {
173
+ role: 'assistant',
174
+ content: [],
175
+ api: model.api,
176
+ provider: model.provider,
177
+ model: model.id,
178
+ usage: {
179
+ input: 0,
180
+ output: 0,
181
+ cacheRead: 0,
182
+ cacheWrite: 0,
183
+ totalTokens: 0,
184
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
185
+ },
186
+ stopReason: 'error',
187
+ errorMessage: `Smart router degraded response (${reasonCode}): ${detail}`,
188
+ timestamp: Date.now(),
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Fail-open terminal (SP-226): never throw to the host on exhaustion paths.
194
+ * Emit a structured warning with the reason code and end the outer stream with
195
+ * a degraded error message so the pi host receives an actionable response.
196
+ */
197
+ function emitDegradedFailure(
198
+ outer: AssistantMessageEventStream,
199
+ model: DegradedModelRef | undefined,
200
+ selectedModelId: string,
201
+ reasonCode: string,
202
+ detail: string,
203
+ ): void {
204
+ console.warn(
205
+ '[smart-router] fail-open degraded response',
206
+ JSON.stringify({
207
+ reason_code: reasonCode,
208
+ selected_model_id: selectedModelId,
209
+ detail,
210
+ }),
211
+ );
212
+ const ref: DegradedModelRef = model ?? {
213
+ api: 'unknown',
214
+ provider: 'unknown',
215
+ id: selectedModelId,
216
+ };
217
+ const errorMessage = createDegradedErrorMessage(ref, reasonCode, detail);
218
+ outer.push({ type: 'error', reason: 'error', error: errorMessage });
219
+ outer.end(errorMessage);
220
+ }
221
+
155
222
  function buildOverflowRoutingDecision(
156
223
  base: RoutingDecision,
157
224
  fallback: ReturnType<typeof resolveContextOverflowFallback>,
@@ -357,9 +424,18 @@ export async function routeAndDelegate(
357
424
  if (decision.selected_model_id === 'unknown' && guardResult) {
358
425
  assertRoutableFleetAfterGeminiToolHistoryGuard(guardResult);
359
426
  }
360
- throw new Error(
427
+ // SP-226 fail-open: no registry model resolved — degrade instead of throwing.
428
+ deps.router.dispatch.recordOutcome(decision.selected_model_id, {
429
+ code: 'NO_REGISTRY_MODEL',
430
+ });
431
+ emitDegradedFailure(
432
+ outer,
433
+ undefined,
434
+ decision.selected_model_id,
435
+ NO_REGISTRY_MODEL,
361
436
  `No registry model available for routing decision ${decision.selected_model_id}`,
362
437
  );
438
+ return;
363
439
  }
364
440
 
365
441
  logRoutingDecision(decision, {
@@ -546,6 +622,16 @@ export async function routeAndDelegate(
546
622
  return;
547
623
  } catch (error) {
548
624
  if (isAbortError(error, options)) {
625
+ // SP-226: telemetry for phase-boundary aborts (previously silent).
626
+ if (isRoutingLogEnabled()) {
627
+ console.warn(
628
+ '[smart-router] delegation aborted',
629
+ JSON.stringify({
630
+ reason_code: DELEGATION_ABORTED,
631
+ model_id: targetModel.id,
632
+ }),
633
+ );
634
+ }
549
635
  const abortMessage = createErrorMessage(targetModel, options, error);
550
636
  outer.push({ type: 'error', reason: 'aborted', error: abortMessage });
551
637
  outer.end(abortMessage);
@@ -565,7 +651,7 @@ export async function routeAndDelegate(
565
651
  );
566
652
  const alternateModel = failover ? resolveTargetModel(deps, failover) : undefined;
567
653
 
568
- if (alternateModel && alternateModel.id !== targetModel.id) {
654
+ if (failover && alternateModel && alternateModel.id !== targetModel.id) {
569
655
  console.warn(
570
656
  '[smart-router] stream delegation failed, failing over',
571
657
  error instanceof Error ? error.message : String(error),
@@ -575,9 +661,6 @@ export async function routeAndDelegate(
575
661
  alternateModelId: alternateModel.id,
576
662
  errorObj: { message: error instanceof Error ? error.message : String(error) },
577
663
  };
578
- if (!failover) {
579
- throw error;
580
- }
581
664
  decision = failover;
582
665
  targetModel = alternateModel;
583
666
  continue;
@@ -585,7 +668,16 @@ export async function routeAndDelegate(
585
668
 
586
669
  const fallbackModel = resolveFallbackModel(deps, effectiveFleet);
587
670
  if (!fallbackModel || fallbackModel.id === targetModel.id) {
588
- throw error;
671
+ // SP-226 fail-open: fleet/failover exhausted and no distinct safe
672
+ // default — degrade instead of throwing to the host.
673
+ emitDegradedFailure(
674
+ outer,
675
+ targetModel,
676
+ decision.selected_model_id,
677
+ FAILOVER_EXHAUSTED,
678
+ error instanceof Error ? error.message : String(error),
679
+ );
680
+ return;
589
681
  }
590
682
 
591
683
  console.warn(
@@ -76,6 +76,35 @@ function messageContentToString(content: string | readonly (TextContent | { type
76
76
  .join('\n');
77
77
  }
78
78
 
79
+ /**
80
+ * SP-225 / #137: read an HTTP-ish status a host may attach to a tool result
81
+ * (either directly on the message or inside `details`). Returns undefined
82
+ * when no finite numeric status is present.
83
+ */
84
+ function readOptionalStatus(source: unknown): number | undefined {
85
+ if (source === null || typeof source !== 'object') {
86
+ return undefined;
87
+ }
88
+ const record = source as Record<string, unknown>;
89
+ const direct = record.status;
90
+ if (typeof direct === 'number' && Number.isFinite(direct) && direct >= 0) {
91
+ return Math.floor(direct);
92
+ }
93
+ return readOptionalStatus(record.details);
94
+ }
95
+
96
+ export interface MapContextMessagesOptions {
97
+ /**
98
+ * Opt-in: include assistant `thinking` blocks in routing `content`.
99
+ * Default false — thinking is model-internal reasoning, not a routing
100
+ * signal, and leaking it inflates token estimates (#137).
101
+ */
102
+ includeThinking?: boolean;
103
+ }
104
+
105
+ /** Operator env gate restoring the pre-SP-225 thinking-in-content behavior. */
106
+ const INCLUDE_THINKING_ENV = 'SMART_ROUTER_INCLUDE_THINKING';
107
+
79
108
  export function extractPromptText(messages: readonly Message[]): string {
80
109
  for (let i = messages.length - 1; i >= 0; i--) {
81
110
  const message = messages[i];
@@ -117,7 +146,11 @@ export function deriveTurnType(messages: readonly Message[]): TurnType {
117
146
  return 'main_loop';
118
147
  }
119
148
 
120
- export function mapContextMessages(messages: readonly Message[]): RoutingMessage[] {
149
+ export function mapContextMessages(
150
+ messages: readonly Message[],
151
+ options?: MapContextMessagesOptions,
152
+ ): RoutingMessage[] {
153
+ const includeThinking = options?.includeThinking === true;
121
154
  return messages.map((message) => {
122
155
  if (message.role === 'user') {
123
156
  return {
@@ -127,27 +160,53 @@ export function mapContextMessages(messages: readonly Message[]): RoutingMessage
127
160
  }
128
161
 
129
162
  if (message.role === 'assistant') {
130
- const content = message.content
131
- .map((block) => {
132
- if (block.type === 'text') {
133
- return block.text;
163
+ const contentParts: string[] = [];
164
+ const toolBlocks: Record<string, unknown>[] = [];
165
+ for (const block of message.content) {
166
+ if (block.type === 'text') {
167
+ contentParts.push(block.text);
168
+ } else if (block.type === 'thinking') {
169
+ if (includeThinking) {
170
+ contentParts.push(block.thinking);
134
171
  }
135
- if (block.type === 'thinking') {
136
- return block.thinking;
137
- }
138
- return '';
139
- })
140
- .filter(Boolean)
141
- .join('\n');
172
+ } else if (block.type === 'toolCall') {
173
+ toolBlocks.push({
174
+ type: 'tool_call',
175
+ tool_call_id: block.id,
176
+ tool_name: block.name,
177
+ });
178
+ }
179
+ }
142
180
 
143
- return { role: message.role, content };
181
+ const mapped: RoutingMessage = {
182
+ role: message.role,
183
+ content: contentParts.filter(Boolean).join('\n'),
184
+ };
185
+ if (toolBlocks.length > 0) {
186
+ return { ...mapped, tool_blocks: toolBlocks };
187
+ }
188
+ return mapped;
144
189
  }
145
190
 
191
+ const status = readOptionalStatus(message);
192
+ // When a host attaches an HTTP-ish status, let the domain status>=400 rule
193
+ // arbitrate (#137): a bare isError=false would otherwise mask the
194
+ // structured signal. Without a status, preserve the host isError verbatim.
195
+ const isError = message.isError === true || status === undefined
196
+ ? message.isError
197
+ : undefined;
146
198
  return {
147
199
  role: 'tool',
148
200
  content: messageContentToString(message.content),
149
- tool_blocks: [],
150
- is_error: message.isError,
201
+ tool_blocks: [
202
+ {
203
+ type: 'tool_result',
204
+ tool_call_id: message.toolCallId,
205
+ tool_name: message.toolName,
206
+ },
207
+ ],
208
+ ...(isError !== undefined ? { is_error: isError } : {}),
209
+ ...(status !== undefined ? { status } : {}),
151
210
  };
152
211
  });
153
212
  }
@@ -164,7 +223,9 @@ export function buildRoutingRequest(
164
223
  request_id: randomUUID(),
165
224
  session_id: sessionId,
166
225
  prompt_text: extractPromptText(context.messages),
167
- messages: mapContextMessages(context.messages),
226
+ messages: mapContextMessages(context.messages, {
227
+ includeThinking: process.env[INCLUDE_THINKING_ENV] === '1',
228
+ }),
168
229
  turn_type: deriveTurnType(context.messages),
169
230
  estimated_input_tokens: estimateInputTokens(context, options),
170
231
  ...lifecycleFlags,
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Auto-model router middleware for the [pi](https://pi.dev) coding agent.**
4
4
 
5
- > **v0.1.0** is initial development (SemVer `0.y.z`). The public API and routing behavior may change until `1.0.0`.
5
+ > Current release: **v0.16.2** (mirrors `package.json`; SemVer `0.y.z`). The public API and routing behavior may change until `1.0.0`.
6
6
 
7
7
  pi-smart-router intercepts every LLM inference request and dynamically routes it to the optimal execution engine — balancing cost, capability, latency, and time-to-first-token (TTFT) — without requiring you to manually pick a model for each turn.
8
8
 
@@ -18,8 +18,8 @@ pi-smart-router intercepts every LLM inference request and dynamically routes it
18
18
  ```text
19
19
  request → hardware probe → loop escalation → turn envelope → context-fit gate
20
20
  → low-intensity tier gate → session pin → deterministic triage
21
- → local zero-tier → HyDRA embedding matchersafe cloud default
22
- → context overflow fallback
21
+ → local zero-tier → triage cloud fallbackHyDRA embedding matcher
22
+ safe cloud default → context overflow fallback
23
23
  ```
24
24
 
25
25
  The pipeline runs **12 stages sequentially with early exit** — the moment any stage reaches a routing decision, subsequent stages are skipped. Every decision includes the stage name, reason code, candidates considered, estimated cost, and routing latency for full observability.
@@ -34,6 +34,7 @@ The pipeline runs **12 stages sequentially with early exit** — the moment any
34
34
  | Session Pin | <1ms | Returns pinned model if session has one; breaks pin on compaction or overflow |
35
35
  | Deterministic Triage | <5ms | Aho-Corasick keyword scan + cyclomatic complexity analysis |
36
36
  | Local Zero-Tier | <15ms | Pings LM Studio + Ollama in parallel; routes locally when eligible |
37
+ | Triage Cloud Fallback | <2ms | Trivial prompts not claimed locally route to the first healthy economical-cloud model |
37
38
  | HyDRA Matcher | 80-120ms | ONNX embeddings, 3D requirement projection, shortfall gate, multi-objective scoring |
38
39
  | Safe Cloud Default | — | First healthy economical-cloud model (context-fit aware) |
39
40
  | Context Overflow Fallback | — | Escalates to largest-fit model when economical tiers cannot fit |
@@ -258,6 +259,10 @@ After typing `/smart-router ` (with a trailing space), press **TAB** to see subc
258
259
  npm run verify:ci
259
260
  ```
260
261
 
262
+ ## Concurrency contract
263
+
264
+ `RouterPipeline.route()` calls on a single router instance are **single-flight**: concurrent calls are serialized internally (SP-230, [#141](https://github.com/beettlle/pi-smart-router/issues/141)). The pipeline keeps per-route transient state on instance fields while stages run, so overlapping executions are queued rather than interleaved — each queued call waits at most one routing latency. This applies to `createRouter()` / `createRouterFromFleet()` handles: a shared `router.dispatch` is safe to call concurrently, and serialization does not change routing policy outcomes. For parallel routing throughput, create separate router instances.
265
+
261
266
  ## Fleet behavior
262
267
 
263
268
  When you use `smart-router/auto`, the extension does **not** read `config/models.yaml`. Instead:
@@ -949,7 +954,7 @@ Contributors must run `npm run build` before publishing or consuming the library
949
954
  | `npm run release:check` | Pre-release gate: `verify:ci` + consumer pack + Tier 0 functional smoke |
950
955
  | `npm run release:functional-smoke` | Tier 0 functional smoke: calibration verify (`--skip-embed`), benchmark profiles, release gate assertions |
951
956
  | `npm run release:consumer-pack` | Pack tarball and verify production dependencies resolve (catches missing runtime deps) |
952
- | `npm run verify:ci` | Full CI parity: build, typecheck, lint, test, coverage |
957
+ | `npm run verify:ci` | Full CI parity: build, typecheck, lint, test, coverage (baseline PR gate; see [PR and pre-release quality gate set](#pr-and-pre-release-quality-gate-set)) |
953
958
  | `npm run typecheck` | TypeScript strict mode check (`tsc --noEmit`) |
954
959
  | `npm test` | Run test suite (`vitest run`) |
955
960
  | `npm run coverage:check` | Tests with line-coverage thresholds |
@@ -1008,7 +1013,9 @@ npm run routing:eval-replay
1008
1013
  npm run routing:twinrouterbench:full-track
1009
1014
  ```
1010
1015
 
1011
- **CI smoke:** `.github/workflows/eval-harness-smoke.yml` runs on PRs that touch eval scripts, fixtures, or the workflow. It executes `routing:eval-harness:smoke`, `routing:eval-harness:corpus-smoke`, and eval unit tests — fast, offline, no provider network calls. Job timeout stays at 10 minutes. The optional full-track nightly (`.github/workflows/twinrouterbench-full-nightly.yml`, `schedule` + `workflow_dispatch` only) is **not** on `pull_request` and must not be configured as a required status check — failures there do not gate PR CI or `release:functional-smoke`.
1016
+ **CI smoke:** `.github/workflows/eval-harness-smoke.yml` runs on PRs that touch eval scripts, fixtures, **any `src/**` or `.pi/extensions/smart-router/**` change**, or the workflow. It executes `routing:eval-harness:smoke`, `routing:eval-harness:corpus-smoke`, and eval unit tests — fast, offline, no provider network calls. Job timeout stays at 10 minutes. The optional full-track nightly (`.github/workflows/twinrouterbench-full-nightly.yml`, `schedule` + `workflow_dispatch` only) is **not** on `pull_request` and must not be configured as a required status check — failures there do not gate PR CI or `release:functional-smoke`.
1017
+
1018
+ **Calibration verify:** `.github/workflows/calibration-verify.yml` runs on PRs that touch calibration config/scripts (`config/routing-calibration.json*`, `config/p-success-weights.json`, `scripts/train-routing-calibration.ts`, `scripts/verify-routing-calibration.ts`, `scripts/lib/isotonic-calibrator.ts`, `scripts/lib/oats-centroid-refinement.ts`) **or calibration-consuming routing code** (`src/domain/routing/**`, `src/domain/pipeline/**`, `src/domain/types/**`, `src/cli/**`). It builds the library and verifies `config/routing-calibration.json.example` against benchmark prompts via `npm run routing:verify-calibration`.
1012
1019
 
1013
1020
  **TwinRouterBench static track:** import step-level router-visible prefixes with execution-verified target tiers (`track: "static"`). The adapter in `scripts/eval/twinrouterbench-adapter.ts` converts static track records into native eval fixtures for the three-track harness. See `docs/gemini-research.md` §9 for methodology context.
1014
1021
 
@@ -1178,6 +1185,21 @@ npm run routing:verify-benchmark-profiles
1178
1185
 
1179
1186
  Tag-triggered publish via GitHub Actions (requires `NPMSECRET` repository secret). pi.dev gallery listing syncs automatically from npm (`pi-package` keyword); no separate submit step.
1180
1187
 
1188
+ #### PR and pre-release quality gate set
1189
+
1190
+ Operators should treat the following as the full gate set before merging routing changes and before tagging a release ([#135](https://github.com/beettlle/pi-smart-router/issues/135)):
1191
+
1192
+ | Gate | Workflow / command | Runs when |
1193
+ |------|--------------------|-----------|
1194
+ | Build / typecheck / lint / coverage | `.github/workflows/ci.yml` (`npm run verify:ci`) | Every PR and push to `main` |
1195
+ | Eval harness smoke (offline) | `.github/workflows/eval-harness-smoke.yml` | PRs touching `scripts/eval/**`, `tests/eval/**`, **`src/**`**, **`.pi/extensions/smart-router/**`**, `package.json`, or the workflow |
1196
+ | Calibration verify | `.github/workflows/calibration-verify.yml` | PRs touching calibration config/scripts or calibration-consuming routing code (`src/domain/routing/**`, `src/domain/pipeline/**`, `src/domain/types/**`, `src/cli/**`) |
1197
+ | Benchmark profile smoke | `.github/workflows/benchmark-profile-refresh.yml` (`routing:verify-benchmark-profiles`) | PRs touching fixtures / ingest / profiles |
1198
+ | Pre-release functional smoke | `npm run release:check` (Tier 0: calibration `--skip-embed` + benchmark profiles + release-gate assertions) | Operator-run before `npm version` / tag |
1199
+ | TwinRouterBench full track | `.github/workflows/twinrouterbench-full-nightly.yml` | Nightly / manual only — **never** a required PR check |
1200
+
1201
+ Required-check configuration for branch protection is a human-operator repo-settings decision; the workflow path filters above guarantee the jobs **run** on routing code edits regardless of which subset the operator marks required.
1202
+
1181
1203
  **Scope composition:** use `/skill:router-release-operator` for themed release planning (not open-ended backlog cycles). **Patch** = docs + bugfixes only; **minor** = new capability (1–3 related issues under one theme). Budgets and audit rules: [`skills/router-release-operator/references/release-profiles.md`](skills/router-release-operator/references/release-profiles.md).
1182
1204
 
1183
1205
  **Tier 0 functional smoke** (`release:functional-smoke`) runs before tag publish and chains:
@@ -7,8 +7,8 @@
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-08-20",
11
- "catalog_freeze_date": "2026-08-20"
10
+ "scrape_date": "2026-08-22",
11
+ "catalog_freeze_date": "2026-08-22"
12
12
  },
13
13
  "aliases": {
14
14
  "anthropic/claude-opus-4": "claude-opus-4-5",
@@ -58,6 +58,12 @@
58
58
  "max_messages": 12,
59
59
  "max_tokens": 16384,
60
60
  "exclude_execution_history": true
61
+ },
62
+ "global_timeout_ms": 120000,
63
+ "sub_call_timeout_ms": 30000,
64
+ "_timeouts_documentation": {
65
+ "global_timeout_ms": "Global cap (ms) for the whole delegate stage; mirrors llm-use WORKER_GLOBAL_TIMEOUT. Default 120000 (SP-213, #120).",
66
+ "sub_call_timeout_ms": "Per-call cap (ms) for each delegate sub-call worker; mirrors llm-use WORKER_CALL_TIMEOUT. Default 30000 (SP-213, #120)."
61
67
  }
62
68
  },
63
69
  "local_zero": {
@@ -143,8 +143,34 @@ export declare class RouterPipeline {
143
143
  private currentPrewarmOutcome;
144
144
  /** Lazily created session-scoped prewarm guard (acceptance state spans routes). */
145
145
  private prewarmGuardInstance;
146
+ /**
147
+ * Single-flight serialization tail (SP-230, #141).
148
+ *
149
+ * Concurrency contract: `route()` calls on one RouterPipeline instance are
150
+ * serialized — a concurrent caller queues behind the in-flight call. The
151
+ * pipeline keeps per-route transient state on instance fields (the
152
+ * `current*` / `activeFleet` / `fullFleet` members above), which every stage
153
+ * reads and writes; overlapping route() executions would race on that state
154
+ * and corrupt routing decisions (e.g. a second call's reset swapping
155
+ * `activeFleet` mid-flight for the first). Routing is a fast, bounded,
156
+ * in-memory computation, so serialization costs at most one routing latency
157
+ * of queuing and never changes routing policy outcomes.
158
+ *
159
+ * Safety notes:
160
+ * - No reentrancy: nothing on a route() execution path awaits another
161
+ * route()/dispatch() on the same instance, so the chain cannot deadlock.
162
+ * - The tail is chained with a rejection handler so a rejected call (the
163
+ * zero-crash catch makes this defensive-only) cannot wedge the queue.
164
+ */
165
+ private routeTail;
146
166
  constructor(fleet: readonly ModelProfile[], options?: PipelineOptions);
167
+ /**
168
+ * Route a request through the pipeline. Concurrent calls are serialized
169
+ * (single-flight) — see `routeTail` for the concurrency contract (SP-230).
170
+ */
147
171
  route(request: RoutingRequest, fleetOverride?: readonly ModelProfile[]): Promise<RoutingDecision>;
172
+ /** Exclusive-route body — never invoke concurrently; see `route()` (SP-230). */
173
+ private routeExclusive;
148
174
  /**
149
175
  * SP-080: move Google/Gemini profiles to the end of the fleet when prior tool
150
176
  * calls exist so tier `.find()` passes prefer non-Gemini models first.
@@ -1 +1 @@
1
- {"version":3,"file":"router-pipeline.d.ts","sourceRoot":"","sources":["../../../src/domain/pipeline/router-pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router-pipeline.d.ts","sourceRoot":"","sources":["../../../src/domain/pipeline/router-pipeline.ts"],"names":[],"mappings":"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"}
@@ -157,6 +157,26 @@ export class RouterPipeline {
157
157
  currentPrewarmOutcome = null;
158
158
  /** Lazily created session-scoped prewarm guard (acceptance state spans routes). */
159
159
  prewarmGuardInstance = null;
160
+ /**
161
+ * Single-flight serialization tail (SP-230, #141).
162
+ *
163
+ * Concurrency contract: `route()` calls on one RouterPipeline instance are
164
+ * serialized — a concurrent caller queues behind the in-flight call. The
165
+ * pipeline keeps per-route transient state on instance fields (the
166
+ * `current*` / `activeFleet` / `fullFleet` members above), which every stage
167
+ * reads and writes; overlapping route() executions would race on that state
168
+ * and corrupt routing decisions (e.g. a second call's reset swapping
169
+ * `activeFleet` mid-flight for the first). Routing is a fast, bounded,
170
+ * in-memory computation, so serialization costs at most one routing latency
171
+ * of queuing and never changes routing policy outcomes.
172
+ *
173
+ * Safety notes:
174
+ * - No reentrancy: nothing on a route() execution path awaits another
175
+ * route()/dispatch() on the same instance, so the chain cannot deadlock.
176
+ * - The tail is chained with a rejection handler so a rejected call (the
177
+ * zero-crash catch makes this defensive-only) cannot wedge the queue.
178
+ */
179
+ routeTail = Promise.resolve();
160
180
  constructor(fleet, options) {
161
181
  this.fleet = fleet;
162
182
  this.options = options ?? {};
@@ -175,7 +195,20 @@ export class RouterPipeline {
175
195
  { name: 'context_overflow_fallback', run: this.contextOverflowFallback.bind(this) },
176
196
  ];
177
197
  }
198
+ /**
199
+ * Route a request through the pipeline. Concurrent calls are serialized
200
+ * (single-flight) — see `routeTail` for the concurrency contract (SP-230).
201
+ */
178
202
  async route(request, fleetOverride) {
203
+ const queued = this.routeTail.then(() => this.routeExclusive(request, fleetOverride));
204
+ // Keep the tail alive even if a call rejects (defensive: routeExclusive
205
+ // catches stage errors, but a throw outside that catch must not wedge the
206
+ // queue for subsequent callers).
207
+ this.routeTail = queued.then(() => undefined, () => undefined);
208
+ return queued;
209
+ }
210
+ /** Exclusive-route body — never invoke concurrently; see `route()` (SP-230). */
211
+ async routeExclusive(request, fleetOverride) {
179
212
  const start = Date.now();
180
213
  this.activeFleet = this.prioritizeFleetForToolHistory(fleetOverride ?? this.fleet, request);
181
214
  this.fullFleet = this.activeFleet;
@@ -1174,6 +1207,12 @@ export class RouterPipeline {
1174
1207
  };
1175
1208
  }
1176
1209
  logExpectedCostExplain(pSuccessCheap, alpha, selection, calibration) {
1210
+ // SP-223 / #138: gate stdout explain behind SMART_ROUTER_LOG_ROUTING —
1211
+ // the full payload is already captured in decision features/telemetry, so
1212
+ // default runs must not flood stdout on every eligible route.
1213
+ if (process.env.SMART_ROUTER_LOG_ROUTING !== '1') {
1214
+ return;
1215
+ }
1177
1216
  console.info('Expected-cost tier gate', {
1178
1217
  reason: selection.reasonCode,
1179
1218
  p_success_cheap: pSuccessCheap,