import type { PhaseRole } from "../../subagent/caller-context.js"; import type { SubagentResult } from "../../forge-subagent.js"; export type StepId = string; /** * Runtime context threaded through a single pipeline run. Opaque to the machine * primitives — the orchestrator defines the concrete shape and reads/writes its * own mutable pipeline state (configCache, verify flags, …) through it. * * NOTE: within a wave, steps run CONCURRENTLY and share this one ctx object, so * the machine never stashes per-step run results on it (that would race). A * subagent step's `requiredOutput` receives its own dispatch result as the * second argument instead. Only single-step waves (deterministic gates) mutate * ctx fields, so those reads/writes are race-free. */ export interface StepRuntimeCtx { [key: string]: unknown; } export interface StepCheckResult { ok: boolean; reason?: string; } /** * Deterministic precondition / postcondition — never fabricates LLM facts. * `requiredOutput` receives the step's own subagent dispatch result (if any) as * `lastResult`; `precondition` is always called with `lastResult` undefined. */ export type StepCheck = (ctx: StepRuntimeCtx, lastResult?: SubagentResult) => Promise; /** A deterministic step: local tool/fs work, spawns NO subagent. */ export interface DeterministicRun { kind: "deterministic"; thunk: (ctx: StepRuntimeCtx) => Promise; } /** A scoped single-responsibility subagent descriptor. */ export interface SubagentRun { kind: "subagent"; /** Which bundled phase prompt to read (1 = collect, 2 = discover). */ promptPhase: 1 | 2; /** Dispatch label / OrchestratorTree node id fragment. */ subLabel: string; /** CallerContextStore role the dispatch runs under. */ subRole: PhaseRole; /** ROLE_TIER key for model resolution. */ modelRole: string; /** Persona noun (loaded from the bundle base-pack). */ persona: string; /** TypeBox schema passed to runForgeSubagent (optional). */ schema?: object; /** Coarse phase group used for IL10 phase-event naming. */ phaseGroup: string; /** Inject the per-step `` block onto the base prompt. */ buildPrompt: (basePrompt: string, ctx: StepRuntimeCtx) => string; } export type StepRun = DeterministicRun | SubagentRun; export interface RetryPolicy { /** 0 = hard-halt on first requiredOutput failure; 1 = one rerun then halt. */ maxReruns: number; } export interface Step { id: StepId; /** Ordering edges — the only serialization constraint (topo layering). */ dependsOn: StepId[]; /** Deterministic gate checked BEFORE run/dispatch. */ precondition?: StepCheck; run: StepRun; /** Deterministic postcondition checked AFTER each run. */ requiredOutput?: StepCheck; retryPolicy: RetryPolicy; } export interface StepOutcome { ok: boolean; reason?: string; /** True when the step actually dispatched a subagent (for IL10 emission). */ dispatched: boolean; /** Number of run attempts (initial + reruns). */ attempts: number; /** The subagent result for a dispatched step (undefined for deterministic). */ result?: SubagentResult; /** * Wall-clock bracket for THIS step (epoch ms). Captured per step so IL10 * emission attributes each subagent's own duration and builds a per-step * unique event timestamp — NOT the shared wave bracket (which over-attributes * duration and collides eventIds across a fan-out wave). */ startMs: number; endMs: number; } /** Dispatch a subagent step and return its result. Injected so tests can spy. */ export type SubagentDispatcher = (run: SubagentRun, ctx: StepRuntimeCtx) => Promise; export interface RunStepDeps { ctx: StepRuntimeCtx; dispatchSubagent: SubagentDispatcher; } /** * Drive one step: check precondition (halt pre-dispatch on failure) → run * (deterministic thunk OR subagent) → check requiredOutput → on failure rerun * up to `retryPolicy.maxReruns`, else halt. * * A precondition failure returns `{ ok:false, dispatched:false }` with ZERO * dispatches — the gate is always checked before any subagent is spawned. */ export declare function runStep(step: Step, deps: RunStepDeps): Promise; /** * Dispatch every step in a wave concurrently via `Promise.all`. Steps sharing a * wave are independent (no unresolved dependency), so they overlap. Returns the * outcomes in the wave's step order. Concurrency is bounded by wave width * (≤ 10 for the kb-doc wave), so no explicit limiter is needed this slice. */ export declare function runWave(steps: Step[], run: (step: Step) => Promise): Promise; /** * Group steps into topo-sorted waves (Kahn layering): every step whose * dependencies are all satisfied by earlier waves lands in the next wave. * Declaration order is preserved within a wave. Throws on a dependency cycle. * Edges to unknown step ids are ignored (treated as already-satisfied). */ export declare function topoSortWaves(steps: Step[]): Step[][];