/** * Graph Execution Engine v2 — Engine State Factory * * Version: 2.0 * Date: 2026-07-24 * * Creates and mutates the top-level {@link EngineState} container for a graph * execution instance. This module owns: * * - The engine lifecycle phase machine (`idle → executing → complete`). * - Frontier (ready-for-dispatch set) management. * - Node registration: `Map`. * - The `advancingLock` re-entrancy guard and `pendingCompletions` queue * (reserved for the Phase-6 advancement critical section). * - Loop-group runtime state provisioning and traversal counters. * - A budget-tracking stub (graph-level cumulative consumption). * * Design reference: `.rolebox/design/engine-state-machine.md` §1. * * Scope note: `provision()` only performs topology bootstrap (root discovery + * initial node status assignment). Join evaluation, edge payload routing, and * node advancement are out of scope for this module (later phases). */ import { EnginePhase } from "../../constants.ts"; import type { GraphDeclaration, NodeConfig } from "../../types.graph-v2.ts"; import type { EngineState, GraphBudgetState, NodeRuntimeState } from "../../types.engine-v2.ts"; /** * Zeroed `UsageRecord` used as the initial value for every node's * `tokensConsumed` and as the base for graph-level budget deltas. */ export declare const EMPTY_USAGE: { readonly inputTokens: 0; readonly outputTokens: 0; readonly cost: 0; }; /** Returns a freshly zeroed graph-level budget state. */ export declare function emptyGraphBudget(): GraphBudgetState; /** * Apply a consumption delta to the graph-level budget counters. * * Budget *enforcement* (checking cumulative consumption against * `graphDeclaration.budget.max_total_*` limits before dispatch) is a * deliberate non-goal for this engine primitive. This function only * accumulates the graph-level counters embedded in `EngineState.budget`. */ export declare function applyBudgetDelta(state: EngineState, delta: { inputTokens?: number; outputTokens?: number; cost?: number; sessions?: number; }): void; /** Whether `state.phase → to` is a legal engine-phase transition. */ export declare function canTransitionPhase(state: EngineState, to: EnginePhase): boolean; /** * Transition the engine lifecycle phase, enforcing the * `idle → executing → complete` ordering. Any other transition is rejected * with an error. */ export declare function transitionPhase(state: EngineState, to: EnginePhase): void; /** * Create a fresh {@link EngineState} in the `idle` phase for the given graph * declaration. The `nodes` map is empty until {@link provision} runs. */ export declare function createEngineState(graphDeclaration: GraphDeclaration, graphId: string): EngineState; /** * Register a single node from its declaration into `state.nodes` as a fresh * `NodeRuntimeState` (status `pending`). Throws on duplicate node ID — node * IDs must be unique within a graph. */ export declare function registerNode(state: EngineState, config: NodeConfig): NodeRuntimeState; /** * Compute the effective in-degree for every node from the graph declaration's * edges, applying the same filtering rules as {@link provision}: * * - `revise_needed` back-edges are excluded (they are feedback, not * upstream dependencies). * - Intra-loop-group `always` edges are excluded (they form the bounded-cycle * backbone and must not block root discovery). * * This is a shared helper called by both `provision()` (bootstrap) and * `adoptPriorNodeStates()` (post-adoption reconciliation), so the two sites * can never drift apart on filtering semantics. */ export declare function computeInDegrees(state: EngineState): Map; /** Return the node IDs that have no incoming edges (graph roots). */ export declare function getRootNodeIds(state: EngineState): string[]; /** * Provision the engine state from its graph declaration. * * For every node: root nodes (no upstream edges) are placed in `ready` status * and added to the frontier; every other node starts in `pending`. This is a * pure topology bootstrap — no edges are evaluated, no joins resolved. */ export declare function provision(state: EngineState): void; /** * Build the signal contract text for a loop-group member node. * * Generates a `` XML block telling the agent which signals * to emit and when. The contract is dynamic — it includes only the signal * types found in the node's on_signal outbound edges (plus the universal * `answer` and `escalate` signals, which every node needs). * * Non-loop nodes and nodes without `on_signal` outbound edges are unaffected. */ export declare function buildSignalContract(signalTypes: string[]): string; /** * Inject signal contract instructions into every loop-group member node * that has one or more `on_signal` outbound edges. * * When a node is part of a loop group AND has outgoing edges that are * signal-activated (type === "on_signal"), the agent needs to know which * signals to emit so the engine can route the result correctly. This * function scans the graph declaration and appends a * block to the node's prompt. * * Non-loop nodes and loop nodes without on_signal outbound edges are * left unchanged. */ export declare function injectSignalContracts(state: EngineState): void; /** * Increment a loop group's traversal counter. * * Returns `false` (no-op) when the group is already at `maxTraversals` — the * `revise_needed` back-edge is deactivated once the hard cap is reached, per * `.rolebox/design/graph-model.md` §4.2. Otherwise increments and returns * `true`. Throws for an unknown group id. */ export declare function incrementLoopTraversal(state: EngineState, groupId: string): boolean; /** Whether a loop group has reached its `maxTraversals` hard cap. */ export declare function isLoopExhausted(state: EngineState, groupId: string): boolean; /** Whether `nodeId` is currently in the frontier (ready-for-dispatch) set. */ export declare function isInFrontier(state: EngineState, nodeId: string): boolean; /** Add a node to the frontier. No-op (returns false) if already present. */ export declare function addToFrontier(state: EngineState, nodeId: string): boolean; /** Remove a node from the frontier. Returns false if it was not present. */ export declare function removeFromFrontier(state: EngineState, nodeId: string): boolean; /** * Acquire the advancement re-entrancy lock. * * Returns `true` if the lock was acquired (was not already held), `false` if it * was already held. Only one advancement critical section may run at a time, * per the `_advancing` pattern in `.rolebox/design/engine-state-machine.md` * §3.3. */ export declare function acquireAdvancingLock(state: EngineState): boolean; /** Release the advancement lock. */ export declare function releaseAdvancingLock(state: EngineState): void; /** * Queue a completion deferred during an advancement critical section. The * completions are drained by {@link drainPendingCompletions} in the * critical section's `finally` block. */ export declare function queuePendingCompletion(state: EngineState, nodeId: string): void; /** * Drain and return the deferred completions, resetting the pending queue. * Completions that arrive while `advancingLock` is held are re-processed after * the critical section exits. */ export declare function drainPendingCompletions(state: EngineState): string[]; /** Convenience: look up a node's runtime state, or throw if absent. */ export declare function getNode(state: EngineState, nodeId: string): NodeRuntimeState; /** * Fingerprint a convergence node's signal payload for stuck detection. * * Strings are compared on their trimmed text; objects are canonicalized. This * is the "identical output" test in graph-model.md §4.3 — two revision findings * that carry the same verdict/content count as a repeated output. */ export declare function fingerprintPayload(payload: unknown): string; /** * Record a convergence node's output against the loop group's rolling * fingerprint and report whether the loop is now stuck. * * - Fingerprint unchanged from the last traversal → `consecutiveStale` rises. * - Fingerprint changed → `consecutiveStale` resets to 1 (a fresh signal). * - First recorded output → `consecutiveStale` set to 1. * * Returns `true` when `consecutiveStale >= CONSECUTIVE_STALE_THRESHOLD`, i.e. * the loop has produced identical convergence output on at least the required * consecutive traversals and should exit with `escalate` (reason `"stuck"`). * * Pure state mutation on the loop group's tracker — never touches a node or the * frontier. The caller decides how to react to the `stuck` verdict. */ export declare function recordConvergenceOutput(state: EngineState, groupId: string, payload: unknown): boolean; /** * Clear a loop group's stuck tracker. * * Called on a `converged` (`answer`) signal — the loop exited on the happy path, * so no output history needs to carry into a fresh group run. */ export declare function resetConvergenceTracker(state: EngineState, groupId: string): void; //# sourceMappingURL=engine-state.d.ts.map