lynkr 9.7.2 → 9.9.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.
Files changed (80) hide show
  1. package/README.md +29 -19
  2. package/bin/cli.js +11 -0
  3. package/bin/lynkr-init.js +14 -1
  4. package/bin/lynkr-usage.js +78 -0
  5. package/bin/wrap.js +60 -35
  6. package/config/difficulty-anchors.json +22 -0
  7. package/package.json +24 -3
  8. package/scripts/audit-log-reader.js +399 -0
  9. package/scripts/calibrate-thresholds.js +38 -157
  10. package/scripts/compact-dictionary.js +204 -0
  11. package/scripts/test-deduplication.js +448 -0
  12. package/scripts/ws7-anchor-replay.js +108 -0
  13. package/skills/lynkr/SKILL.md +195 -0
  14. package/src/agents/context-manager.js +18 -2
  15. package/src/agents/definitions/loader.js +90 -0
  16. package/src/agents/executor.js +24 -2
  17. package/src/agents/index.js +9 -1
  18. package/src/agents/parallel-coordinator.js +2 -2
  19. package/src/agents/reflector.js +11 -1
  20. package/src/api/middleware/loop-guard.js +87 -0
  21. package/src/api/middleware/request-logging.js +5 -64
  22. package/src/api/middleware/session.js +0 -0
  23. package/src/api/openai-router.js +120 -101
  24. package/src/api/providers-handler.js +27 -2
  25. package/src/api/router.js +450 -125
  26. package/src/budget/index.js +2 -19
  27. package/src/cache/semantic.js +9 -0
  28. package/src/clients/databricks.js +459 -146
  29. package/src/clients/gpt-utils.js +11 -105
  30. package/src/clients/openai-format.js +10 -3
  31. package/src/clients/openrouter-utils.js +49 -24
  32. package/src/clients/prompt-cache-injection.js +1 -0
  33. package/src/clients/provider-capabilities.js +1 -1
  34. package/src/clients/responses-format.js +34 -3
  35. package/src/clients/routing.js +15 -0
  36. package/src/config/index.js +36 -2
  37. package/src/context/gcf.js +275 -0
  38. package/src/context/tool-result-compressor.js +51 -9
  39. package/src/dashboard/api.js +1 -0
  40. package/src/logger/index.js +14 -1
  41. package/src/memory/search.js +12 -40
  42. package/src/memory/tools.js +3 -24
  43. package/src/orchestrator/bypass.js +4 -2
  44. package/src/orchestrator/index.js +144 -88
  45. package/src/routing/affinity-store.js +194 -0
  46. package/src/routing/agentic-detector.js +36 -6
  47. package/src/routing/bandit.js +25 -6
  48. package/src/routing/calibration.js +212 -0
  49. package/src/routing/client-profiles.js +292 -0
  50. package/src/routing/complexity-analyzer.js +48 -11
  51. package/src/routing/deescalator.js +148 -0
  52. package/src/routing/degradation.js +109 -0
  53. package/src/routing/feedback.js +157 -0
  54. package/src/routing/index.js +897 -87
  55. package/src/routing/intent-score.js +339 -0
  56. package/src/routing/interaction.js +3 -0
  57. package/src/routing/knn-router.js +70 -21
  58. package/src/routing/model-registry.js +28 -7
  59. package/src/routing/model-tiers.js +25 -2
  60. package/src/routing/reward-pipeline.js +68 -2
  61. package/src/routing/risk-analyzer.js +30 -1
  62. package/src/routing/risk-classifier.js +6 -2
  63. package/src/routing/session-affinity.js +162 -34
  64. package/src/routing/telemetry.js +298 -10
  65. package/src/routing/verifier.js +267 -0
  66. package/src/server.js +66 -21
  67. package/src/sessions/cleanup.js +17 -0
  68. package/src/tools/index.js +1 -15
  69. package/src/tools/smart-selection.js +10 -0
  70. package/src/tools/web-client.js +3 -3
  71. package/.eslintrc.cjs +0 -12
  72. package/benchmark-configs/litellm_config.yaml +0 -86
  73. package/benchmark-configs/lynkr.env +0 -48
  74. package/benchmark-configs/portkey-config.json +0 -60
  75. package/benchmark-configs/portkey-docker.sh +0 -23
  76. package/benchmark-tier-routing.js +0 -449
  77. package/funding.json +0 -110
  78. package/src/api/middleware/validation.js +0 -261
  79. package/src/routing/drift-monitor.js +0 -113
  80. package/src/workers/helpers.js +0 -185
@@ -0,0 +1,157 @@
1
+ /**
2
+ * WS5.3 — feedback loop.
3
+ *
4
+ * Every routing decision produces an outcome (quality, cost, latency,
5
+ * status, error). Historically the bandit's `update()` was a dead export
6
+ * and `reward-pipeline.js` had no importer at all — the ML pipeline was
7
+ * built but never trained. This module closes that loop:
8
+ *
9
+ * 1. Compute the reward from the outcome (`reward-pipeline`).
10
+ * 2. If the decision came from a bandit pick, feed the reward back with
11
+ * the same context vector the bandit saw (`bandit.update`).
12
+ * 3. If we captured the query embedding at decision time and the outcome
13
+ * is conclusive (quality ≥ 70 or ≤ 40), add the entry to the kNN
14
+ * index for online growth.
15
+ *
16
+ * Contracts:
17
+ * - `recordOutcome()` NEVER throws. Every failure is swallowed into the
18
+ * degradation registry so we never poison the response path.
19
+ * - Work runs on `setImmediate` — the caller is invoking this from the
20
+ * hot path right after `telemetry.record(...)`.
21
+ * - Missing / partial routing metadata is acceptable: the function
22
+ * records what it can and skips what it can't.
23
+ */
24
+
25
+ const logger = require('../logger');
26
+ const degradation = require('./degradation');
27
+ const { getRewardPipeline } = require('./reward-pipeline');
28
+ const { getBandit } = require('./bandit');
29
+ const { getKnnRouter } = require('./knn-router');
30
+
31
+ // Quality thresholds for kNN online growth. Above HIGH → positive exemplar;
32
+ // below LOW → negative exemplar. The mid-band is intentionally excluded to
33
+ // avoid polluting the index with ambiguous cases.
34
+ const KNN_POSITIVE_QUALITY = 70;
35
+ const KNN_NEGATIVE_QUALITY = 40;
36
+
37
+ /**
38
+ * @param {object} args
39
+ * @param {object} args.routingResult — the decision returned by
40
+ * `determineProviderSmart` (or `pickTierByIntent` — see call sites in
41
+ * src/clients/databricks.js). Only underscored internals are consumed.
42
+ * @param {object} args.body — the original request body (unused today but
43
+ * accepted so future signals — user id, workspace, task id — can be
44
+ * threaded without a signature change).
45
+ * @param {object} args.outcome — {qualityScore, costUsd, latencyMs,
46
+ * statusCode, errorType, wasFallback}.
47
+ * @returns {void}
48
+ */
49
+ function recordOutcome(args) {
50
+ // Never trust the caller's shape. Any non-object gets silently dropped —
51
+ // the response path is not the place to surface a caller bug.
52
+ const safeArgs = (args && typeof args === 'object') ? args : {};
53
+ setImmediate(() => {
54
+ try {
55
+ _recordOutcomeSync(safeArgs);
56
+ } catch (err) {
57
+ // Absolute last-resort guard. Every sub-call inside _recordOutcomeSync
58
+ // already has its own try/catch → degradation.record(...); this catch
59
+ // exists so a truly unexpected throw (e.g. degradation itself blowing
60
+ // up) can never leak.
61
+ try { degradation.record('feedback', err); } catch (_) { /* nope */ }
62
+ }
63
+ });
64
+ }
65
+
66
+ function _recordOutcomeSync({ routingResult, outcome }) {
67
+ if (!routingResult || !outcome) return;
68
+
69
+ const {
70
+ qualityScore = null,
71
+ costUsd = null,
72
+ latencyMs = null,
73
+ } = outcome;
74
+
75
+ let reward = null;
76
+ try {
77
+ const pipeline = getRewardPipeline();
78
+ reward = pipeline.reward({
79
+ quality: qualityScore,
80
+ cost: costUsd ?? 0,
81
+ latency: latencyMs ?? 0,
82
+ });
83
+ } catch (err) {
84
+ degradation.record('feedback', err);
85
+ reward = null;
86
+ }
87
+
88
+ // Bandit update — only when the decision actually came from a bandit pick
89
+ // (i.e. we stashed the context vector on the decision). Without ctx, an
90
+ // update would be meaningless: LinUCB's A/b matrices require the same
91
+ // feature vector the arm was scored on.
92
+ if (routingResult._banditContext
93
+ && routingResult.provider
94
+ && routingResult.model
95
+ && routingResult.tier
96
+ && typeof reward === 'number') {
97
+ try {
98
+ const bandit = getBandit();
99
+ bandit.update(
100
+ routingResult.tier,
101
+ routingResult.provider,
102
+ routingResult.model,
103
+ routingResult._banditContext,
104
+ reward,
105
+ );
106
+ } catch (err) {
107
+ degradation.record('feedback', err);
108
+ }
109
+ }
110
+
111
+ // kNN online growth — conclusive quality only. We paid for the embedding
112
+ // at decision time (stashed on `_queryEmbedding`), so add() is essentially
113
+ // free. Skipping the mid-band (40 < q < 70) keeps the index from filling
114
+ // up with ambiguous exemplars that would only muddy future advice.
115
+ if (routingResult._queryEmbedding
116
+ && typeof qualityScore === 'number'
117
+ && (qualityScore >= KNN_POSITIVE_QUALITY || qualityScore <= KNN_NEGATIVE_QUALITY)) {
118
+ try {
119
+ const router = getKnnRouter();
120
+ router.add(routingResult._queryEmbedding, {
121
+ query: routingResult._queryText ?? null,
122
+ provider: routingResult.provider,
123
+ model: routingResult.model,
124
+ tier: routingResult.tier,
125
+ quality: qualityScore,
126
+ cost: costUsd ?? 0,
127
+ latency: latencyMs ?? 0,
128
+ // A negative exemplar is worth recording so future queries whose
129
+ // neighbours are these bad outcomes score poorly. The router's
130
+ // score function already blends quality naturally; no separate
131
+ // "polarity" flag is needed.
132
+ });
133
+ } catch (err) {
134
+ degradation.record('feedback', err);
135
+ }
136
+ }
137
+
138
+ if (reward != null) {
139
+ logger.debug({
140
+ reward: reward.toFixed(2),
141
+ tier: routingResult.tier,
142
+ provider: routingResult.provider,
143
+ model: routingResult.model,
144
+ hasBandit: !!routingResult._banditContext,
145
+ hasEmbedding: !!routingResult._queryEmbedding,
146
+ qualityScore,
147
+ }, '[Feedback] Outcome recorded');
148
+ }
149
+ }
150
+
151
+ module.exports = {
152
+ recordOutcome,
153
+ // Test-only export so unit tests can drive the sync path directly.
154
+ _recordOutcomeSync,
155
+ KNN_POSITIVE_QUALITY,
156
+ KNN_NEGATIVE_QUALITY,
157
+ };