/**
 * graph() — a FIXED DAG of runners, levelized at build time.
 *
 * WHY this exists: `Sequence` and `workflow()` run steps in a line, and
 * `Parallel` fans out ONCE and merges. Real pipelines are neither: an
 * intake step feeds two independent lookups, and a writer waits for both.
 * Expressing that with the existing compositions means nesting a Parallel
 * inside a Sequence and threading values through by hand — and on this
 * codebase that hand-off silently loses structured data (see "The trap"
 * below). `graph()` states the shape once, as nodes and edges, and lets
 * the engine work out what can run at the same time.
 *
 * What it gives you:
 *   - **Concurrency you did not have to schedule.** Kahn levelization at
 *     BUILD time groups nodes with no dependency between them; every node
 *     in a level runs at the same time.
 *   - **A shape checked before it runs.** A cycle, an edge pointing at a
 *     node that does not exist, or a duplicate id is refused at BUILD
 *     time, naming the offender. You cannot construct a broken graph.
 *   - **No silent merges.** A node with two or more parents MUST declare
 *     a `join` — a silent merge is a wrong merge, so the build refuses
 *     and names the node.
 *   - **Values, not text.** An edge's payload is the producer's OUTPUT
 *     handed to the consumer, unchanged. There is no shared mutable scope
 *     between nodes: a node reads exactly what its parents produced.
 *
 * Pattern: Adapter over footprintjs's subflow mounts — a level with
 *          several nodes becomes stacked `addSubFlowChart` calls (a fork,
 *          run concurrently); a level with one node is mounted
 *          sequentially (`addSubFlowChartNext`, which resumes cleanly
 *          across a pause). One join stage between levels.
 * Role:    core-flow/ layer, alongside Sequence/Parallel/Conditional/
 *          Loop/Workflow. Pure control flow — no LLM dependency.
 * Emits:   agentfootprint.composition.enter / exit, reported as kind
 *          `'Sequence'`. See "Why kind 'Sequence'" below.
 *
 * ## The trap this was built around (verified against footprintjs, not docs)
 *
 * The obvious sketch — `graph = Sequence(Parallel(level0), Parallel(level1), …)`
 * — does NOT work on this codebase, for two independent reasons:
 *
 *   1. `Sequence`'s step contract is `{ message: string } -> string`, and
 *      its step `outputMapper` coerces a non-string step output to `''`.
 *      `workflow()` (v7.10.0) exists precisely because of this.
 *   2. `Parallel` has the SAME limit one layer down: its branch type is
 *      `Runner<{ message: string }, string>` and its branch `outputMapper`
 *      coerces a non-string branch output to `''` (`Parallel.ts`, the
 *      `typeof sfOutput === 'string' ? sfOutput : ''` mapper). So a
 *      Parallel level cannot carry a structured value either.
 *
 * So `graph()` is built on the pass-through model `workflow()` established
 * — its own composition, its own mappers, the same recorder wiring and the
 * same `composition.enter` / `exit` events — rather than on top of
 * Sequence/Parallel.
 *
 * ## Why kind 'Sequence'
 *
 * `CompositionKind` is a CLOSED public union (`'Sequence' | 'Parallel' |
 * 'Conditional' | 'Loop'`). Widening it would break exhaustive switches in
 * consumer code for no behavioural gain — the same call v7.10.0 made for
 * `workflow()`. A graph's LEVELS are a sequence (level 0, then level 1, …),
 * so `'Sequence'` is the honest member of that union: this composition runs
 * its levels in order. The fan-out WITHIN a level is visible in the chart
 * itself (a fork node per level), which is where a renderer reads it from.
 *
 * ## Honest limits (all verified against the engine, pinned in tests)
 *
 *   1. Only PLAIN DATA crosses a node boundary — the same limit
 *      `workflow()` documents. A value with a prototype (Date, Map, class
 *      instance) arrives as `{}`; `undefined` fields are dropped.
 *   2. A node must RETURN its output: the value handed to its children is
 *      the node chart's traversal result.
 *   3. A node that THROWS is always reported as
 *      `graph '<id>': node '<node>' failed: <reason>`, but it reaches that
 *      sentence by two different routes. In a CONCURRENT level footprintjs
 *      runs children under `Promise.allSettled`, so a failed child is
 *      simply ABSENT from the results and the level join turns that
 *      absence into the error. In a SEQUENTIAL (single-node) level the
 *      error rejects the run raw, and `rethrowWithNodeAttribution` renames
 *      it. Consumers see one shape either way.
 *   4. A node that PAUSES surfaces as a pause — the engine halts the
 *      traversal before the level's join runs, so `run()` returns a
 *      `RunnerPauseOutcome`. `resume()` then carries on through the REST
 *      of the graph only when the paused node was ALONE in its level (a
 *      sequential mount). Resuming into a fork child completes that child
 *      and stops: the remaining levels do not run. Give a node that asks a
 *      human a level of its own. Both halves are pinned in tests.
 *
 * @example  a diamond: A feeds B and C, D waits for both
 * ```ts
 * const pipeline = graph({
 *   nodes: [
 *     { id: 'intake', runner: intake },
 *     { id: 'orders', runner: lookupOrders },
 *     { id: 'billing', runner: lookupBilling },
 *     {
 *       id: 'reply',
 *       runner: writeReply,
 *       // Two parents ⇒ a join is REQUIRED. `upstream` is keyed by node id.
 *       join: (upstream) => ({
 *         orders: upstream.orders as OrderInfo,
 *         billing: upstream.billing as BillingInfo,
 *       }),
 *     },
 *   ],
 *   edges: [
 *     { from: 'intake', to: 'orders' },
 *     { from: 'intake', to: 'billing' },
 *     { from: 'orders', to: 'reply' },
 *     { from: 'billing', to: 'reply' },
 *   ],
 * });
 *
 * const out = await pipeline.run({ message: 'where is my refund?' });
 * // out = { intake: …, orders: …, billing: …, reply: … } — keyed by node id
 * ```
 */
import { type FlowchartCheckpoint, type StructureRecorder } from 'footprintjs';
import type { RunOptions } from '../core/runner.js';
import type { RunnerPauseOutcome } from '../core/pause.js';
import type { Runner } from '../core/runner.js';
import { RunnerBase } from '../core/RunnerBase.js';
/**
 * One node of the graph: an id, the runner that does the work, and — when
 * the node has more than one parent — how to merge what those parents
 * produced into this node's input.
 */
export interface GraphNode<I = unknown, O = unknown> {
    /** Unique within the graph. Used as the results key and the chart node id. */
    readonly id: string;
    /** The work. Any Runner: LLMCall, Agent, a Sequence, another graph. */
    readonly runner: Runner<I, O>;
    /**
     * Merge upstream outputs into this node's input. `upstream` is keyed by
     * PARENT NODE ID, and each value is that parent's output, unchanged.
     *
     * Optional for a node with 0 or 1 parents (a single parent's output is
     * passed through). **REQUIRED when a node has 2+ parents** — a silent
     * merge is a wrong merge, so the build refuses and names the node.
     */
    readonly join?: (upstream: Readonly<Record<string, unknown>>) => I;
    /** Human-friendly label for events + topology. Default: the node id. */
    readonly name?: string;
}
/** A directed dependency: `from` must finish before `to` starts. */
export interface GraphEdge {
    readonly from: string;
    readonly to: string;
}
export interface GraphOptions {
    /** The nodes. Ids must be unique; at least one is required. */
    readonly nodes: readonly GraphNode<any, any>[];
    /** The dependencies. Every endpoint must name a declared node. */
    readonly edges: readonly GraphEdge[];
    /** Human-friendly name for events + topology. Default `'Graph'`. */
    readonly name?: string;
    /** Stable id used for topology + events. Default `'graph'`. */
    readonly id?: string;
    /**
     * Optional build-time recorders passed through to footprintjs's
     * `flowChart()` factory — they observe this graph's OWN nodes (Seed +
     * one mount per graph node + one join per level + Finalize). Not
     * propagated into the mounted node charts; attach them to each node
     * runner for full coverage.
     */
    readonly structureRecorders?: readonly StructureRecorder[];
}
/** The graph's own input — handed to every ROOT node (one with no parents). */
export type GraphInput = Record<string, unknown>;
/** Outputs keyed by node id. Every node that ran contributes one entry. */
export type GraphOutput = Record<string, unknown>;
/**
 * Kahn levelization: group nodes so that everything in level N depends
 * only on levels < N. Nodes within a level are independent BY
 * CONSTRUCTION, which is exactly the licence to run them concurrently.
 *
 * Declaration order is preserved inside each level so a graph's chart —
 * and therefore its trace — is deterministic.
 *
 * Throws (naming the offender) on: an unknown edge endpoint, a duplicate
 * node id, a cycle, or a fan-in > 1 with no `join`.
 */
export declare function levelize(nodes: readonly GraphNode<any, any>[], edges: readonly GraphEdge[]): readonly (readonly GraphNode<any, any>[])[];
/**
 * A fixed DAG of runners. Build one with {@link graph}.
 */
export declare class Graph extends RunnerBase<GraphInput, GraphOutput> {
    readonly name: string;
    readonly id: string;
    private readonly nodes;
    private readonly levels;
    private readonly parentsOf;
    private readonly opts;
    private currentRunContext;
    /**
     * Per-node first-error records for the current run. footprintjs's
     * `SubflowExecutor` swallows a subflow error into the parent's debug
     * bag and skips the `outputMapper`, so the message never reaches parent
     * scope on its own. An internal recorder captures it here; the level
     * join reads it to name what actually went wrong. Mirrors Parallel's
     * `branchErrors`, epoch-guarded for the same reason.
     */
    private readonly nodeErrors;
    /** Monotonic run token — see Parallel's `runEpoch`. */
    private runEpoch;
    constructor(opts: GraphOptions);
    /** How the graph was levelized — level 0 first. Stable post-construction. */
    getLevels(): readonly (readonly string[])[];
    run(input: GraphInput, options?: RunOptions): Promise<GraphOutput | RunnerPauseOutcome>;
    resume(checkpoint: FlowchartCheckpoint, input?: unknown, options?: RunOptions): Promise<GraphOutput | RunnerPauseOutcome>;
    /**
     * Give a RAW rejection the same node-naming shape the level join
     * produces.
     *
     * A node in a SEQUENTIAL (single-node) level rejects the run with its
     * own error — the level join never runs, so nothing has attributed it to
     * a node yet. The error recorder did see it, so correlate (by identity
     * first, then bare message) and rename. Anything that does not correlate
     * — including the join's own already-attributed error — is rethrown
     * untouched.
     */
    private rethrowWithNodeAttribution;
    private createExecutor;
    /**
     * Capture the first error per node. The node id is the first segment of
     * the engine-prefixed `stageId` (`orders/call-llm` → node `orders`) —
     * the same correlation Parallel uses, and the only one that survives a
     * node mounting subflows of its own.
     */
    private makeNodeErrorRecorder;
    private finalizeResult;
    private buildChart;
    /**
     * What one node receives. Roots get the graph's own input; a single
     * parent is passed through; 2+ parents go through the node's `join`
     * (which the build already guaranteed exists).
     *
     * A root's input is the graph's own, as it came (a composed mark handed to
     * the graph included). Every other node is handed OTHER runners' output —
     * marked for a runner that reads the marker (`core/messageFrom.ts` ·
     * `composedInput`), so another model's words never count as the person's;
     * every other runner gets the input it always did.
     *
     * `parent` here is the RAW parent state the engine hands an
     * `inputMapper` — not a TypedScope — so structured upstream values read
     * back intact.
     */
    private inputForNode;
}
/**
 * Build a fixed DAG of runners. Independent nodes run concurrently; the
 * result is every node's output, keyed by node id.
 *
 * The shape is checked at BUILD time — a cycle, an edge pointing at an
 * unknown node, a duplicate id, or a 2+-parent node with no `join` throws
 * here, naming the offender, rather than misbehaving mid-run.
 *
 * @example  a fan-out with a merge
 * ```ts
 * const pipeline = graph({
 *   nodes: [
 *     { id: 'plan', runner: planner },
 *     { id: 'search', runner: searcher },
 *     { id: 'recall', runner: memory },
 *     { id: 'answer', runner: writer, join: (u) => ({ ...u }) },
 *   ],
 *   edges: [
 *     { from: 'plan', to: 'search' },
 *     { from: 'plan', to: 'recall' },
 *     { from: 'search', to: 'answer' },
 *     { from: 'recall', to: 'answer' },
 *   ],
 * });
 *
 * const out = await pipeline.run({ message: 'what changed last week?' });
 * console.log(out.answer);
 * ```
 */
export declare function graph(opts: GraphOptions): Graph;
