eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
150 lines • 7.61 kB
TypeScript
import type { HookResumeTiming } from '#compiled/@workflow/world/index.js';
/**
* Hook-triggered time-to-resume (TTR): wall-clock from entry into
* `resumeHook()` (T0) to the first line of the next durable step (T7), plus a
* decomposition into non-overlapping phases that sum exactly to it.
*
* ```text
* T0 resumeHook() entered
* producer_prep : hook lookup, serialization, encryption
* T1 queue publish requested
* queue_delivery : network + VQS delivery (incl. any affinity re-route)
* T2 final consumer's queue handler entered
* resume_setup : affinity check, hook_received re-ensure, replay preload
* T3 replay begins
* replay : VM/session creation and workflow replay
* T4 next durable step encountered
* step_dispatch : suspension handling, inline batch or queue dispatch
* T5 step_started request begins
* step_claim : the claim round trip
* T6 step_started response returned
* step_prepare : key resolution, argument hydration, context setup
* T7 immediately before stepFn.apply()
* ```
*
* The producer's `hook_received` write is awaited inside `producer_prep`
* (the wake is only published after it commits). Older producers may report
* `lazy`, where the consumer materializes the event from `hookInput`, or
* `parallel`, where the write raced the publish. The write has no phase of
* its own; it remains visible as a contextual span (`hook.resume`).
*
* T0/T1 are stamped on the producer's machine and T2..T7 on the consumer's, so
* the measurement is subject to cross-machine clock skew. Rather than clamp
* (which would break the sum-equals-total property this decomposition exists
* for), a non-monotonic boundary set drops the whole sample; see
* {@link computeResumeTtrAttributes}.
*/
/** What caused the resumption being measured. Only hooks are measured today. */
export type ResumeTrigger = 'hook';
/**
* Which `resumeHook()` dispatch path produced this resume.
*
* Current producers always send `sequential` (durable write, then wake).
* Older producers may send `lazy` (the consumer materializes the event from
* `hookInput`) or `parallel` (the write raced the publish).
*/
export type ResumeStrategy = 'lazy' | 'parallel' | 'sequential';
/**
* How the consuming invocation initialized its replay state:
*
* - `hook_preload`: the hoisted `hook_received` write returned a usable
* replay preload, so neither `run_started` nor the initial `events.list` ran.
* - `run_started`: the generic `run_started` setup ran (including the fast
* path's fallback, where the hoisted write succeeded but returned no usable
* preload).
* - `event_load`: neither setup ran because the run arrived already loaded,
* so setup was a plain event load. No current path produces it (the one
* preloaded-run path, the background-step fall-through, consumes the
* tracking on its own step first); it is the honest default rather than a
* live value, and keeps the dimension total if such a path is added.
*/
export type ResumeSetupSource = 'hook_preload' | 'run_started' | 'event_load';
/** Whether the measured step ran in the resuming invocation or a queued one. */
export type ResumeStepExecution = 'inline' | 'dispatched';
/**
* The resume boundaries observed so far, threaded from the queue handler into
* `executeStep`. Producer fields arrive on the queue message
* ({@link HookResumeTiming}); consumer fields are stamped by the invocation
* that replays the resume.
*
* The runtime holds at most one of these per invocation and CONSUMES it when
* it hands it to the execution that will attempt the next durable step, so a
* later step in the same invocation (or a retry of the same step) never
* re-reports the same resumption. Within one inline batch the object is
* shared by every step and the {@link ResumeTtrTracking.reported} latch picks
* the single reporter.
*/
export interface ResumeTtrTracking {
trigger: ResumeTrigger;
/** Absent only if an older producer omitted it from the queue message. */
strategy?: ResumeStrategy;
/** T0: entry into `resumeHook()`. */
resumeRequestedAtMs: number;
/** T1: immediately before the queue publish was requested. */
queuePublishRequestedAtMs: number;
/**
* T2: entry into the FINAL consumer's queue handler. A delivery that
* re-routes for deployment affinity never stamps this, so the re-routed hop
* stays inside `queue_delivery` where it belongs.
*/
consumerStartedAtMs: number;
/** T3: this invocation's first replay pass. */
replayStartedAtMs?: number;
/** T4: replay first encountered a durable step after the resume. */
nextStepEncounteredAtMs?: number;
setupSource?: ResumeSetupSource;
stepExecution: ResumeStepExecution;
/**
* One-shot latch, set by the step executor once this resumption has been
* reported. Every step of an inline batch is handed the SAME tracking
* object, so the first one to reach user code takes the measurement and the
* rest see this and skip: one resumption, one sample, without pinning the
* sample to a step that may lose its create-claim and never run.
*
* Deliberately not part of {@link HookResumeTiming}: it is invocation-local
* state, and a queue message carries the boundaries, not the claim.
*/
reported?: boolean;
}
/**
* Rebuild tracking from a queue message's timing object. Returns undefined for
* a message that carries none (an older producer, or any non-hook delivery);
* the caller then reports no TTR.
*/
export declare function resumeTrackingFromMessage(timing: HookResumeTiming | undefined, stepExecution: ResumeStepExecution): ResumeTtrTracking | undefined;
/**
* Serialize tracking back onto a queue message, for the case where the
* resuming invocation dispatches the next durable step to another invocation
* instead of running it inline. Returns undefined when the tracking is not
* complete enough to be worth forwarding.
*/
export declare function resumeTimingForMessage(tracking: ResumeTtrTracking | undefined): HookResumeTiming | undefined;
/**
* Compute the TTR span attributes for a step that is the first durable step
* following a hook resume. Returns undefined (emitting nothing at all) when
* any of these hold:
*
* - the invocation carries no resume tracking (not a hook resume, or an older
* queue message with no timing);
* - this is a retry (`attempt !== 1`), which measures a re-execution rather
* than the resumption;
* - a required boundary is missing, non-finite, or out of order.
*
* `step_claim_ms` is the one phase that may be individually omitted. Under
* optimistic inline start the `step_started` claim is deliberately NOT awaited
* before the body runs, so its completion instant does not exist yet at T7;
* rather than invent one, the claim phase is dropped and `step_prepare_ms`
* spans T5→T7. The sum-equals-total property holds either way.
*/
export declare function computeResumeTtrAttributes(params: {
tracking: ResumeTtrTracking | undefined;
/** The attempt number of the execution about to run. */
attempt: number;
/** T5: `Date.now()` immediately before the `step_started` request. */
stepClaimStartedAtMs: number | undefined;
/** T6: `Date.now()` once the `step_started` response returned. */
stepClaimCompletedAtMs: number | undefined;
/** T7: `Date.now()` immediately before `stepFn.apply()`. */
stepCodeStartedAtMs: number;
}): Record<string, string | number> | undefined;
//# sourceMappingURL=resume-latency.d.ts.map