import type { ExecutorResult, Loop, LoopRun } from "../types.js"; import { type CircuitBreakerThreshold } from "./advancement.js"; import type { Store } from "./store.js"; export { CIRCUIT_BREAKER_REASON_PREFIX, DEFAULT_CIRCUIT_BREAKER_THRESHOLD, MAX_RETRY_DELAY_MS, retryBackoffDelayMs, } from "./advancement.js"; export interface SchedulerDeps { store: Store; runnerId: string; now?: () => Date; beforeRun?: (loop: Loop, scheduledFor: string) => void; beforeFinalize?: (loop: Loop, run: LoopRun) => void; daemonLeaseId?: string; execute?: (loop: Loop, run: LoopRun) => Promise; onError?: (loop: Loop, error: unknown) => void; onRun?: (run: LoopRun) => void; /** randomness source for retry jitter; injectable for deterministic tests */ random?: () => number; /** consecutive-final-failure count that trips the circuit breaker; number, per-loop resolver, or <= 0 to disable */ circuitBreakerThreshold?: number | ((loop: Loop) => number | undefined); } export interface TickResult { claimed: LoopRun[]; completed: LoopRun[]; skipped: LoopRun[]; recovered: LoopRun[]; expired: Loop[]; } export interface ClaimedLoopRun { loop: Loop; run: LoopRun; claimToken: string; } export interface ClaimDueRunsResult extends TickResult { claims: ClaimedLoopRun[]; } /** * Scheduler concurrency lanes. Command-target loops are typically fast * (monitors, digests, syncs); agent/workflow-target loops are long-running * headless workers (minutes to over an hour). They draw from separate claim * budgets so a saturated agent lane cannot starve fast command loops (and vice * versa) — the single shared pool let long workers monopolize every slot. */ export type SchedulerLane = "command" | "agent"; /** The concurrency lane a loop's target belongs to. */ export declare function loopLane(loop: Loop): SchedulerLane; /** Remaining claim budget per lane for a single `claimDueRuns` pass. */ export type LaneLimits = Partial>; export declare function manualRunScheduledFor(loop: Loop, now?: Date): string; export declare function shouldAdvanceManualRun(loop: Loop, scheduledFor: string, now?: Date): boolean; export type ManualRunSource = "ad_hoc" | "due_slot" | "retry_slot"; export declare function manualRunSource(loop: Loop, scheduledFor: string, now?: Date): ManualRunSource; /** * Inline (non-daemon) runners claim runs as `:`: `manual:` * (CLI run-now), `manual-tick:` (CLI tick), `sdk:` (LoopsClient * default), and caller-supplied SDK runner ids following the same convention. * The daemon claims as `${hostname}:${pid}:${leaseId}` (three segments) and * MCP run-now uses schedule mode, so neither ever matches. Centralized here so * the daemon's "spare runs owned by a live inline runner" check cannot drift * from the surfaces that share runLoopNow/executeClaimedRun semantics. */ export declare const INLINE_RUNNER_ID_PATTERN: RegExp; /** Owner pid of an inline runner claim, or undefined for daemon/unknown claims. */ export declare function inlineRunnerOwnerPid(claimedBy: string | undefined): number | undefined; export type RunLoopNowMode = "inline" | "schedule"; export interface RunLoopNowDeps { store: Store; /** loop id or exact loop name */ idOrName: string; runnerId: string; /** * "inline" claims and executes the run in this process (CLI/SDK semantics); * "schedule" only marks the loop due now for daemon pickup (MCP semantics). */ mode?: RunLoopNowMode; now?: () => Date; execute?: (loop: Loop, run: LoopRun) => Promise; } export interface RunLoopNowScheduled { mode: "schedule"; loop: Loop; scheduledFor: string; } export interface RunLoopNowExecuted { mode: "inline"; loop: Loop; run: LoopRun; source: ManualRunSource; advancedLoop: boolean; } /** * An overlap:"skip" run-now that could not claim its slot because the * previous run is still executing. The run field carries the bookkeeping * "skipped" row recorded instead of an executed run; the CLI/SDK treat it * like any other skipped run (exit 0, status "skipped"). */ export interface RunLoopNowSkipped { mode: "inline"; loop: Loop; run: LoopRun; source: ManualRunSource; advancedLoop: boolean; /** Distinguishes a recorded skip from an executed inline run. */ skipped: true; } export type RunLoopNowResult = RunLoopNowScheduled | RunLoopNowExecuted | RunLoopNowSkipped; export declare function runLoopNow(deps: RunLoopNowDeps & { mode: "schedule"; }): Promise; export declare function runLoopNow(deps: RunLoopNowDeps & { mode?: "inline"; }): Promise; export declare const MAX_SKIPS_PER_LOOP_PER_TICK = 10; export interface AdvanceLoopOptions { daemonLeaseId?: string; random?: () => number; circuitBreakerThreshold?: CircuitBreakerThreshold; onRun?: (run: LoopRun) => void; } /** * Count consecutive final failures (failed/timed_out/abandoned) in recent run * history. Skipped bookkeeping runs and in-flight runs are neutral; a success * resets the streak. Failed runs with attempts remaining (attempt < * maxAttempts) are pending retries by the scheduler's own semantics, so they * are neutral too — only exhausted slots count as final failures. A previous * circuit-breaker marker acts as a watermark (compared via scheduledFor, which * shares the scheduler clock with run slots, unlike the marker's wall-clock * createdAt): only failures after it count, so a manual resume requires a * fresh streak before the breaker can trip again. */ export declare function consecutiveFailureCount(store: Store, loopId: string, maxAttempts?: number, scanLimit?: number): number; export declare function advanceLoop(store: Store, loop: Loop, run: LoopRun, finishedAt: Date, succeeded: boolean, opts?: AdvanceLoopOptions): void; export declare function executeClaimedRun(deps: { store: Store; runnerId: string; claimToken: string; loop: Loop; run: LoopRun; now?: () => Date; beforeFinalize?: (loop: Loop, run: LoopRun) => void; daemonLeaseId?: string; execute?: (loop: Loop, run: LoopRun) => Promise; finalizeResult?: (result: ExecutorResult, loop: Loop, run: LoopRun) => Omit & { status: LoopRun["status"]; }; onError?: (loop: Loop, error: unknown) => void; }): Promise; export declare function claimDueRuns(deps: SchedulerDeps & { maxClaims?: number; laneLimits?: LaneLimits; }): ClaimDueRunsResult; export declare function tick(deps: SchedulerDeps): Promise;