deepline 0.3.229 → 0.3.230

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 (30) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  3. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +19 -0
  4. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +47 -32
  5. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +3 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +38 -2
  7. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +8 -3
  8. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +9 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +39 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +293 -4
  11. package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +29 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +50 -10
  13. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +1 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +4 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +4 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +187 -14
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-work-counters.ts +52 -2
  18. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +5 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +30 -2
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +118 -6
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +135 -21
  22. package/dist/cli/index.js +102 -2
  23. package/dist/cli/index.mjs +102 -2
  24. package/dist/index.js +93 -2
  25. package/dist/index.mjs +93 -2
  26. package/dist/release.d.mts +1 -1
  27. package/dist/release.d.ts +1 -1
  28. package/dist/release.js +1 -1
  29. package/dist/release.mjs +1 -1
  30. package/package.json +1 -1
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.229',
203
+ version: '0.3.230',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "id": "github.agentic-deepline-install",
4
+ "name": "Agentic Deepline Installation",
5
+ "scheduler": "github_actions",
6
+ "cadence": "15 7 * * *",
7
+ "timezone": "America/New_York",
8
+ "source": ".github/workflows/agentic-install.yml",
9
+ "locator": "schedule:",
10
+ "owner": "product",
11
+ "criticality": "critical",
12
+ "purpose": "Run clean Sonnet/Claude Code and Luna/Codex installs from the production homepage prompt through browser claim completion and the agent’s complete starter handoff.",
13
+ "successDefinition": "Both latest coding harnesses install Deepline without installation warnings or errors, finish browser authorization and setup, present all four starter choices as a card or text, and verify test key revocation; after seven valid daily runs each finishes within 240 seconds.",
14
+ "failureDefinition": "Either agent, browser claim completion, read-only setup verification, complete starter handoff, verified key cleanup, or the applicable timing gate fails.",
15
+ "startGraceMs": 900000,
16
+ "runTimeoutMs": 2700000,
17
+ "failureAlert": "github_workflow_run",
18
+ "domainReportRequired": true
19
+ },
2
20
  {
3
21
  "id": "github.billing-identity-sync",
4
22
  "name": "Billing identity lookup sync",
@@ -114,6 +114,16 @@ export type RuntimeStatusUpdate = {
114
114
  * attempt starts at offset zero without colliding with earlier attempts.
115
115
  */
116
116
  liveLogProducerAttempt?: number;
117
+ /** Sampled attempt-local calls; terminal samples supersede missed live updates. */
118
+ workCounters?: {
119
+ attempt: number;
120
+ invocationSeq: number;
121
+ segmentId: string;
122
+ logicalCalls: number;
123
+ providerRequests: number;
124
+ provider429s: number;
125
+ final: boolean;
126
+ };
117
127
  datasetLifecycleEvents?: Array<{
118
128
  datasetId: string;
119
129
  path: string;
@@ -2205,6 +2215,15 @@ async function updateRunStatusViaAppRuntimeUnlocked(
2205
2215
  now: update.lastCheckpointAt ?? Date.now(),
2206
2216
  source: 'worker',
2207
2217
  });
2218
+ if (update.workCounters) {
2219
+ events.push({
2220
+ type: 'work.counters',
2221
+ runId: update.playId,
2222
+ source: 'worker',
2223
+ occurredAt: update.lastCheckpointAt ?? Date.now(),
2224
+ ...update.workCounters,
2225
+ });
2226
+ }
2208
2227
  for (const event of update.datasetLifecycleEvents ?? []) {
2209
2228
  const current = previousSnapshot.datasetsById[event.datasetId];
2210
2229
  const changesSnapshot =
@@ -202,6 +202,7 @@ import {
202
202
  TOOL_EXECUTE_RUNNER_ACCEPTS_VALUE,
203
203
  TOOL_EXECUTE_PROVIDER_RATE_SCOPE_HEADER,
204
204
  TOOL_EXECUTE_PROVIDER_RATE_RETRY_SNAPSHOT_HEADER,
205
+ TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
205
206
  classifyToolExecuteHttpFailure,
206
207
  createToolExecuteHttpFailureAttemptTracker,
207
208
  TOOL_EXECUTE_BARE_RATE_LIMIT_MAX_ATTEMPTS,
@@ -2634,9 +2635,7 @@ const WAITING_ROW = Symbol('deepline.waiting_row');
2634
2635
  const FAILED_ROW = Symbol('deepline.failed_row');
2635
2636
  const COMPLETED_ROW = Symbol('deepline.completed_row');
2636
2637
  type MapRowExecutionResult =
2637
- | typeof COMPLETED_ROW
2638
- | typeof WAITING_ROW
2639
- | typeof FAILED_ROW;
2638
+ typeof COMPLETED_ROW | typeof WAITING_ROW | typeof FAILED_ROW;
2640
2639
 
2641
2640
  function normalizeFetchHeaders(
2642
2641
  headers: RequestInit['headers'],
@@ -4434,8 +4433,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4434
4433
  artifactTableNamespace: string;
4435
4434
  mapNodeId?: string | null;
4436
4435
  explicitKey?:
4437
- | ((row: Record<string, unknown>, index: number) => string)
4438
- | null;
4436
+ ((row: Record<string, unknown>, index: number) => string) | null;
4439
4437
  }): MapExecutionScope {
4440
4438
  const mapInvocationId = `${input.logicalNamespace}:${this.mapInvocationIndex}`;
4441
4439
  this.mapInvocationIndex += 1;
@@ -4685,12 +4683,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
4685
4683
  // run actually owns. The map/cohort publisher keeps the claim-free
4686
4684
  // batched write, where the pointer genuinely shares the canonical
4687
4685
  // result's single bounded request.
4688
- if (
4689
- !(
4690
- this.#options.claimRuntimeStepReceipt ||
4691
- this.#options.claimRuntimeStepReceipts
4692
- )
4693
- ) {
4686
+ if (!(
4687
+ this.#options.claimRuntimeStepReceipt ||
4688
+ this.#options.claimRuntimeStepReceipts
4689
+ )) {
4694
4690
  return;
4695
4691
  }
4696
4692
  const existing = await this.getRuntimeStepReceipts(
@@ -8474,8 +8470,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8474
8470
  _key: string,
8475
8471
  _items: PlayDatasetInput<T>,
8476
8472
  _input?:
8477
- | MapFieldDefinition<T, Record<string, unknown>>
8478
- | RuntimeStepProgram,
8473
+ MapFieldDefinition<T, Record<string, unknown>> | RuntimeStepProgram,
8479
8474
  _options?: RuntimeDatasetOptions<T>,
8480
8475
  ): never {
8481
8476
  void _key;
@@ -8599,8 +8594,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8599
8594
 
8600
8595
  const userKeyOption = options?.key;
8601
8596
  let explicitKeyResolver:
8602
- | ((row: Record<string, unknown>, index: number) => string)
8603
- | null = null;
8597
+ ((row: Record<string, unknown>, index: number) => string) | null = null;
8604
8598
  if (userKeyOption !== undefined) {
8605
8599
  explicitKeyResolver = (row, index) => {
8606
8600
  const inputRow = stripFieldOutputs(row);
@@ -13007,6 +13001,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13007
13001
  const callIndex = this.toolCallIndexByKey.get(callIndexKey) ?? 0;
13008
13002
  this.toolCallIndexByKey.set(callIndexKey, callIndex + 1);
13009
13003
  const logicalCallId = stableDigest(`${callIndexKey}:${callIndex}`);
13004
+ this.resourceGovernor.recordLogicalToolCall();
13010
13005
  let executionAuthScopeDigest =
13011
13006
  (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
13012
13007
  const eventWaitHandler =
@@ -15496,6 +15491,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15496
15491
  // Terminal logs are intentionally bounded; keep the small aggregate in
15497
15492
  // stats so every retained terminal snapshot can explain queueing.
15498
15493
  runtimeResources: this.getRuntimeResourceSnapshot(),
15494
+ runtimeWorkCounters: this.getRuntimeWorkCounters(),
15499
15495
  };
15500
15496
  }
15501
15497
 
@@ -15504,6 +15500,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
15504
15500
  return this.resourceGovernor.snapshot();
15505
15501
  }
15506
15502
 
15503
+ /** Attempt-local counts without serializing the diagnostic resource map. */
15504
+ getRuntimeWorkCounters(): {
15505
+ logicalCalls: number;
15506
+ providerRequests: number;
15507
+ provider429s: number;
15508
+ } {
15509
+ return this.resourceGovernor.workCounters();
15510
+ }
15511
+
15507
15512
  /**
15508
15513
  * Record what one receipt gateway response said about itself.
15509
15514
  *
@@ -16680,8 +16685,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
16680
16685
  },
16681
16686
  })),
16682
16687
  buildPhysicalCall: async ({ members }) => {
16683
- const owned = members.map(
16684
- (member) => batchRequestByCallId.get(member.memberId)!,
16688
+ const owned = members.map((member) =>
16689
+ batchRequestByCallId.get(member.memberId)!,
16685
16690
  );
16686
16691
  const compiled = compileOwnedPhysicalBatch(owned);
16687
16692
  const providerActionVersion =
@@ -19007,9 +19012,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19007
19012
  // running for.
19008
19013
  const activeRowScope = rowContext.getStore();
19009
19014
  const memoryLedgerRowScopes: readonly (
19010
- | MemoryLedgerRowScope
19011
- | null
19012
- | undefined
19015
+ MemoryLedgerRowScope | null | undefined
19013
19016
  )[] = options?.memoryLedgerRowScopes ?? [
19014
19017
  { rowKey: activeRowScope?.rowKey, fieldName: activeRowScope?.fieldName },
19015
19018
  ];
@@ -19852,6 +19855,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19852
19855
  logProvider429RetryPhase('http_dispatch_started');
19853
19856
  activeProviderRequest =
19854
19857
  this.beginProviderRequest(provider);
19858
+ // Count at dispatch, including ambiguous transport
19859
+ // failures that never produce a residence sample.
19860
+ this.resourceGovernor.recordProviderDispatch();
19855
19861
  const dispatchedResponse = await fetch(url, {
19856
19862
  method: 'POST',
19857
19863
  signal: abortController?.signal,
@@ -19907,21 +19913,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19907
19913
  // it has been read and verified below.
19908
19914
  response = dispatchedResponse;
19909
19915
  if (dispatchedResponse.status === 429) {
19910
- provider429RetryDiagnosticStartedAt =
19911
- performance.now();
19912
- provider429RetryDiagnosticNextInvocationAttempt =
19913
- invocationAttempt + 1;
19914
- logProvider429RetryPhase(
19915
- 'http_429_response_received',
19916
- );
19917
- providerRequestsInFlightAt429 =
19918
- activeProviderRequest?.inFlightCount() ?? null;
19919
19916
  const observedAt = Date.now();
19920
19917
  const retryAfter =
19921
19918
  dispatchedResponse.headers.get('retry-after');
19922
- // Without a provider deadline the classifier owns
19923
- // the conservative bare-429 cooldown. Do not replace
19924
- // it with the generic one-second HTTP retry fallback.
19919
+ // Preserve absolute provider deadlines even when the
19920
+ // response did not carry the gateway's counter
19921
+ // attestation. Classification below still decides
19922
+ // whether this 429 may update shared provider pacing.
19925
19923
  providerRetryAtMs = retryAfter?.trim()
19926
19924
  ? parseToolExecuteRetryAtMs(
19927
19925
  retryAfter,
@@ -19929,6 +19927,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19929
19927
  )
19930
19928
  : undefined;
19931
19929
  }
19930
+ if (
19931
+ dispatchedResponse.status === 429 &&
19932
+ dispatchedResponse.headers.get(
19933
+ TOOL_EXECUTE_PROVIDER_THROTTLE_HEADER,
19934
+ ) === '1'
19935
+ ) {
19936
+ this.resourceGovernor.recordProvider429();
19937
+ provider429RetryDiagnosticStartedAt =
19938
+ performance.now();
19939
+ provider429RetryDiagnosticNextInvocationAttempt =
19940
+ invocationAttempt + 1;
19941
+ logProvider429RetryPhase(
19942
+ 'http_429_response_received',
19943
+ );
19944
+ providerRequestsInFlightAt429 =
19945
+ activeProviderRequest?.inFlightCount() ?? null;
19946
+ }
19932
19947
  const dispatchedFetchToHeadersMs =
19933
19948
  Date.now() - fetchedAt;
19934
19949
  // The response writer owns these named wall times.
@@ -1171,6 +1171,9 @@ export interface ContextOptions {
1171
1171
  }
1172
1172
 
1173
1173
  export interface PlayCheckpoint {
1174
+ /** Last completed runner invocation within the current scheduler attempt. */
1175
+ workCounterInvocationAttempt?: number;
1176
+ workCounterInvocationSeq?: number;
1174
1177
  /**
1175
1178
  * Clock captured when this logical run first creates its context. All
1176
1179
  * staleAfterSeconds receipt buckets use this value across durable resumes.
@@ -566,6 +566,7 @@ export class FlyMachinesClient {
566
566
  method: string,
567
567
  path: string,
568
568
  body?: unknown,
569
+ leaseNonce?: string,
569
570
  ): Promise<unknown> {
570
571
  const retryDelaysMs =
571
572
  this.rateLimitOptions.retryDelaysMs ??
@@ -585,6 +586,7 @@ export class FlyMachinesClient {
585
586
  headers: {
586
587
  authorization: `Bearer ${this.token}`,
587
588
  'content-type': 'application/json',
589
+ ...(leaseNonce ? { 'fly-machine-lease-nonce': leaseNonce } : {}),
588
590
  },
589
591
  body: body === undefined ? undefined : JSON.stringify(body),
590
592
  signal,
@@ -689,10 +691,43 @@ export class FlyMachinesClient {
689
691
  );
690
692
  }
691
693
 
692
- async deleteMachine(id: string, force: boolean): Promise<void> {
694
+ /** Hold exclusive provider ownership while rechecking and reclaiming a
695
+ * stopped Machine. A concurrent rollback cannot update its hold or restart
696
+ * it until this lease is released or expires. */
697
+ async acquireMachineLease(
698
+ id: string,
699
+ options: { ttl: number; description: string },
700
+ ): Promise<string> {
701
+ const result = await this.request('POST', `/machines/${id}/lease`, options);
702
+ const nonce =
703
+ result &&
704
+ typeof result === 'object' &&
705
+ 'data' in result &&
706
+ result.data &&
707
+ typeof result.data === 'object' &&
708
+ 'nonce' in result.data
709
+ ? result.data.nonce
710
+ : undefined;
711
+ if (typeof nonce !== 'string' || nonce.trim() === '') {
712
+ throw new Error('Fly Machines API did not return a lease nonce.');
713
+ }
714
+ return nonce;
715
+ }
716
+
717
+ async releaseMachineLease(id: string, nonce: string): Promise<void> {
718
+ await this.request('DELETE', `/machines/${id}/lease`, undefined, nonce);
719
+ }
720
+
721
+ async deleteMachine(
722
+ id: string,
723
+ force: boolean,
724
+ leaseNonce?: string,
725
+ ): Promise<void> {
693
726
  await this.request(
694
727
  'DELETE',
695
728
  `/machines/${id}?force=${force ? 'true' : 'false'}`,
729
+ undefined,
730
+ leaseNonce,
696
731
  );
697
732
  }
698
733
 
@@ -718,8 +753,9 @@ export class FlyMachinesClient {
718
753
  async stopMachine(
719
754
  id: string,
720
755
  options?: { signal?: string; timeout?: string },
756
+ leaseNonce?: string,
721
757
  ): Promise<void> {
722
- await this.request('POST', `/machines/${id}/stop`, options);
758
+ await this.request('POST', `/machines/${id}/stop`, options, leaseNonce);
723
759
  }
724
760
 
725
761
  async machinesInLane(releaseId: string): Promise<FlyMachine[]> {
@@ -12,6 +12,10 @@ import type { RunLedgerProjectionPorts } from '../../play-runs/projection/ports'
12
12
 
13
13
  export type GatewayRunLedgerProjectionAdapterInput = {
14
14
  runId: string;
15
+ /**
16
+ * Launch-pinned runtime context. Gateway authority refreshes its credentials
17
+ * against the active outbox lease while retaining supported transport hooks.
18
+ */
15
19
  runtimeApi: WorkerRuntimeApiContext;
16
20
  withClient: <T>(
17
21
  operation: (client: PostgresSchedulerQueryClient) => Promise<T>,
@@ -20,9 +24,10 @@ export type GatewayRunLedgerProjectionAdapterInput = {
20
24
  };
21
25
 
22
26
  /**
23
- * Gateway Adapter for the durable Run Ledger outbox. It retains the worker's
24
- * no-signing-key rule and turns both normal and targeted delivery into the
25
- * one `RunLedgerProjection.deliver` port.
27
+ * Gateway adapter for the durable Run Ledger outbox. It retains the worker's
28
+ * no-signing-key rule, refreshes credentials for each claimed batch, and turns
29
+ * normal and targeted delivery into the one `RunLedgerProjection.deliver`
30
+ * port.
26
31
  */
27
32
  export function createGatewayRunLedgerProjectionAdapter(
28
33
  input: GatewayRunLedgerProjectionAdapterInput,
@@ -317,6 +317,15 @@ export type PlayRunnerLogEvent = {
317
317
 
318
318
  export type PlayRunnerEvent =
319
319
  | PlayRunnerLogEvent
320
+ | {
321
+ type: 'work_counters';
322
+ attempt: number;
323
+ invocationSeq: number;
324
+ segmentId: string;
325
+ logicalCalls: number;
326
+ providerRequests: number;
327
+ provider429s: number;
328
+ }
320
329
  | {
321
330
  type: 'checkpoint';
322
331
  checkpoint: PlayCheckpoint;
@@ -734,6 +734,12 @@ export interface RuntimeResourceGovernor {
734
734
  inFlightAtDispatch: number;
735
735
  release(): void;
736
736
  };
737
+ /** Count one actual tool HTTP dispatch before transport can fail ambiguously. */
738
+ recordProviderDispatch(): void;
739
+ /** Count a provider HTTP 429 as soon as response headers arrive. */
740
+ recordProvider429(): void;
741
+ /** Count one admitted ctx.tools.execute invocation, including a later failure. */
742
+ recordLogicalToolCall(): void;
737
743
  /**
738
744
  * Probe app transport capacity at the physical provider-dispatch edge. A
739
745
  * caller that misses it must cancel and re-admit its provider ticket.
@@ -757,6 +763,12 @@ export interface RuntimeResourceGovernor {
757
763
  }): Promise<void>;
758
764
  observe(input: RuntimeResourceObservation): void;
759
765
  snapshot(): RuntimeResourceSnapshot;
766
+ /** O(1) counters for sampled live status; no provider-map scan. */
767
+ workCounters(): {
768
+ logicalCalls: number;
769
+ providerRequests: number;
770
+ provider429s: number;
771
+ };
760
772
  /** Stop the event-loop lag sampler. The timer is unref'd either way. */
761
773
  stopSampling(): void;
762
774
  /** Smoothed event-loop lag from the sampler, or null before its first tick. */
@@ -1047,6 +1059,13 @@ export function createRuntimeResourceGovernor(input: {
1047
1059
  const toolCounters = {
1048
1060
  acquired: 0,
1049
1061
  };
1062
+ // Dispatch is counted at the fetch edge. A failed transport is still a
1063
+ // physical request attempt; response-residence observations omit it.
1064
+ const workCounters = {
1065
+ logicalCalls: 0,
1066
+ providerRequests: 0,
1067
+ provider429s: 0,
1068
+ };
1050
1069
  const sheetFlushCounters = {
1051
1070
  acquired: 0,
1052
1071
  bytes: 0,
@@ -1642,6 +1661,18 @@ export function createRuntimeResourceGovernor(input: {
1642
1661
  };
1643
1662
  },
1644
1663
 
1664
+ recordProviderDispatch() {
1665
+ workCounters.providerRequests += 1;
1666
+ },
1667
+
1668
+ recordProvider429() {
1669
+ workCounters.provider429s += 1;
1670
+ },
1671
+
1672
+ recordLogicalToolCall() {
1673
+ workCounters.logicalCalls += 1;
1674
+ },
1675
+
1645
1676
  tryAcquireIntegrationRequestSlot() {
1646
1677
  return input.executionGovernor.tryAcquireIntegrationRequestSlot();
1647
1678
  },
@@ -1672,6 +1703,14 @@ export function createRuntimeResourceGovernor(input: {
1672
1703
 
1673
1704
  observe,
1674
1705
 
1706
+ workCounters() {
1707
+ return {
1708
+ logicalCalls: workCounters.logicalCalls,
1709
+ providerRequests: workCounters.providerRequests,
1710
+ provider429s: workCounters.provider429s,
1711
+ };
1712
+ },
1713
+
1675
1714
  stopSampling: stopEventLoopLagSampler,
1676
1715
  eventLoopLagEwmaMs: () => phases.eventLoopLag.snapshot().ewmaMs,
1677
1716