@holoscript/framework 6.0.3 → 6.0.4

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 (160) hide show
  1. package/CHANGELOG.md +1 -2
  2. package/ROADMAP.md +68 -66
  3. package/dist/{InvisibleWallet-BB6tFvRA.d.cts → InvisibleWallet-EFiuaLn3.d.cts} +1 -1
  4. package/dist/{OrchestratorAgent-BvWgf9uw.d.cts → OrchestratorAgent-CrLDGNL6.d.cts} +1 -1
  5. package/dist/agents/index.cjs +11 -10
  6. package/dist/agents/index.d.cts +4 -16
  7. package/dist/ai/index.cjs +2 -2
  8. package/dist/behavior.cjs +10 -0
  9. package/dist/economy/index.cjs +4 -4
  10. package/dist/economy/index.d.cts +2 -2
  11. package/dist/index.cjs +33 -11
  12. package/dist/index.d.cts +3 -3
  13. package/dist/swarm/index.cjs +3 -0
  14. package/package.json +14 -9
  15. package/src/__tests__/bounty-marketplace.test.ts +53 -21
  16. package/src/__tests__/delegation.test.ts +1 -4
  17. package/src/__tests__/done-log-audit.test.ts +38 -46
  18. package/src/__tests__/framework.test.ts +172 -53
  19. package/src/__tests__/goal-synthesizer.test.ts +9 -6
  20. package/src/__tests__/presence.test.ts +1 -1
  21. package/src/__tests__/protocol-agent.test.ts +12 -11
  22. package/src/__tests__/revenue-splitter.test.ts +22 -15
  23. package/src/__tests__/scenario-driven-todo.test.ts +55 -35
  24. package/src/__tests__/self-improve.test.ts +28 -9
  25. package/src/__tests__/service-lifecycle.test.ts +9 -3
  26. package/src/__tests__/skill-router.test.ts +3 -3
  27. package/src/agents/CulturalMemory.ts +6 -6
  28. package/src/agents/DelegationTraceHooks.ts +560 -0
  29. package/src/agents/FederatedRegistryAdapter.ts +1 -1
  30. package/src/agents/NormEngine.ts +3 -8
  31. package/src/agents/OrchestratorAgent.ts +1 -1
  32. package/src/agents/TaskDelegationService.ts +5 -9
  33. package/src/agents/__tests__/AgentWalletRegistry.test.ts +5 -4
  34. package/src/agents/__tests__/CrossRealityHandoff.test.ts +9 -3
  35. package/src/agents/__tests__/DelegationTraceHooks.test.ts +390 -0
  36. package/src/agents/__tests__/TaskDelegationService.test.ts +4 -2
  37. package/src/agents/spatial-comms/Layer1RealTime.ts +36 -19
  38. package/src/agents/spatial-comms/Layer2A2A.ts +1 -3
  39. package/src/agents/spatial-comms/Layer3MCP.ts +13 -4
  40. package/src/agents/spatial-comms/ProtocolTypes.ts +5 -2
  41. package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +2 -2
  42. package/src/ai/HoloScriptGenerator.ts +2 -2
  43. package/src/ai/__tests__/PerceptionSystem.prod.test.ts +1 -1
  44. package/src/ai/__tests__/PerceptionSystem.test.ts +14 -14
  45. package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +1 -1
  46. package/src/ai/index.ts +5 -1
  47. package/src/board/audit.ts +17 -6
  48. package/src/board/board-ops.ts +45 -15
  49. package/src/board/board-types.ts +94 -20
  50. package/src/delegation.ts +5 -3
  51. package/src/distributed-claimer.ts +13 -2
  52. package/src/economy/BountyManager.ts +40 -18
  53. package/src/economy/KnowledgeMarketplace.ts +27 -8
  54. package/src/economy/PaymentWebhookService.ts +0 -1
  55. package/src/economy/RevenueSplitter.ts +2 -4
  56. package/src/economy/UnifiedBudgetOptimizer.ts +8 -9
  57. package/src/economy/_core-stubs.ts +1 -1
  58. package/src/economy/x402-facilitator.ts +17 -8
  59. package/src/index.ts +16 -12
  60. package/src/knowledge/__tests__/knowledge-consolidator.test.ts +138 -89
  61. package/src/knowledge/__tests__/knowledge-store-vector.test.ts +59 -16
  62. package/src/knowledge/brain.ts +7 -7
  63. package/src/knowledge/consolidation.ts +16 -16
  64. package/src/knowledge/knowledge-consolidator.ts +60 -30
  65. package/src/knowledge/knowledge-store.ts +83 -45
  66. package/src/learning/ProceduralCompiler.ts +6 -1
  67. package/src/learning/learning/MemoryConsolidator.ts +102 -0
  68. package/src/learning/learning/MemoryScorer.ts +69 -0
  69. package/src/learning/learning/ProceduralCompiler.ts +45 -0
  70. package/src/learning/learning/SemanticClusterer.ts +66 -0
  71. package/src/llm/llm-adapter.ts +24 -10
  72. package/src/mesh/index.ts +37 -17
  73. package/src/protocol/goal-synthesizer.ts +24 -34
  74. package/src/protocol/implementations.ts +91 -22
  75. package/src/protocol/micro-phase-decomposer.ts +25 -17
  76. package/src/protocol/micro-step-decomposer.test.ts +104 -39
  77. package/src/protocol-agent.test.ts +17 -7
  78. package/src/protocol-agent.ts +45 -42
  79. package/src/self-improve/absorb-scanner.ts +9 -6
  80. package/src/self-improve/evolution-engine.ts +36 -18
  81. package/src/self-improve/framework-absorber.ts +21 -16
  82. package/src/self-improve/index.ts +2 -10
  83. package/src/self-improve/prompt-optimizer.ts +31 -19
  84. package/src/self-improve/test-generator.ts +16 -12
  85. package/src/skill-router.ts +7 -6
  86. package/src/swarm/messaging/GossipProtocol.ts +1 -1
  87. package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +31 -9
  88. package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +21 -7
  89. package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +24 -8
  90. package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +6 -2
  91. package/src/team.ts +277 -122
  92. package/src/training/scripts/generate-spatial-dataset.ts +1 -1
  93. package/src/training/training/LRScheduler.ts +377 -0
  94. package/src/training/training/QualityScoringPipeline.ts +139 -0
  95. package/src/training/training/SoftDedup.ts +461 -0
  96. package/src/training/training/SparsityMonitor.ts +685 -0
  97. package/src/training/training/SparsityMonitorTypes.ts +209 -0
  98. package/src/training/training/SpatialTrainingDataGenerator.ts +1526 -0
  99. package/src/training/training/SpatialTrainingDataTypes.ts +216 -0
  100. package/src/training/training/TrainingPipelineConfig.ts +215 -0
  101. package/src/training/training/__tests__/CorpusValidation.test.ts +87 -0
  102. package/src/training/training/__tests__/LRScheduler.test.ts +592 -0
  103. package/src/training/training/__tests__/SoftDedup.test.ts +415 -0
  104. package/src/training/training/__tests__/SparsityMonitor.test.ts +1623 -0
  105. package/src/training/training/__tests__/SpatialCorpusValidation.test.ts +72 -0
  106. package/src/training/training/__tests__/SpatialTrainingDataGenerator.test.ts +1244 -0
  107. package/src/training/training/__tests__/TrainingMonkeyIntegration.test.ts +897 -0
  108. package/src/training/training/__tests__/TrainingPipelineConfig.test.ts +202 -0
  109. package/src/training/training/__tests__/schema.test.ts +72 -0
  110. package/src/training/training/__tests__/training-constants.test.ts +106 -0
  111. package/src/training/training/__tests__/trait-mappings.test.ts +81 -0
  112. package/src/training/training/constants.ts +94 -0
  113. package/src/training/training/index.ts +17 -0
  114. package/src/training/training/schema.ts +147 -0
  115. package/src/training/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
  116. package/src/training/training/scripts/generate-spatial-dataset.ts +521 -0
  117. package/src/training/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
  118. package/src/training/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
  119. package/src/training/training/trainingmonkey/index.ts +26 -0
  120. package/src/training/training/trait-mappings.ts +157 -0
  121. package/src/types.ts +2 -7
  122. package/ALL-test-results.json +0 -1
  123. package/LICENSE +0 -21
  124. package/dist/AgentManifest-CB4xM-Ma.d.ts +0 -704
  125. package/dist/BehaviorTree-BrBFECv5.d.ts +0 -103
  126. package/dist/InvisibleWallet-rtRrBOA8.d.ts +0 -1732
  127. package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +0 -798
  128. package/dist/agents/index.d.ts +0 -1788
  129. package/dist/agents/index.js +0 -4695
  130. package/dist/ai/index.d.ts +0 -1753
  131. package/dist/ai/index.js +0 -5244
  132. package/dist/behavior.d.ts +0 -130
  133. package/dist/behavior.js +0 -407
  134. package/dist/economy/index.d.ts +0 -747
  135. package/dist/economy/index.js +0 -3617
  136. package/dist/implementations-D9T3un9D.d.ts +0 -236
  137. package/dist/index.d.ts +0 -1729
  138. package/dist/index.js +0 -24277
  139. package/dist/learning/index.d.ts +0 -104
  140. package/dist/learning/index.js +0 -189
  141. package/dist/negotiation/index.d.ts +0 -610
  142. package/dist/negotiation/index.js +0 -931
  143. package/dist/skills/index.d.ts +0 -289
  144. package/dist/skills/index.js +0 -1079
  145. package/dist/swarm/index.d.ts +0 -2433
  146. package/dist/swarm/index.js +0 -5221
  147. package/dist/training/index.d.ts +0 -1734
  148. package/dist/training/index.js +0 -2687
  149. package/extract-failures.js +0 -10
  150. package/src/training/training/data/novel-use-cases.jsonl +0 -153
  151. package/src/training/training/data/spatial-reasoning-10k.jsonl +0 -9354
  152. package/src/types/core-stubs.d.ts +0 -113
  153. package/test-output.txt +0 -0
  154. package/test-result.json +0 -1
  155. package/tsc-errors.txt +0 -4
  156. package/tsc_output.txt +0 -0
  157. package/typescript-errors-2.txt +0 -0
  158. package/typescript-errors.txt +0 -22
  159. package/vitest-log-utf8.txt +0 -268
  160. package/vitest-log.txt +0 -0
@@ -0,0 +1,592 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ LRScheduler,
4
+ createSFTScheduler,
5
+ createGRPOScheduler,
6
+ DEFAULT_LR_SCHEDULER_CONFIG,
7
+ GRPO_LR_SCHEDULER_CONFIG,
8
+ } from '../LRScheduler';
9
+
10
+ // =============================================================================
11
+ // TESTS
12
+ // =============================================================================
13
+
14
+ describe('LRScheduler', () => {
15
+ // ---------------------------------------------------------------------------
16
+ // CONSTRUCTION & CONFIGURATION
17
+ // ---------------------------------------------------------------------------
18
+
19
+ describe('constructor', () => {
20
+ it('uses default config when no overrides provided', () => {
21
+ const scheduler = new LRScheduler();
22
+ const config = scheduler.getConfig();
23
+ expect(config).toEqual(DEFAULT_LR_SCHEDULER_CONFIG);
24
+ });
25
+
26
+ it('merges partial config with defaults', () => {
27
+ const scheduler = new LRScheduler({ baseLR: 1e-3, totalSteps: 5000 });
28
+ const config = scheduler.getConfig();
29
+ expect(config.baseLR).toBe(1e-3);
30
+ expect(config.totalSteps).toBe(5000);
31
+ expect(config.warmupRatio).toBe(DEFAULT_LR_SCHEDULER_CONFIG.warmupRatio);
32
+ });
33
+
34
+ it('throws on invalid baseLR (<= 0)', () => {
35
+ expect(() => new LRScheduler({ baseLR: 0 })).toThrow('baseLR');
36
+ expect(() => new LRScheduler({ baseLR: -1e-4 })).toThrow('baseLR');
37
+ });
38
+
39
+ it('throws on invalid totalSteps (negative)', () => {
40
+ expect(() => new LRScheduler({ totalSteps: -1 })).toThrow('totalSteps');
41
+ });
42
+
43
+ it('throws on invalid totalSteps (non-integer)', () => {
44
+ expect(() => new LRScheduler({ totalSteps: 100.5 })).toThrow('totalSteps');
45
+ });
46
+
47
+ it('throws on invalid warmupRatio (< 0)', () => {
48
+ expect(() => new LRScheduler({ warmupRatio: -0.1 })).toThrow('warmupRatio');
49
+ });
50
+
51
+ it('throws on invalid warmupRatio (>= 1)', () => {
52
+ expect(() => new LRScheduler({ warmupRatio: 1.0 })).toThrow('warmupRatio');
53
+ });
54
+
55
+ it('throws on invalid minLR (negative)', () => {
56
+ expect(() => new LRScheduler({ minLR: -1 })).toThrow('minLR');
57
+ });
58
+
59
+ it('throws on invalid minLR (>= baseLR)', () => {
60
+ expect(() => new LRScheduler({ baseLR: 1e-4, minLR: 1e-4 })).toThrow('minLR');
61
+ expect(() => new LRScheduler({ baseLR: 1e-4, minLR: 2e-4 })).toThrow('minLR');
62
+ });
63
+
64
+ it('throws on invalid numCycles (< 1)', () => {
65
+ expect(() => new LRScheduler({ numCycles: 0 })).toThrow('numCycles');
66
+ });
67
+
68
+ it('throws on invalid numCycles (non-integer)', () => {
69
+ expect(() => new LRScheduler({ numCycles: 1.5 })).toThrow('numCycles');
70
+ });
71
+
72
+ it('accepts totalSteps of 0', () => {
73
+ const scheduler = new LRScheduler({ totalSteps: 0 });
74
+ expect(scheduler.getConfig().totalSteps).toBe(0);
75
+ });
76
+ });
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // WARMUP PHASE
80
+ // ---------------------------------------------------------------------------
81
+
82
+ describe('warmup phase', () => {
83
+ it('starts at LR = 0 at step 0', () => {
84
+ const scheduler = new LRScheduler({
85
+ baseLR: 2e-4,
86
+ totalSteps: 1000,
87
+ warmupRatio: 0.1,
88
+ });
89
+ expect(scheduler.getLR(0)).toBe(0);
90
+ });
91
+
92
+ it('ramps linearly to baseLR during warmup', () => {
93
+ const scheduler = new LRScheduler({
94
+ baseLR: 2e-4,
95
+ totalSteps: 1000,
96
+ warmupRatio: 0.1, // 100 warmup steps
97
+ });
98
+
99
+ // At step 50 (halfway through warmup), LR should be ~1e-4
100
+ const lr50 = scheduler.getLR(50);
101
+ expect(lr50).toBeCloseTo(1e-4, 6);
102
+
103
+ // At step 100 (end of warmup), LR should be 2e-4
104
+ const lr100 = scheduler.getLR(100);
105
+ expect(lr100).toBeCloseTo(2e-4, 6);
106
+ });
107
+
108
+ it('warmup is linear (monotonically increasing)', () => {
109
+ const scheduler = new LRScheduler({
110
+ baseLR: 2e-4,
111
+ totalSteps: 1000,
112
+ warmupRatio: 0.1,
113
+ });
114
+
115
+ let prevLR = -1;
116
+ for (let step = 0; step <= 100; step++) {
117
+ const lr = scheduler.getLR(step);
118
+ expect(lr).toBeGreaterThanOrEqual(prevLR);
119
+ prevLR = lr;
120
+ }
121
+ });
122
+
123
+ it('computes correct warmup steps', () => {
124
+ const scheduler = new LRScheduler({
125
+ totalSteps: 10000,
126
+ warmupRatio: 0.1,
127
+ });
128
+ expect(scheduler.getWarmupSteps()).toBe(1000);
129
+ });
130
+
131
+ it('warmup steps rounds down', () => {
132
+ const scheduler = new LRScheduler({
133
+ totalSteps: 1000,
134
+ warmupRatio: 0.15, // 150 steps
135
+ });
136
+ expect(scheduler.getWarmupSteps()).toBe(150);
137
+ });
138
+ });
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // COSINE DECAY PHASE
142
+ // ---------------------------------------------------------------------------
143
+
144
+ describe('cosine decay phase', () => {
145
+ it('starts at baseLR after warmup', () => {
146
+ const scheduler = new LRScheduler({
147
+ baseLR: 2e-4,
148
+ totalSteps: 1000,
149
+ warmupRatio: 0.1,
150
+ minLR: 0,
151
+ });
152
+
153
+ // Right at the boundary of warmup -> decay
154
+ const lr = scheduler.getLR(100);
155
+ expect(lr).toBeCloseTo(2e-4, 6);
156
+ });
157
+
158
+ it('ends at minLR at totalSteps', () => {
159
+ const scheduler = new LRScheduler({
160
+ baseLR: 2e-4,
161
+ totalSteps: 1000,
162
+ warmupRatio: 0.1,
163
+ minLR: 1e-7,
164
+ });
165
+
166
+ const lr = scheduler.getLR(1000);
167
+ expect(lr).toBeCloseTo(1e-7, 9);
168
+ });
169
+
170
+ it('ends at 0 when minLR is 0', () => {
171
+ const scheduler = new LRScheduler({
172
+ baseLR: 2e-4,
173
+ totalSteps: 1000,
174
+ warmupRatio: 0.1,
175
+ minLR: 0,
176
+ });
177
+
178
+ const lr = scheduler.getLR(1000);
179
+ expect(lr).toBeCloseTo(0, 10);
180
+ });
181
+
182
+ it('decays monotonically during single-cycle cosine', () => {
183
+ const scheduler = new LRScheduler({
184
+ baseLR: 2e-4,
185
+ totalSteps: 1000,
186
+ warmupRatio: 0.1,
187
+ minLR: 0,
188
+ numCycles: 1,
189
+ });
190
+
191
+ let prevLR = Infinity;
192
+ for (let step = 100; step <= 1000; step += 10) {
193
+ const lr = scheduler.getLR(step);
194
+ expect(lr).toBeLessThanOrEqual(prevLR + 1e-12); // Allow tiny float errors
195
+ prevLR = lr;
196
+ }
197
+ });
198
+
199
+ it('midpoint of decay is approximately (baseLR + minLR) / 2', () => {
200
+ const scheduler = new LRScheduler({
201
+ baseLR: 2e-4,
202
+ totalSteps: 1000,
203
+ warmupRatio: 0.1,
204
+ minLR: 0,
205
+ numCycles: 1,
206
+ });
207
+
208
+ // Midpoint of decay: step 550 (halfway between 100 and 1000)
209
+ const lr = scheduler.getLR(550);
210
+ const expectedMidpoint = (2e-4 + 0) / 2;
211
+ expect(lr).toBeCloseTo(expectedMidpoint, 5);
212
+ });
213
+ });
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // COSINE ANNEALING WITH WARM RESTARTS
217
+ // ---------------------------------------------------------------------------
218
+
219
+ describe('multiple cycles (warm restarts)', () => {
220
+ it('with 2 cycles, LR returns to peak mid-training', () => {
221
+ const scheduler = new LRScheduler({
222
+ baseLR: 2e-4,
223
+ totalSteps: 1000,
224
+ warmupRatio: 0.1,
225
+ minLR: 0,
226
+ numCycles: 2,
227
+ });
228
+
229
+ // At step 550, we should be halfway through cycle 2's rise back
230
+ // The cosine with 2 cycles completes a full period across the decay phase
231
+ // At step 550 (midpoint of decay), cos(2*pi*0.5) = cos(pi) = -1
232
+ // (1 + (-1)) / 2 = 0 -> minLR
233
+ const lrMid = scheduler.getLR(550);
234
+ expect(lrMid).toBeCloseTo(0, 5);
235
+
236
+ // At step 325 (quarter of decay), cos(2*pi*0.25) = cos(pi/2) = 0
237
+ // (1 + 0) / 2 = 0.5 -> baseLR * 0.5
238
+ const lrQuarter = scheduler.getLR(325);
239
+ expect(lrQuarter).toBeCloseTo(1e-4, 5);
240
+ });
241
+ });
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // EDGE CASES
245
+ // ---------------------------------------------------------------------------
246
+
247
+ describe('edge cases', () => {
248
+ it('handles totalSteps = 0', () => {
249
+ const scheduler = new LRScheduler({
250
+ baseLR: 2e-4,
251
+ totalSteps: 0,
252
+ warmupRatio: 0.1,
253
+ });
254
+
255
+ // Step 0 should return baseLR since warmupSteps = 0
256
+ const lr = scheduler.getLR(0);
257
+ expect(lr).toBe(2e-4);
258
+ });
259
+
260
+ it('handles warmupRatio = 0 (no warmup)', () => {
261
+ const scheduler = new LRScheduler({
262
+ baseLR: 2e-4,
263
+ totalSteps: 1000,
264
+ warmupRatio: 0,
265
+ minLR: 0,
266
+ });
267
+
268
+ expect(scheduler.getWarmupSteps()).toBe(0);
269
+ // Step 0 should already be at baseLR
270
+ expect(scheduler.getLR(0)).toBeCloseTo(2e-4, 6);
271
+ // End should be at minLR
272
+ expect(scheduler.getLR(1000)).toBeCloseTo(0, 10);
273
+ });
274
+
275
+ it('clamps negative step to 0', () => {
276
+ const scheduler = new LRScheduler({
277
+ baseLR: 2e-4,
278
+ totalSteps: 1000,
279
+ warmupRatio: 0.1,
280
+ });
281
+
282
+ expect(scheduler.getLR(-10)).toBe(scheduler.getLR(0));
283
+ });
284
+
285
+ it('clamps step beyond totalSteps', () => {
286
+ const scheduler = new LRScheduler({
287
+ baseLR: 2e-4,
288
+ totalSteps: 1000,
289
+ warmupRatio: 0.1,
290
+ minLR: 1e-7,
291
+ });
292
+
293
+ expect(scheduler.getLR(2000)).toBe(scheduler.getLR(1000));
294
+ });
295
+
296
+ it('handles very small totalSteps', () => {
297
+ const scheduler = new LRScheduler({
298
+ baseLR: 2e-4,
299
+ totalSteps: 1,
300
+ warmupRatio: 0.1,
301
+ });
302
+
303
+ // With 1 step and 10% warmup -> 0 warmup steps
304
+ expect(scheduler.getWarmupSteps()).toBe(0);
305
+ const lr0 = scheduler.getLR(0);
306
+ const lr1 = scheduler.getLR(1);
307
+ expect(lr0).toBeGreaterThan(0);
308
+ expect(lr1).toBeLessThanOrEqual(lr0);
309
+ });
310
+
311
+ it('handles very large totalSteps', () => {
312
+ const scheduler = new LRScheduler({
313
+ baseLR: 2e-4,
314
+ totalSteps: 1_000_000,
315
+ warmupRatio: 0.1,
316
+ });
317
+
318
+ expect(scheduler.getWarmupSteps()).toBe(100_000);
319
+ expect(scheduler.getLR(0)).toBe(0);
320
+ expect(scheduler.getLR(100_000)).toBeCloseTo(2e-4, 6);
321
+ });
322
+ });
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // SNAPSHOTS
326
+ // ---------------------------------------------------------------------------
327
+
328
+ describe('getSnapshot', () => {
329
+ it('returns warmup phase during warmup', () => {
330
+ const scheduler = new LRScheduler({
331
+ baseLR: 2e-4,
332
+ totalSteps: 1000,
333
+ warmupRatio: 0.1,
334
+ });
335
+
336
+ const snapshot = scheduler.getSnapshot(50);
337
+ expect(snapshot.phase).toBe('warmup');
338
+ expect(snapshot.step).toBe(50);
339
+ expect(snapshot.phaseProgress).toBeCloseTo(0.5, 5);
340
+ expect(snapshot.overallProgress).toBeCloseTo(0.05, 5);
341
+ expect(snapshot.learningRate).toBeCloseTo(1e-4, 6);
342
+ });
343
+
344
+ it('returns decay phase after warmup', () => {
345
+ const scheduler = new LRScheduler({
346
+ baseLR: 2e-4,
347
+ totalSteps: 1000,
348
+ warmupRatio: 0.1,
349
+ });
350
+
351
+ const snapshot = scheduler.getSnapshot(550);
352
+ expect(snapshot.phase).toBe('decay');
353
+ expect(snapshot.step).toBe(550);
354
+ expect(snapshot.phaseProgress).toBe(0.5); // (550-100)/900 = 0.5
355
+ expect(snapshot.overallProgress).toBe(0.55);
356
+ });
357
+
358
+ it('clamps step in snapshot', () => {
359
+ const scheduler = new LRScheduler({
360
+ baseLR: 2e-4,
361
+ totalSteps: 1000,
362
+ });
363
+
364
+ const snapshot = scheduler.getSnapshot(2000);
365
+ expect(snapshot.step).toBe(1000);
366
+ expect(snapshot.overallProgress).toBe(1);
367
+ });
368
+ });
369
+
370
+ // ---------------------------------------------------------------------------
371
+ // STATS
372
+ // ---------------------------------------------------------------------------
373
+
374
+ describe('getStats', () => {
375
+ it('returns correct peak and min LR', () => {
376
+ const scheduler = new LRScheduler({
377
+ baseLR: 2e-4,
378
+ totalSteps: 1000,
379
+ warmupRatio: 0.1,
380
+ minLR: 1e-7,
381
+ });
382
+
383
+ const stats = scheduler.getStats();
384
+ expect(stats.peakLR).toBe(2e-4);
385
+ expect(stats.minLR).toBe(1e-7);
386
+ expect(stats.warmupSteps).toBe(100);
387
+ expect(stats.decaySteps).toBe(900);
388
+ expect(stats.totalSteps).toBe(1000);
389
+ });
390
+
391
+ it('computes reasonable average LR', () => {
392
+ const scheduler = new LRScheduler({
393
+ baseLR: 2e-4,
394
+ totalSteps: 1000,
395
+ warmupRatio: 0.1,
396
+ minLR: 0,
397
+ });
398
+
399
+ const stats = scheduler.getStats();
400
+ // Average should be between 0 and baseLR
401
+ expect(stats.avgLR).toBeGreaterThan(0);
402
+ expect(stats.avgLR).toBeLessThan(2e-4);
403
+ });
404
+
405
+ it('handles totalSteps = 0', () => {
406
+ const scheduler = new LRScheduler({
407
+ baseLR: 2e-4,
408
+ totalSteps: 0,
409
+ });
410
+
411
+ const stats = scheduler.getStats();
412
+ expect(stats.totalSteps).toBe(0);
413
+ expect(stats.warmupSteps).toBe(0);
414
+ });
415
+ });
416
+
417
+ // ---------------------------------------------------------------------------
418
+ // SCHEDULE GENERATION
419
+ // ---------------------------------------------------------------------------
420
+
421
+ describe('getSchedule', () => {
422
+ it('generates requested number of points', () => {
423
+ const scheduler = new LRScheduler({
424
+ baseLR: 2e-4,
425
+ totalSteps: 1000,
426
+ });
427
+
428
+ const schedule = scheduler.getSchedule(50);
429
+ expect(schedule).toHaveLength(50);
430
+ });
431
+
432
+ it('starts at step 0 and ends at totalSteps', () => {
433
+ const scheduler = new LRScheduler({
434
+ baseLR: 2e-4,
435
+ totalSteps: 1000,
436
+ });
437
+
438
+ const schedule = scheduler.getSchedule(100);
439
+ expect(schedule[0][0]).toBe(0);
440
+ expect(schedule[schedule.length - 1][0]).toBe(1000);
441
+ });
442
+
443
+ it('each entry is [step, lr] pair', () => {
444
+ const scheduler = new LRScheduler({
445
+ baseLR: 2e-4,
446
+ totalSteps: 1000,
447
+ });
448
+
449
+ const schedule = scheduler.getSchedule(10);
450
+ for (const [step, lr] of schedule) {
451
+ expect(typeof step).toBe('number');
452
+ expect(typeof lr).toBe('number');
453
+ expect(step).toBeGreaterThanOrEqual(0);
454
+ expect(step).toBeLessThanOrEqual(1000);
455
+ expect(lr).toBeGreaterThanOrEqual(0);
456
+ expect(lr).toBeLessThanOrEqual(2e-4 + 1e-10);
457
+ }
458
+ });
459
+
460
+ it('enforces minimum of 2 points', () => {
461
+ const scheduler = new LRScheduler({
462
+ baseLR: 2e-4,
463
+ totalSteps: 1000,
464
+ });
465
+
466
+ const schedule = scheduler.getSchedule(1);
467
+ expect(schedule.length).toBeGreaterThanOrEqual(2);
468
+ });
469
+ });
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // FACTORY FUNCTIONS
473
+ // ---------------------------------------------------------------------------
474
+
475
+ describe('createSFTScheduler', () => {
476
+ it('creates scheduler with SFT defaults', () => {
477
+ const scheduler = createSFTScheduler();
478
+ expect(scheduler.getConfig().baseLR).toBe(2e-4);
479
+ });
480
+
481
+ it('allows overrides on SFT defaults', () => {
482
+ const scheduler = createSFTScheduler({ totalSteps: 5000 });
483
+ expect(scheduler.getConfig().baseLR).toBe(2e-4);
484
+ expect(scheduler.getConfig().totalSteps).toBe(5000);
485
+ });
486
+ });
487
+
488
+ describe('createGRPOScheduler', () => {
489
+ it('creates scheduler with GRPO defaults', () => {
490
+ const scheduler = createGRPOScheduler();
491
+ expect(scheduler.getConfig().baseLR).toBe(1e-6);
492
+ });
493
+
494
+ it('allows overrides on GRPO defaults', () => {
495
+ const scheduler = createGRPOScheduler({ totalSteps: 2000 });
496
+ expect(scheduler.getConfig().baseLR).toBe(1e-6);
497
+ expect(scheduler.getConfig().totalSteps).toBe(2000);
498
+ });
499
+ });
500
+
501
+ // ---------------------------------------------------------------------------
502
+ // DEFAULT CONFIGS
503
+ // ---------------------------------------------------------------------------
504
+
505
+ describe('DEFAULT_LR_SCHEDULER_CONFIG', () => {
506
+ it('has expected values per W.006 and W.009', () => {
507
+ expect(DEFAULT_LR_SCHEDULER_CONFIG.baseLR).toBe(2e-4);
508
+ expect(DEFAULT_LR_SCHEDULER_CONFIG.warmupRatio).toBe(0.1);
509
+ expect(DEFAULT_LR_SCHEDULER_CONFIG.minLR).toBe(0);
510
+ expect(DEFAULT_LR_SCHEDULER_CONFIG.numCycles).toBe(1);
511
+ });
512
+ });
513
+
514
+ describe('GRPO_LR_SCHEDULER_CONFIG', () => {
515
+ it('has lower baseLR for GRPO', () => {
516
+ expect(GRPO_LR_SCHEDULER_CONFIG.baseLR).toBe(1e-6);
517
+ expect(GRPO_LR_SCHEDULER_CONFIG.warmupRatio).toBe(0.1);
518
+ });
519
+ });
520
+
521
+ // ---------------------------------------------------------------------------
522
+ // MATHEMATICAL PROPERTIES
523
+ // ---------------------------------------------------------------------------
524
+
525
+ describe('mathematical properties', () => {
526
+ it('LR never exceeds baseLR', () => {
527
+ const scheduler = new LRScheduler({
528
+ baseLR: 2e-4,
529
+ totalSteps: 1000,
530
+ warmupRatio: 0.1,
531
+ minLR: 0,
532
+ });
533
+
534
+ for (let step = 0; step <= 1000; step += 10) {
535
+ expect(scheduler.getLR(step)).toBeLessThanOrEqual(2e-4 + 1e-12);
536
+ }
537
+ });
538
+
539
+ it('LR never goes below minLR (for single cycle)', () => {
540
+ const scheduler = new LRScheduler({
541
+ baseLR: 2e-4,
542
+ totalSteps: 1000,
543
+ warmupRatio: 0.1,
544
+ minLR: 1e-7,
545
+ numCycles: 1,
546
+ });
547
+
548
+ for (let step = 0; step <= 1000; step += 10) {
549
+ // During warmup, LR can be below minLR (it starts at 0)
550
+ if (step >= scheduler.getWarmupSteps()) {
551
+ expect(scheduler.getLR(step)).toBeGreaterThanOrEqual(1e-7 - 1e-12);
552
+ }
553
+ }
554
+ });
555
+
556
+ it('warmup is exactly linear', () => {
557
+ const scheduler = new LRScheduler({
558
+ baseLR: 2e-4,
559
+ totalSteps: 1000,
560
+ warmupRatio: 0.1,
561
+ });
562
+
563
+ // Check linearity: LR at step k = baseLR * k / warmupSteps
564
+ for (let step = 0; step <= 100; step += 10) {
565
+ const expected = 2e-4 * (step / 100);
566
+ expect(scheduler.getLR(step)).toBeCloseTo(expected, 10);
567
+ }
568
+ });
569
+
570
+ it('cosine decay follows cos((pi * progress) / 2) shape', () => {
571
+ const baseLR = 2e-4;
572
+ const minLR = 0;
573
+ const scheduler = new LRScheduler({
574
+ baseLR,
575
+ totalSteps: 1000,
576
+ warmupRatio: 0.1,
577
+ minLR,
578
+ numCycles: 1,
579
+ });
580
+
581
+ // Sample a few points and verify cosine formula
582
+ const warmupSteps = 100;
583
+ const decaySteps = 900;
584
+
585
+ for (const step of [200, 400, 600, 800, 1000]) {
586
+ const progress = (step - warmupSteps) / decaySteps;
587
+ const expected = minLR + (baseLR - minLR) * ((1 + Math.cos(Math.PI * progress)) / 2);
588
+ expect(scheduler.getLR(step)).toBeCloseTo(expected, 10);
589
+ }
590
+ });
591
+ });
592
+ });