lynkr 9.14.14 → 9.14.16

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.
@@ -28,5 +28,17 @@
28
28
  "design a horizontally scalable architecture for this service with failure-mode analysis",
29
29
  "Compare how databricks.js and openai-format.js each handle non-2xx upstream responses, then propose a unified error-normalization helper both could share",
30
30
  "Code review the PR #84 routing hardening changes"
31
+ ],
32
+ "frontier": [
33
+ "Prove the correctness of this lock-free queue implementation and identify any ABA hazards",
34
+ "Design a novel conflict-free replicated data type for collaborative rich-text editing and argue its convergence from first principles",
35
+ "Derive the optimal cache eviction policy for this access distribution and prove its competitive ratio",
36
+ "Diagnose this heisenbug: a race that only reproduces under load across three services, given these interleaved logs",
37
+ "Design a Byzantine-fault-tolerant consensus protocol variant that tolerates f faults with 2f+1 replicas by weakening liveness, and analyze exactly which guarantees are lost",
38
+ "Formally verify that this state machine can never deadlock, or produce a counterexample trace",
39
+ "Given these profiler traces, determine whether the tail latency is queueing-theoretic or GC-driven, and prove which by constructing a discriminating experiment",
40
+ "Reason through the security of this key-rotation scheme under an adversary who can observe but not modify traffic, and find the weakest assumption it relies on",
41
+ "Work out the exact memory ordering constraints this concurrent hashmap needs on ARM, and justify each barrier from the C++ memory model",
42
+ "Given this failing distributed transaction trace, determine whether the anomaly is write skew or lost update, and design the minimal isolation-level change that eliminates it"
31
43
  ]
32
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "9.14.14",
3
+ "version": "9.14.16",
4
4
  "files": [
5
5
  "index.js",
6
6
  "install.sh",
@@ -38,7 +38,7 @@
38
38
  "start:supervised": "while true; do node index.js 2>&1 | npx pino-pretty --sync; code=$?; echo \"[supervisor] lynkr exited ($code) — restarting in 3s\"; sleep 3; done",
39
39
  "lint": "eslint src index.js",
40
40
  "test": "npm run test:unit && npm run test:performance",
41
- "test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js test/jev-router.test.js test/jev-routing.test.js test/force-patterns.test.js test/upstream-fidelity.test.js test/tool-schema-compression.test.js test/passthrough-route.test.js test/quota-ledger.test.js",
41
+ "test:unit": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com LOG_FILE_ENABLED=false node --test test/routing.test.js test/hybrid-routing-integration.test.js test/retry-logic.test.js test/sse-transformer.test.js test/passthrough-stream.test.js test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js test/azure-openai-config.test.js test/azure-openai-format-conversion.test.js test/azure-openai-routing.test.js test/azure-openai-streaming.test.js test/azure-openai-error-resilience.test.js test/azure-openai-integration.test.js test/openai-integration.test.js test/atlas-integration.test.js test/toon-compression.test.js test/gcf-compression.test.js test/llamacpp-integration.test.js test/resilience.test.js test/telemetry-routing.test.js test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js test/distill.test.js test/large-payload.test.js test/prompt-cache-injection.test.js test/risk-analyzer.test.js test/interaction-block.test.js test/preflight.test.js test/token-reduction.test.js test/session-affinity.test.js test/cache-state.test.js test/cache-switch-cost.test.js test/lens-recommendations.test.js test/model-registry-cost.test.js test/output-format-guard.test.js test/tier-fallback.test.js test/wrap.test.js test/init.test.js test/tool-call-response-metadata.test.js test/degradation.test.js test/routing-telemetry-columns.test.js test/sticky-routing.test.js test/knn-ambiguous-escalate.test.js test/deescalator.test.js test/client-profiles.test.js test/strip-internal-fields.test.js test/complexity-tool-subtraction.test.js test/bandit.test.js test/routing-propensity.test.js test/reward-pipeline.test.js test/knn-cold-start.test.js test/calibration.test.js test/feedback-loop.test.js test/session-fingerprint.test.js test/side-request-guards.test.js test/verifier.test.js test/intent-score.test.js test/difficulty-classifier.test.js test/classifier-setup.test.js test/usage-stats.test.js test/loop-guard.test.js test/moonshot-model-mapping.test.js test/baidu-model-mapping.test.js test/tenant-policy-ingress-parity.test.js test/decide.test.js test/embeddings-degradation.test.js test/health-probe.test.js test/stuck-detector.test.js test/onnx-embedder.test.js test/ope.test.js test/hierarchical-budget.test.js test/token-rate-limit.test.js test/otel-export.test.js test/mcp-broker.test.js test/compression-budget.test.js test/gpt-utils.test.js test/dedup-observe-only.test.js test/context-window-header.test.js test/token-budget-auto.test.js test/opencode-setup.test.js test/auth-mode-first-party.test.js test/harness-envelope.test.js test/task-ledger.test.js test/jev-router.test.js test/jev-routing.test.js test/force-patterns.test.js test/upstream-fidelity.test.js test/tool-schema-compression.test.js test/passthrough-route.test.js test/quota-ledger.test.js",
42
42
  "test:memory": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/memory/store.test.js test/memory/surprise.test.js test/memory/extractor.test.js test/memory/search.test.js test/memory/retriever.test.js test/memory/distiller.test.js test/memory/distiller-freeze.test.js test/memory/wiki.test.js test/memory/skills-cache.test.js test/memory/tencentdb-launcher.test.js",
43
43
  "test:new-features": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node --test test/passthrough-mode.test.js test/openrouter-error-resilience.test.js test/format-conversion.test.js",
44
44
  "test:performance": "LYNKR_KNN_DIR=/tmp/lynkr-test-knn DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/hybrid-routing-performance.test.js && DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com node test/performance-tests.js",
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Harvest short follow-up turns from routing telemetry for the TaskBand
4
+ * continuation-detector eval set.
5
+ *
6
+ * Reads <cwd>/.lynkr/telemetry.db (read-only — never mutates production
7
+ * telemetry), groups rows by session ordered by timestamp, and emits every
8
+ * turn whose request_text is ≤200 chars: short asks are where "do the same
9
+ * here" follow-ups live, and where single-message scoring is blindest.
10
+ * Each line carries up to 3 prior asks from the same session so a labeler
11
+ * can judge continuation vs. new task without replaying the session.
12
+ *
13
+ * Output: data/followup-eval-unlabeled.jsonl (gitignored), one JSON object
14
+ * per line: {session_id, ts, request_text, prior_asks, chosen_tier,
15
+ * complexity_score, label: null} — label is filled in by hand.
16
+ *
17
+ * Usage: node scripts/harvest-followup-eval.js
18
+ */
19
+
20
+ const fs = require("fs");
21
+ const path = require("path");
22
+
23
+ const MAX_ASK_CHARS = 200;
24
+ const MAX_PRIOR_ASKS = 3;
25
+ const OUT_FILE = path.join(__dirname, "../data/followup-eval-unlabeled.jsonl");
26
+
27
+ let Database;
28
+ try {
29
+ Database = require("better-sqlite3");
30
+ } catch {
31
+ console.error("better-sqlite3 is not installed (it is an optionalDependency) — cannot read telemetry.db.");
32
+ console.error("Install it with: npm install better-sqlite3");
33
+ process.exit(1);
34
+ }
35
+
36
+ function main() {
37
+ const dbPath = path.join(process.cwd(), ".lynkr", "telemetry.db");
38
+ if (!fs.existsSync(dbPath)) {
39
+ console.error(`No telemetry database at ${dbPath} — run the proxy from this directory first to collect telemetry.`);
40
+ process.exit(1);
41
+ }
42
+
43
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
44
+
45
+ const rows = db
46
+ .prepare(
47
+ `SELECT session_id, timestamp, request_text, tier, complexity_score
48
+ FROM routing_telemetry
49
+ WHERE session_id IS NOT NULL AND request_text IS NOT NULL AND request_text != ''
50
+ ORDER BY session_id, timestamp ASC`
51
+ )
52
+ .all();
53
+ db.close();
54
+
55
+ const out = [];
56
+ let sessions = 0;
57
+ let prevSession = null;
58
+ let priorAsks = [];
59
+
60
+ for (const row of rows) {
61
+ if (row.session_id !== prevSession) {
62
+ prevSession = row.session_id;
63
+ priorAsks = [];
64
+ sessions++;
65
+ }
66
+ const text = String(row.request_text);
67
+ if (text.length <= MAX_ASK_CHARS) {
68
+ out.push(JSON.stringify({
69
+ session_id: row.session_id,
70
+ ts: row.timestamp,
71
+ request_text: text,
72
+ prior_asks: priorAsks.slice(-MAX_PRIOR_ASKS),
73
+ chosen_tier: row.tier ?? null,
74
+ complexity_score: row.complexity_score ?? null,
75
+ label: null,
76
+ }));
77
+ }
78
+ priorAsks.push(text);
79
+ }
80
+
81
+ fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
82
+ fs.writeFileSync(OUT_FILE, out.length ? out.join("\n") + "\n" : "");
83
+ console.log(`Scanned ${rows.length} rows across ${sessions} sessions.`);
84
+ console.log(`Wrote ${OUT_FILE}: ${out.length} candidate follow-up turns (label: null — awaiting hand labels).`);
85
+ }
86
+
87
+ main();
package/src/api/router.js CHANGED
@@ -10,6 +10,7 @@ const providersRouter = require("./providers-handler");
10
10
  const claudeDesktopGatewayRouter = require("./claude-desktop-gateway");
11
11
  const { getRoutingHeaders, getRoutingStats, analyzeComplexity, getModelTierSelector, analyzeRisk, checkSessionPin, writeSessionPin, checkPinScoreDrift } = require("../routing");
12
12
  const { resolveTierForModelId } = require("../routing/model-slots");
13
+ const { stripHarnessEnvelope } = require("../routing/harness-envelope");
13
14
 
14
15
  // Upstream streams can die without a clean end (reader.read() never
15
16
  // resolves on a dropped socket), hanging the client forever. Every
@@ -44,6 +45,24 @@ const router = express.Router();
44
45
 
45
46
  const rateLimiter = createRateLimiter();
46
47
 
48
+ // TaskBand holdout: a deterministic slice of sessions never receives the
49
+ // continuation floor, so telemetry keeps a running baseline cohort proving
50
+ // floored sessions aren't quietly doing worse. FNV-1a bucketing mirrors
51
+ // deescalator.isHeldOut; the percentage is task-ledger's hardcoded
52
+ // HOLDOUT_PCT (no env knob by design).
53
+ function _taskbandHoldout(sessionKey, pct) {
54
+ if (!sessionKey) return false;
55
+ const cut = Math.round(Math.max(0, Math.min(1, Number(pct) || 0)) * 100);
56
+ if (cut === 0) return false;
57
+ let hash = 0x811c9dc5;
58
+ const s = String(sessionKey);
59
+ for (let i = 0; i < s.length; i++) {
60
+ hash ^= s.charCodeAt(i);
61
+ hash = Math.imul(hash, 0x01000193);
62
+ }
63
+ return ((hash >>> 0) % 100) < cut;
64
+ }
65
+
47
66
  /**
48
67
  * Decide which tier/provider/model handles an OAuth-subscription request.
49
68
  *
@@ -170,45 +189,25 @@ async function pickTierByIntent(body) {
170
189
  };
171
190
  }
172
191
 
173
- // Condense a message's text for classifier context: text blocks only,
174
- // reminders stripped, whitespace collapsed, hard-capped. Best-effort.
175
- const _condenseText = (msg, max = 140) => {
176
- if (!msg) return '';
177
- let text = '';
178
- if (typeof msg.content === 'string') text = msg.content;
179
- else if (Array.isArray(msg.content)) {
180
- text = msg.content
181
- .filter((b) => b?.type === 'text' && typeof b.text === 'string')
182
- .map((b) => b.text)
183
- .join(' ');
184
- }
185
- return stripReminders(text).replace(/\s+/g, ' ').trim().slice(0, max);
186
- };
187
-
188
- // Conversation context for the difficulty classifier: the nearest prior
189
- // assistant reply and the user turn before it. A bare follow-up ("Who
190
- // kills him ?") is unclassifiable in isolation — the single-message
192
+ // Task-ledger context for the difficulty judge: a slim stable string
193
+ // (task anchor + last ask) folded from the PREFIX of the thread — the ask
194
+ // being scored never feeds its own context. A bare follow-up ("Who kills
195
+ // him ?") is unclassifiable in isolation — the single-message
191
196
  // intentPayload below is context-blind BY DESIGN (envelope invariance),
192
197
  // so the context travels as a separate condensed string, never as extra
193
- // messages that would pollute the anchor score.
194
- const _contextBefore = (msg) => {
198
+ // messages that would pollute the anchor score. The ledger's prefix memo
199
+ // keeps the per-index derivation O(1) per new ask.
200
+ let _taskLedgerMod = null;
201
+ try { _taskLedgerMod = require("../routing/task-ledger"); } catch { /* context degrades to null */ }
202
+ const _ledgerBefore = (msg) => {
203
+ if (!_taskLedgerMod) return null;
195
204
  const idx = messages.indexOf(msg);
196
205
  if (idx <= 0) return null;
197
- let assistantText = '';
198
- let priorUserText = '';
199
- for (let j = idx - 1; j >= 0; j--) {
200
- const m = messages[j];
201
- if (!assistantText && m?.role === 'assistant') assistantText = _condenseText(m);
202
- else if (assistantText && m?.role === 'user') {
203
- priorUserText = _condenseText(m);
204
- if (priorUserText) break;
205
- }
206
+ try {
207
+ return _taskLedgerMod.deriveTaskLedger(messages.slice(0, idx));
208
+ } catch {
209
+ return null;
206
210
  }
207
- if (!assistantText && !priorUserText) return null;
208
- const parts = [];
209
- if (priorUserText) parts.push(`user asked: """${priorUserText}"""`);
210
- if (assistantText) parts.push(`assistant replied about: """${assistantText}"""`);
211
- return parts.join(' → ');
212
211
  };
213
212
 
214
213
  // Per-message scoring intentionally omits _sessionId so session affinity
@@ -223,6 +222,11 @@ async function pickTierByIntent(body) {
223
222
  for (let i = 0; i < windowUserMsgs.length; i++) {
224
223
  const age = windowUserMsgs.length - 1 - i; // 0 = latest, length-1 = oldest in window
225
224
  const cleaned = cleanMsg(windowUserMsgs[i]);
225
+ const _msgLedger = _ledgerBefore(windowUserMsgs[i]);
226
+ let _msgContext = null;
227
+ try {
228
+ _msgContext = _msgLedger ? _taskLedgerMod.buildTaskContext(_msgLedger) : null;
229
+ } catch { /* context degrades to null */ }
226
230
  const intentPayload = {
227
231
  messages: cleaned ? [cleaned] : [],
228
232
  tools: intentTools,
@@ -230,10 +234,17 @@ async function pickTierByIntent(body) {
230
234
  // agentic detector inside determineProviderSmart can subtract the
231
235
  // harness's baseline tools during intent scoring too.
232
236
  _clientProfile: body?._clientProfile || null,
233
- // Classifier context (see _contextBefore). Underscored — stripped at
234
- // the outbound chokepoint like every internal field.
235
- _conversationContext: _contextBefore(windowUserMsgs[i]),
237
+ // Classifier context + memo identity (see _ledgerBefore). Underscored
238
+ // — stripped at the outbound chokepoint like every internal field.
239
+ _conversationContext: _msgContext,
240
+ _taskAnchorHash: _msgLedger?.anchorHash || null,
236
241
  };
242
+ if (age === 0) {
243
+ // Continuation detection ran ONCE in the handler, on the latest typed
244
+ // ask only — windowed history messages must never carry these.
245
+ if (body?._isContinuation) intentPayload._isContinuation = true;
246
+ if (body?._inheritedFloorTier) intentPayload._inheritedFloorTier = body._inheritedFloorTier;
247
+ }
237
248
  try {
238
249
  const decision = await determineProviderSmart(intentPayload, {
239
250
  workspace: body?._workspace || null,
@@ -359,7 +370,9 @@ async function handleOauthPassthrough(req, res, opts = {}) {
359
370
  // Strip Lynkr's internal underscore-prefixed fields (_sessionId,
360
371
  // _forceProvider, _tierModel, _tierName, _forcedMethod, _baseTier,
361
372
  // _escalationSource, _pinnedRoute, _switchReason, _clientProfile,
362
- // _workspace, _tenantPolicy, _deadlineMs, _suggestionModeModel). Anthropic
373
+ // _workspace, _tenantPolicy, _deadlineMs, _suggestionModeModel,
374
+ // _conversationContext, _taskAnchorHash, _isContinuation,
375
+ // _inheritedFloorTier, _taskband). Anthropic
363
376
  // rejects unknown top-level keys with "Extra inputs are not permitted".
364
377
  // The orchestrator's downstream paths whitelist their outbound bodies,
365
378
  // but the passthrough sends this body VERBATIM — so we must strip here.
@@ -664,6 +677,7 @@ async function handleOauthPassthrough(req, res, opts = {}) {
664
677
  message_count: req.body?.messages?.length || null,
665
678
  tool_count: Array.isArray(req.body?.tools) ? req.body.tools.length : 0,
666
679
  ...tlm.jevFields(tier),
680
+ ...tlm.taskbandFields(tier),
667
681
  // Inner-decision attribution: the tier label alone can't explain a
668
682
  // serve (a COMPLEX label once served Haiku with no trace because the
669
683
  // tier object carried a null model). Record the decision's own
@@ -1180,6 +1194,96 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1180
1194
  req.body._sessionId = req.sessionId;
1181
1195
  }
1182
1196
 
1197
+ // TaskBand — fold the thread (minus the current ask) into a task ledger
1198
+ // and run continuation detection ONCE, on the latest typed ask only.
1199
+ // Stamped on the body so pin-drift checks, the per-message intent loop
1200
+ // and the difficulty judge all score from identical inputs. Best-effort
1201
+ // throughout: any failure leaves routing exactly as before TaskBand.
1202
+ let _taskLedger = null;
1203
+ let _taskCont = null;
1204
+ let _taskFloorTier = null; // would-be continuation floor (tier name)
1205
+ let _taskFloorHeldOut = false; // holdout cohort: floor logged, not applied
1206
+ let _taskPreFloorTier = null; // fresh tier before the floor raised it
1207
+ let _continuationFloored = false;
1208
+ let _taskSideRequest = false;
1209
+ let _jevStatsBefore = null;
1210
+ try { _jevStatsBefore = require("../routing/jev-router").getJevCacheStats(); } catch { /* stamp stays null */ }
1211
+ try {
1212
+ const taskLedgerMod = require("../routing/task-ledger");
1213
+ const { cleanUserText } = require("../routing/jev-router");
1214
+ const _msgs = Array.isArray(req.body?.messages) ? req.body.messages : [];
1215
+ let _askIdx = -1;
1216
+ let _askText = null;
1217
+ for (let i = _msgs.length - 1; i >= 0; i--) {
1218
+ if (_msgs[i]?.role !== 'user') continue;
1219
+ const t = cleanUserText(_msgs[i]);
1220
+ if (t) { _askIdx = i; _askText = t; break; }
1221
+ }
1222
+ if (_askIdx > 0) _taskLedger = taskLedgerMod.deriveTaskLedger(_msgs.slice(0, _askIdx));
1223
+ if (_taskLedger) {
1224
+ const _ctx = taskLedgerMod.buildTaskContext(_taskLedger);
1225
+ if (_ctx) req.body._conversationContext = _ctx;
1226
+ if (_taskLedger.anchorHash) req.body._taskAnchorHash = _taskLedger.anchorHash;
1227
+ const _embedFn = async (t) => {
1228
+ try {
1229
+ return await require("../routing/knn-router").getKnnRouter().embed(t);
1230
+ } catch { return null; }
1231
+ };
1232
+ _taskCont = await taskLedgerMod.detectContinuation(_askText, _taskLedger, _embedFn);
1233
+ if (_taskCont?.isContinuation) req.body._isContinuation = true;
1234
+ }
1235
+ const {
1236
+ FLOOR_ENABLED = true,
1237
+ DECAY_TURNS = 2,
1238
+ HOLDOUT_PCT = 0.10,
1239
+ } = taskLedgerMod;
1240
+ if (FLOOR_ENABLED && _taskCont?.isContinuation && _taskLedger?.anchorHash) {
1241
+ const TIER_ORDER = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
1242
+ let effIdx = null;
1243
+ let coldFallback = false;
1244
+ const _taskState = req.body._sessionId
1245
+ ? require("../routing/affinity-store").getTaskState(req.body._sessionId)
1246
+ : null;
1247
+ if (_taskState && _taskState.anchorHash === _taskLedger.anchorHash
1248
+ && Number.isInteger(_taskState.effectiveBand)) {
1249
+ effIdx = Math.max(0, Math.min(3, _taskState.effectiveBand));
1250
+ } else if (_taskLedger.anchorText) {
1251
+ // COLD fallback: the task predates its state row (restart, TTL,
1252
+ // anchor change). Anchor-only score of the task's first ask,
1253
+ // capped at MEDIUM — an unproven inheritance must not re-derive
1254
+ // an expensive band from a single string.
1255
+ const { scoreIntent } = require("../routing/intent-score");
1256
+ const anchorIntent = await scoreIntent(
1257
+ { messages: [{ role: 'user', content: _taskLedger.anchorText }] },
1258
+ { skipClassifier: true },
1259
+ );
1260
+ if (anchorIntent && Number.isFinite(anchorIntent.score)) {
1261
+ const t = getModelTierSelector().getTier(anchorIntent.score);
1262
+ effIdx = Math.min(TIER_ORDER.indexOf(t), TIER_ORDER.indexOf('MEDIUM'));
1263
+ coldFallback = true;
1264
+ }
1265
+ }
1266
+ let floorIdx = null;
1267
+ if (effIdx != null && effIdx >= 0) {
1268
+ if (_taskLedger.open) floorIdx = effIdx;
1269
+ else if ((_taskLedger.turnsSinceClose ?? Infinity) <= DECAY_TURNS) floorIdx = effIdx - 1;
1270
+ }
1271
+ if (floorIdx != null && coldFallback) {
1272
+ floorIdx = Math.min(floorIdx, TIER_ORDER.indexOf('COMPLEX'));
1273
+ }
1274
+ if (floorIdx != null && floorIdx >= 0) {
1275
+ _taskFloorTier = TIER_ORDER[floorIdx];
1276
+ _taskFloorHeldOut = _taskbandHoldout(req.body._sessionId, HOLDOUT_PCT);
1277
+ // Held-out sessions must stay floor-free end to end — the judge's
1278
+ // cap widening counts as an application, so the body stamp is
1279
+ // withheld too, keeping the baseline cohort clean.
1280
+ if (!_taskFloorHeldOut) req.body._inheritedFloorTier = _taskFloorTier;
1281
+ }
1282
+ }
1283
+ } catch (err) {
1284
+ logger.debug({ err: err.message }, '[TaskBand] ledger/continuation derivation failed');
1285
+ }
1286
+
1183
1287
  // WS1 — sticky-session reuse. If this session already has a valid pin
1184
1288
  // (guards pass, no compaction), skip pickTierByIntent entirely and reuse
1185
1289
  // the pinned decision. This is the biggest cost win of WS1: repeat turns
@@ -1306,7 +1410,11 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1306
1410
  }
1307
1411
  return '';
1308
1412
  })();
1413
+ // Suggestion-mode detection reads the RAW text (the marker is itself
1414
+ // wrapper text); the force/risk probes below get envelope-stripped text
1415
+ // so Cursor's <user_info>/<rules> blocks can't fire triggers on "Hi".
1309
1416
  const isSuggestionMode = _lastUserText.includes('[SUGGESTION MODE:');
1417
+ const _lastUserAskClean = stripHarnessEnvelope(_lastUserText);
1310
1418
  // Tool-lessness alone is NOT harness evidence: generic API clients
1311
1419
  // (curl, benchmarks, SDKs) legitimately send bare messages and must get
1312
1420
  // full routing — live regression 2026-07-08: a benchmark's security-
@@ -1358,6 +1466,9 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1358
1466
  || isBareRequest
1359
1467
  || isFingerprintDrift
1360
1468
  || ((!Array.isArray(req.body?.tools) || req.body.tools.length === 0) && isKnownHarness);
1469
+ // Task state must never see side traffic (hoisted for the upkeep block
1470
+ // at the final-decision site — the model-id-pin branch skips this scope).
1471
+ _taskSideRequest = isSideRequest;
1361
1472
  // Side requests short-circuit to the static SIMPLE tier: no pin read
1362
1473
  // (a COMPLEX-pinned conversation would burn expensive tokens on
1363
1474
  // autocomplete), no pin write, no intent scoring of wrapper text.
@@ -1429,7 +1540,7 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1429
1540
  let _pinForceBypass = null;
1430
1541
  if (!sideTier && pinCheck.serve && pinCheck.reason === 'guards_passed' && !isSideRequest) {
1431
1542
  try {
1432
- const _probe = { messages: [{ role: 'user', content: _lastUserText || '' }] };
1543
+ const _probe = { messages: [{ role: 'user', content: _lastUserAskClean || '' }] };
1433
1544
  const ca = require("../routing/complexity-analyzer");
1434
1545
  if (_lastUserText && ca.shouldForceReasoning(_probe)) _pinForceBypass = 'force_reasoning';
1435
1546
  else if (_lastUserText && ca.shouldForceCloud(_probe)) _pinForceBypass = 'force_cloud';
@@ -1521,6 +1632,63 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1521
1632
  };
1522
1633
  }
1523
1634
  }
1635
+ // TaskBand continuation floor — fresh decisions only, and only to
1636
+ // RAISE. Placement is deliberate: after the compaction floor so the
1637
+ // two floors compose (max wins), before the pin write so a floored
1638
+ // tier can never fossilize into a session pin (writeSessionPin also
1639
+ // refuses '+continuation_inherit', mirroring the risk skip).
1640
+ if (!isSideRequest && _taskFloorTier && tier?.tier) {
1641
+ try {
1642
+ const { TIER_DEFINITIONS } = require("../routing/model-tiers");
1643
+ const pri = (t) => TIER_DEFINITIONS[t]?.priority ?? -1;
1644
+ const wouldRaise = pri(tier.tier) < pri(_taskFloorTier);
1645
+ if (_taskFloorHeldOut && wouldRaise) {
1646
+ tier.method = (tier.method || 'tier_config') + '+continuation_holdout';
1647
+ logger.debug({
1648
+ sessionId: pinCheck.sessionId,
1649
+ freshTier: tier.tier,
1650
+ wouldFloorTo: _taskFloorTier,
1651
+ evidence: _taskCont?.evidence || [],
1652
+ }, "OAuth intent — continuation floor held out (baseline cohort)");
1653
+ } else if (wouldRaise) {
1654
+ const flooredSel = getModelTierSelector().selectModel(_taskFloorTier, null);
1655
+ if (flooredSel?.provider) {
1656
+ const _fromTier = tier.tier;
1657
+ const _fromModel = tier.model || null;
1658
+ _taskPreFloorTier = _fromTier;
1659
+ logger.info({
1660
+ sessionId: pinCheck.sessionId,
1661
+ freshTier: _fromTier,
1662
+ flooredTo: _taskFloorTier,
1663
+ evidence: _taskCont?.evidence || [],
1664
+ }, "OAuth intent — continuation inherits task band");
1665
+ tier = {
1666
+ ...tier,
1667
+ tier: _taskFloorTier,
1668
+ provider: flooredSel.provider,
1669
+ model: flooredSel.model || null,
1670
+ method: (tier.method || 'tier_config') + '+continuation_inherit',
1671
+ switch_reason: tier.switch_reason || 'continuation_inherit',
1672
+ base_tier: tier.base_tier ?? _fromTier,
1673
+ escalation_source: tier.escalation_source ?? 'continuation_inherit',
1674
+ escalations: [
1675
+ ...(Array.isArray(tier.escalations) ? tier.escalations : []),
1676
+ {
1677
+ source: 'continuation_inherit',
1678
+ fromTier: _fromTier,
1679
+ toTier: _taskFloorTier,
1680
+ fromModel: _fromModel,
1681
+ toModel: flooredSel.model || null,
1682
+ },
1683
+ ],
1684
+ };
1685
+ _continuationFloored = true;
1686
+ }
1687
+ }
1688
+ } catch (err) {
1689
+ logger.debug({ err: err.message }, '[TaskBand] continuation floor failed — fresh tier stands');
1690
+ }
1691
+ }
1524
1692
  // Persist the fresh decision so the next turn on this session can
1525
1693
  // reuse it. checkSessionPin returned serve=false (or WS1.5 drift fired),
1526
1694
  // so either there was no pin, the pin lost a guard (context/vision/
@@ -1546,6 +1714,104 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1546
1714
  tier.switch_reason = 'model_pin_drift';
1547
1715
  }
1548
1716
 
1717
+ // TaskBand telemetry stamp — travels on the decision AND (underscored)
1718
+ // on the body, so the forced-provider path can reconstitute it onto the
1719
+ // routingResult that feeds routing_telemetry.
1720
+ try {
1721
+ let _jevCacheHit = null;
1722
+ if (_jevStatsBefore) {
1723
+ const _jevStatsAfter = require("../routing/jev-router").getJevCacheStats();
1724
+ const _missDelta = _jevStatsAfter.misses - _jevStatsBefore.misses;
1725
+ const _hitDelta = _jevStatsAfter.hits - _jevStatsBefore.hits;
1726
+ // Null means the judge never consulted its cache this request
1727
+ // (pin serve, side frame, classifier skip) — "not measured".
1728
+ _jevCacheHit = _missDelta > 0 ? 0 : (_hitDelta > 0 ? 1 : null);
1729
+ }
1730
+ tier.taskband = {
1731
+ isContinuation: _taskCont?.isContinuation ? 1 : 0,
1732
+ inheritedFloor: _taskFloorTier || null,
1733
+ anchorHash: _taskLedger?.anchorHash || null,
1734
+ jevContext: typeof req.body._conversationContext === 'string'
1735
+ ? req.body._conversationContext : null,
1736
+ jevCacheHit: _jevCacheHit,
1737
+ };
1738
+ req.body._taskband = tier.taskband;
1739
+ } catch (err) {
1740
+ logger.debug({ err: err.message }, '[TaskBand] telemetry stamp failed');
1741
+ }
1742
+
1743
+ // TaskBand task-state upkeep — once per request, at the final-decision
1744
+ // site, never inside the per-message scoring loop. Side requests never
1745
+ // touch task state. Pin replays never write: a pinned tier keeps no
1746
+ // provenance of how it was chosen (a force-triggered tier pins like any
1747
+ // scored one), so replaying it must not launder into earned band — the
1748
+ // fresh serve that created the pin already merged. Model-id pins are
1749
+ // explicit client choices, not earned difficulty — no writes either.
1750
+ try {
1751
+ const _sessForTask = req.body._sessionId || null;
1752
+ if (_sessForTask && _taskLedger?.anchorHash && !_taskSideRequest) {
1753
+ const store = require("../routing/affinity-store");
1754
+ let AUTO_CLOSE_LOW_TURNS = 3;
1755
+ try {
1756
+ const _tl = require("../routing/task-ledger");
1757
+ if (Number.isInteger(_tl.AUTO_CLOSE_LOW_TURNS)) AUTO_CLOSE_LOW_TURNS = _tl.AUTO_CLOSE_LOW_TURNS;
1758
+ } catch { /* default stands */ }
1759
+ const TIER_ORDER = ['SIMPLE', 'MEDIUM', 'COMPLEX', 'REASONING'];
1760
+ const _method = tier?.method || '';
1761
+ const _openNow = _taskLedger.open ? 1 : 0;
1762
+ const _row = store.getTaskState(_sessForTask);
1763
+ const _anchorMatches = !!(_row && _row.anchorHash === _taskLedger.anchorHash);
1764
+ if (_method !== 'session_pin' && _method !== 'model_id_pin' && _method !== 'fallback') {
1765
+ // Low-streak decay reads the FRESH-scored tier, not the floored
1766
+ // one — otherwise an applied floor could never auto-close.
1767
+ let _freshTier = _taskPreFloorTier || tier?.tier || null;
1768
+ // A jev-override lift rides a cap the inherited floor may have
1769
+ // widened; only the scored base tier is earned band. Merging the
1770
+ // lifted tier would raise the next turn's floor, which widens the
1771
+ // next cap — the ratchet the one-band-up cap exists to break.
1772
+ if (/(^|\+)jev_override(\+|$)/.test(_method)) {
1773
+ const _scoredIdx = TIER_ORDER.indexOf(tier?.base_tier);
1774
+ if (_scoredIdx >= 0 && _scoredIdx < TIER_ORDER.indexOf(_freshTier)) {
1775
+ _freshTier = tier.base_tier;
1776
+ }
1777
+ }
1778
+ const _freshIdx = TIER_ORDER.indexOf(_freshTier);
1779
+ const _forceOrRisk = /(^|\+)(force|risk)(\+|$)/.test(_method)
1780
+ || tier?.escalation_source === 'risk';
1781
+ if (_freshIdx >= 0) {
1782
+ let _lowStreak = 0;
1783
+ if (_anchorMatches && Number.isInteger(_row.effectiveBand)
1784
+ && _freshIdx <= _row.effectiveBand - 2) {
1785
+ _lowStreak = (_row.lowStreak ?? 0) + 1;
1786
+ }
1787
+ if (_lowStreak >= AUTO_CLOSE_LOW_TURNS) {
1788
+ store.clearTaskState(_sessForTask);
1789
+ } else if (!_continuationFloored && !_forceOrRisk) {
1790
+ // MAX-merge on matching anchor, whole-row overwrite on an
1791
+ // anchor change — both live inside setTaskState.
1792
+ store.setTaskState(_sessForTask, {
1793
+ anchorHash: _taskLedger.anchorHash,
1794
+ effectiveBand: _freshIdx,
1795
+ open: _openNow,
1796
+ lowStreak: _lowStreak,
1797
+ });
1798
+ } else if (_anchorMatches) {
1799
+ // Floored/force/risk serves never merge their band, but the
1800
+ // marker-close and the low streak still have to land.
1801
+ store.setTaskState(_sessForTask, {
1802
+ anchorHash: _row.anchorHash,
1803
+ effectiveBand: Number.isInteger(_row.effectiveBand) ? _row.effectiveBand : _freshIdx,
1804
+ open: _row.open && _openNow ? 1 : 0,
1805
+ lowStreak: _lowStreak,
1806
+ });
1807
+ }
1808
+ }
1809
+ }
1810
+ }
1811
+ } catch (err) {
1812
+ logger.debug({ err: err.message }, '[TaskBand] task-state upkeep failed');
1813
+ }
1814
+
1549
1815
  // Subscription-only fork: byte-for-byte passthrough to api.anthropic.com
1550
1816
  // when the picked tier resolves to azure-anthropic — FIRST-PARTY
1551
1817
  // Anthropic clients only (Claude Code / Claude Desktop wrapping their
@@ -1622,6 +1888,15 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1622
1888
  tierMethod: tier?.method || null,
1623
1889
  tierPinned: tier?.pinned ?? null,
1624
1890
  cacheState: _pinCacheState,
1891
+ // Mid tool-exchange frames must keep serving the in-flight model
1892
+ // (same invariant as the orchestrator's tool_history pin serves).
1893
+ hasToolHistory: (() => {
1894
+ try { return require("../routing/session-affinity").payloadHasToolHistory(req.body); }
1895
+ catch { return false; }
1896
+ })(),
1897
+ // This branch IS the flat-fee subscription: the gate's dollar
1898
+ // break-even leg has no premium to amortize here.
1899
+ flatRate: true,
1625
1900
  // Quota pressure shortens the hold horizon inside the gate (high
1626
1901
  // sustained burn → descents clear sooner). Zero when the ledger has
1627
1902
  // no history — the gate behaves exactly as before.
@@ -1658,6 +1933,36 @@ router.post("/v1/messages", rateLimiter, async (req, res, next) => {
1658
1933
  logger.debug({ err: err.message }, '[Routing] Pin-hold restore failed (non-fatal)');
1659
1934
  }
1660
1935
  }
1936
+ // Upgrades must persist the SERVED model into the pin, mirroring the
1937
+ // pin_hold restore above — the write at the fresh-decision site stored
1938
+ // the pre-rewrite tier model, and a pin that forgets the upgrade lets
1939
+ // later frames descend below what actually served. Unlike pin_hold,
1940
+ // an upgrade usually happens with no prior pin, so the payload comes
1941
+ // from the fresh tier object. Floored (+continuation_inherit) and
1942
+ // risk-lifted tiers stay unpinned (writeSessionPin re-checks, but the
1943
+ // method is overridden to 'session_pin' here, so guard at the source).
1944
+ if (
1945
+ _passthroughRoute.action === 'upgrade'
1946
+ && _passthroughRoute.model
1947
+ && pinCheck?.sessionId
1948
+ && tier?.provider
1949
+ && !String(tier?.method || '').includes('+continuation_inherit')
1950
+ && tier?.escalation_source !== 'risk'
1951
+ ) {
1952
+ try {
1953
+ const { writeSessionPin } = require("../routing/index");
1954
+ writeSessionPin(pinCheck.sessionId, {
1955
+ provider: tier.provider,
1956
+ model: _passthroughRoute.model,
1957
+ tier: tier.tier,
1958
+ score: tier.score ?? null,
1959
+ method: 'session_pin',
1960
+ reason: 'passthrough_upgrade',
1961
+ }, req.body);
1962
+ } catch (err) {
1963
+ logger.debug({ err: err.message }, '[Routing] Upgrade pin persist failed (non-fatal)');
1964
+ }
1965
+ }
1661
1966
  if (_passthroughRoute.action !== 'verbatim' && _passthroughRoute.model) {
1662
1967
  logger.info({
1663
1968
  from: _passthroughClientModel,