UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

172 lines 8.63 kB
import type { Event, SerializedData, World } from '#compiled/@workflow/world/index.js'; import type { CryptoKey } from '../encryption.js'; import { type StepLatencyTracking } from './step-latency.js'; export declare const DEFAULT_STEP_MAX_RETRIES = 3; export interface StepExecutorParams { world: World; workflowRunId: string; /** Deployment that owns the workflow run, for forwarded writable streams. */ workflowDeploymentId?: string; workflowName: string; workflowStartedAt: number; /** Root run id of this run's lineage, carried into the step context. */ rootRunId?: string; stepId: string; stepName: string; encryptionKey?: CryptoKey; /** * The workflow run's specVersion, used to gate payload compression. * Step outputs/errors are only gzip-compressed when the run is marked * as possibly containing compressed payloads (specVersion >= 5). */ runSpecVersion?: number; /** * Lazy step start: the already-dehydrated step input. When provided, the * `step_started` event carries this input so the world creates the step on * the fly (no separate `step_created` round-trip). Set by the owned-inline * path for the step whose `step_created` the suspension handler deferred. * The world's atomic create-claim is the exactly-one-owner gate: losing it * surfaces as `EntityConflictError` → `{ type: 'skipped' }`, so a handler * that did not win the create never runs the body. Omitted on every other * path, where the step already has a `step_created` and `step_started` * carries no payload (the legacy contract). */ lazyStepInput?: SerializedData; /** * Inline step ownership: the queue message ID of the invocation this * executeStep call runs in (from the queue handler's meta). When set, the * `step_started` this call sends is stamped with it — on the lazy paths * (where `lazyStepInput` is present) and on the owned-recovery bare start * (where it is not; the re-stamp keeps a recovered step readable as owned * by this message, since ownership derives from the LATEST start). Wake * replays that observe an actively-owned step suppress the immediate * requeue and enqueue a delayed backstop instead. Omitted on the * background-step path, whose bare start intentionally clears ownership * (the step is queue-owned from that point). */ ownerMessageId?: string; /** * Inline-delta optimization (opt-in). When provided, the cursor of the * event log as observed by the caller *before* this step's events were * written. It is threaded into the step-terminal `events.create` so a * supporting World can return the delta of events written since (see * {@link import('@workflow/world').CreateEventParams.sinceCursor}). * Only set this when `correlationId`-based ownership guarantees this * handler is the sole inline writer for the run on this iteration. */ inlineDeltaSinceCursor?: string; /** * Precondition-guard snapshot (epoch ms of the latest event the caller's * replay loaded) to attach to this step's `step_started` claim. On the lazy * inline path the claim is the step's FIRST durable write (its * `step_created` is deferred), so without this the claim would bypass the * optimistic-concurrency guard entirely: a replay working from a stale view * could claim — and then commit — a step scheduled without observing an * out-of-band event. A guard-enforcing World rejects a stale claim with * `PreconditionFailedError` (412); executeStep does NOT translate that * rejection (re-claiming in place would still commit the stale schedule), * so it propagates for the caller to abandon the batch and force a fresh * replay. Undefined when the guard is disabled or the caller has no * snapshot; Worlds that don't enforce the guard ignore it. */ stateUpdatedAt?: number; /** * Suppress optimistic inline start for this step regardless of * `WORKFLOW_OPTIMISTIC_INLINE_START` / `forceOptimisticStart`: take the * await-then-run path so the body only runs after the `step_started` claim * succeeds. Set by the runtime for guard-enforced batches that are * stale-sensitive (an open hook means an out-of-band event can make the * scheduling view stale): the guard's 412 fence can reject a stale claim's * durable writes, but it cannot un-run a body that optimistic start began * before the claim settled — awaiting the claim extends the fence to user * code. Wins over `forceOptimisticStart` and the env flag. */ suppressOptimisticStart?: boolean; /** * Force optimistic inline start regardless of * `WORKFLOW_OPTIMISTIC_INLINE_START`. Set by turbo mode on the first delivery * of the first invocation, where forcing it is safe: there is no concurrent * peer handler to race the create-claim, so the body runs exactly once. Only * meaningful together with `lazyStepInput` (a brand-new lazy step). */ forceOptimisticStart?: boolean; /** * Turbo mode only: a promise that resolves once the backgrounded * `run_started` has landed. When set, the lazy/optimistic `step_started` is * chained on it so the step is never created before its run exists. The body * still runs immediately against locally-synthesized state — only the network * write waits — so the `run_started` round-trip overlaps the body. `undefined` * outside turbo, where `run_started` was already awaited up front. */ runReadyBarrier?: Promise<unknown>; /** * Latency telemetry (TTFS / STSO): eligibility and anchor timestamps decided * by the orchestrator. When set, this executor computes the final values * against the wall clock taken immediately before user code runs and * attaches them to the step's terminal event. Set only for the first step of * an inline batch, and only on first-attempt executions that qualify — see * runtime/step-latency.ts. */ latencyTracking?: StepLatencyTracking; /** * Authoritative attempt number for this execution, used to bound retries * against `maxRetries` BEFORE the body runs. In order to also catch * step timeouts (which we can't have catch handlers for), we determine * the attempt count based as follows: * - Inline (combined handler): the number of `step_started` events already * in the event log for this step, plus one for the attempt about to run. * - Background (queue-dispatched): the queue delivery count * (`metadata.attempt`), which increments on every redelivery. */ authoritativeAttempt?: number; } /** * Inline-delta returned by a step-terminal write when the caller passed * {@link StepExecutorParams.inlineDeltaSinceCursor} and the World supports * the optimization. `events` are the events written strictly after that * cursor (this step's events plus anything interleaved in-band), `cursor` * is the position past the last one, and `hasMore` signals a further page * (the caller then falls back to a full incremental fetch). Absent when * the World did not return a delta. */ export interface InlineEventDelta { events: Event[]; cursor: string | null; hasMore: boolean; } /** * Result of a step execution attempt. The caller decides what to do * based on the result type (e.g., queue workflow continuation, replay inline, etc.). * * `inlineDelta` is attached to a `completed` result when the caller * requested it via {@link StepExecutorParams.inlineDeltaSinceCursor} and * the World returned one. Step failures are rare and not the inline * optimization's target, so the `failed` path leaves it to the normal * incremental fetch. */ export type StepExecutionResult = { type: 'completed'; hasPendingOps?: boolean; inlineDelta?: InlineEventDelta; } | { type: 'failed'; } | { type: 'retry'; timeoutSeconds: number; } | { type: 'skipped'; } | { type: 'gone'; } | { type: 'throttled'; timeoutSeconds: number; }; /** * Executes a single step: creates step_started event, hydrates input, * runs the step function, creates step_completed/step_failed/step_retrying events. * * Does NOT queue workflow continuation messages — the caller decides what to do next. * Used by both the V1 step handler and the V2 combined handler. */ export declare function executeStep(params: StepExecutorParams): Promise<StepExecutionResult>; //# sourceMappingURL=step-executor.d.ts.map