/** * Execution Heartbeat (2.8.0). * * Active lease maintenance for a single execution ownership lifetime. * * The durable execution lease protects *authoritative* mutation, but by itself * it cannot stop a long-running executor (local Qwen, tools, tests) from * continuing to burn resources after its lease expires and another process * takes ownership. This module is the runtime half of that contract: * * - It renews the lease on a safe cadence while the owning execution is * healthy, without ever changing the fencing token. * - It classifies renewal outcomes into SUCCESS, TRANSIENT retryable failure, * or DEFINITE ownership loss. * - It aborts the owning execution through an injected authority-loss * callback when ownership is lost or can no longer be proven before the * lease safety margin. * * The heartbeat is deliberately a *local* runtime object owned by the * coordinator's execution lifetime. It is never a global/background timer and * never a daemon: it starts only after durable RUNNING ownership is confirmed * and stops exactly once on completion, cancellation, launch failure, authority * loss, or an unhandled execution error. */ export interface HeartbeatTimingInput { /** Lease lifetime used by the coordinator/store. Must be a positive safe integer. */ leaseDurationMs: number; /** * Renewal cadence. Defaults to `leaseDurationMs / 3`, i.e. three renewals * per lease lifetime — enough to survive a single missed renewal while * keeping filesystem churn negligible relative to LLM execution. */ heartbeatIntervalMs?: number; /** * Safety window before expiry inside which a renewal can no longer be * trusted. Defaults to `leaseDurationMs / 6`. If the heartbeat reaches this * window without a confirmed renewal it aborts conservatively rather than * continue work on an expiring lease. */ renewalSafetyMarginMs?: number; } export interface ResolvedHeartbeatTiming { leaseDurationMs: number; heartbeatIntervalMs: number; renewalSafetyMarginMs: number; } /** * Validate and normalize heartbeat timing. Invalid configurations must never * silently create unsafe behavior (a heartbeat that can never run, or one that * only fires after the lease is already expiring). */ export declare function resolveHeartbeatTiming(input: HeartbeatTimingInput): ResolvedHeartbeatTiming; export interface HeartbeatTimer { clear(): void; } /** * Injectable timer scheduling (default: `setTimeout`). Recursive scheduling is * used so at most one renewal is ever in flight and ticks never overlap. */ export type HeartbeatScheduler = (fn: () => void, delayMs: number) => HeartbeatTimer; export declare const defaultHeartbeatScheduler: HeartbeatScheduler; export type HeartbeatRenewalFailureKind = "DEFINITE_OWNERSHIP_LOSS" | "TRANSIENT_RENEWAL_FAILURE"; /** * Classify a renewal error. Only structured, authoritative ownership-loss codes * are DEFINITE; everything else (short filesystem contention, temporary lock * timeouts, unexpected I/O) is TRANSIENT and may be retried while lease * validity still remains. */ export declare function classifyHeartbeatRenewalError(error: unknown): HeartbeatRenewalFailureKind; export type HeartbeatAuthorityLossReason = "EXECUTION_AUTHORITY_LOST" | "HEARTBEAT_LEASE_EXPIRING" | "HEARTBEAT_RENEWAL_FAILED"; export interface HeartbeatAuthorityLossInfo { reason: HeartbeatAuthorityLossReason; message: string; atMs: number; detail?: Readonly>; } /** * Internal execution-authority-loss abstraction. Distinct from the existing * store/ownership errors (`STALE_EXECUTION_OWNER`, etc.): those describe a * fenced store mutation, whereas this describes the local runtime decision to * stop because the execution can no longer prove it owns the lease. */ export declare class ExecutionAuthorityLostError extends Error { readonly code: "EXECUTION_AUTHORITY_LOST"; readonly reason: HeartbeatAuthorityLossReason; readonly detail?: Readonly>; constructor(info: HeartbeatAuthorityLossInfo); } export interface HeartbeatTelemetry { heartbeatActive: boolean; heartbeatIntervalMs: number; lastRenewalAt?: number; leaseExpiresAt: number; renewalCount: number; renewalFailureCount: number; lastRenewalError?: string; authorityLost: boolean; authorityLostAt?: number; authorityLostReason?: HeartbeatAuthorityLossReason; } export interface ExecutionHeartbeatDeps { missionId: string; leaseId: string; fencingToken: number; timing: ResolvedHeartbeatTiming; now: () => number; schedule: HeartbeatScheduler; /** * Resolve with the renewed lease expiry. Reject with an * `ExecutionOwnershipError` (or an unexpected runtime error) on failure. */ renew: (now: number) => Promise<{ expiresAtMs: number; }>; /** Called exactly once when ownership is lost or can no longer be proven. */ onAuthorityLost: (info: HeartbeatAuthorityLossInfo) => void; } export declare class ExecutionHeartbeat { private readonly _deps; private _active; private _timer; private _expiresAtMs; private _lastRenewalAt; private _renewalCount; private _renewalFailureCount; private _lastRenewalError; private _authorityLost; private _authorityLostAt; private _authorityLostReason; constructor(deps: ExecutionHeartbeatDeps); get missionId(): string; get leaseId(): string; get fencingToken(): number; /** Begin renewal after the initial ownership has been durably confirmed. */ start(initialExpiresAtMs: number): void; /** Idempotent stop. Never invokes the authority-loss callback. */ stop(): void; telemetry(): HeartbeatTelemetry; private _scheduleNext; private _clearTimer; private _tick; private _triggerAuthorityLost; } //# sourceMappingURL=execution-heartbeat.d.ts.map