/** * Speculative branch-and-promote — parallel A/B solution exploration over * COW memory branches (agenticow step 4). * * Concept (from agenticow's examples/ab-branches + promotion-pipeline): * Fan out N candidate approaches, each on its own Copy-On-Write branch of a * shared base `.rvf` memory. Each candidate explores/writes independently * against its own branch handle. Score the results, PROMOTE the winner's * branch back into base, and DISCARD the losers — which for agenticow means * deleting the branch files (162 bytes each), not re-copying GB of state. * * This is the memory-state analogue of the git-worktree-per-agent pattern * used for parallel code agents: cheap speculative forks, keep one, throw the * rest away at near-zero cost. * * This module is intentionally COMPOSED ON TOP of the existing agenticow verbs * (`fork` / `promote`) and the shared `_agenticow.ts` helpers — it does not * reimplement COW semantics. It is generic over the candidate result type so * callers supply their own `fn` (what to do on each branch) and `score` * (how good the branch turned out). * * @module @claude-flow/cli/agenticow/speculative-exploration */ import { type AgenticowApi } from '../mcp-tools/agenticow-loader.js'; /** A COW memory handle (agenticow `AgenticMemory`), kept `any` to avoid a hard type dep. */ export type MemoryHandle = any; export interface SpeculativeCandidate { /** Human-readable branch label (validated: `[A-Za-z0-9_.\-:/@]`). */ label: string; /** * The exploration to run against this candidate's isolated branch handle. * Receives the forked branch (read-through of base ∪ its own edits). May * ingest, delete, query, etc. Its return value is fed to `score`. */ fn: (branch: MemoryHandle) => TResult | Promise; } export interface ExploreOptions { /** * Maps a candidate label to the on-disk path for its branch `.rvf` file. * Loser paths are deleted after scoring. */ branchPath: (label: string) => string; /** Persist winner-branch + save manifests. Default true. */ persist?: boolean; /** * Tie-break: when two candidates tie on score, the earlier one (lower index) * wins by default (stable). Set `'last'` to prefer the later candidate. */ tieBreak?: 'first' | 'last'; /** * PROMOTION GATE (ADR-171). A branch is promote-INELIGIBLE unless cleared by a * real evaluation oracle, or by an explicitly-accepted Fable judge. When * provided, the top-scoring candidate is promoted ONLY if this returns * `{cleared:true, by:'oracle:test-exec'}` or `{cleared:true, by:'judge:fable'}`. * `proxy:structural` can NEVER clear a promote — score rank alone does not * graduate work into base. Omit for legacy score-only promotion (unverified). */ clearance?: (winnerResult: unknown, label: string) => Promise<{ cleared: boolean; by: PromotionProvenance; reason?: string; }>; /** * Force the clearance gate even without a `clearance` fn — a missing gate then * means the winner is ineligible (fail-closed). Default: gate enforced iff * `clearance` is supplied. */ requireClearance?: boolean; } /** Provenance of a promotion decision (ADR-171 trust tiers). */ export type PromotionProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural' | 'unverified'; /** * Causal failure receipt (ADR-171). Emitted for every discarded loser and for * an ineligible/failed winner — a rollback that loses *why* is half-useful. */ export interface SpeculativeReceipt { label: string; score: number; /** What the branch changed vs its lineage, when introspectable. */ diff: { added: number[]; overridden: number[]; deleted: number[]; } | null; /** Why this branch did not graduate. */ outcome: 'discarded-loser' | 'winner-ineligible' | 'winner-failed'; provenance: PromotionProvenance; reason?: string; } export interface SpeculativeBranchOutcome { label: string; path: string; score: number; result: TResult; /** true for the promoted winner, false for discarded losers. */ kept: boolean; } export interface SpeculativeResult { /** Label of the winning (promoted) candidate. */ winner: string; /** label → score for every candidate. */ scores: Record; /** Whether the winner was successfully promoted into base. */ promoted: boolean; /** How the promotion decision was reached (ADR-171 provenance). */ promotedBy: PromotionProvenance; /** Human-readable promotion decision, e.g. 'promoted:oracle:test-exec' or 'ineligible:proxy-cannot-clear'. */ promotionDecision: string; /** agenticow promote() stats for the winner ({ ingested, deleted }). */ promoteStats: { ingested: number; deleted: number; } | null; /** Labels of the discarded losers whose branch files were deleted. */ discarded: string[]; /** Causal failure receipts for discarded losers + an ineligible winner. */ receipts: SpeculativeReceipt[]; /** Per-candidate detail (score, path, result, kept). */ branches: SpeculativeBranchOutcome[]; } /** * Fork one branch per candidate off `base`, run each `fn` against its own * branch handle, score the results, PROMOTE the best branch back into `base`, * and DISCARD (delete the files of) the rest. * * The caller owns `base`: this function mutates it in-memory via `promote()` * but does NOT save it (only the caller knows the base file path). Persist the * base yourself after this resolves (e.g. `base.save(manifestFor(basePath))`). * * @param base An opened agenticow memory handle to branch from. * @param candidates The A/B candidates — each `{label, fn}`. * @param score Scores a candidate's result; higher wins. * @param opts Branch-path mapping + persistence knobs. */ export declare function explore(base: MemoryHandle, candidates: SpeculativeCandidate[], score: (result: TResult, label: string) => number, opts: ExploreOptions): Promise>; /** * Convenience wrapper that owns the whole lifecycle for a file-path base: * loads agenticow (returns `null` when the optional dep is absent), opens the * base with its lineage, runs {@link explore}, then persists the mutated base. * * Returns `null` when agenticow is not installed so callers can emit the * standard `{degraded:true}` contract. */ export declare function exploreFromPath(basePath: string, candidates: SpeculativeCandidate[], score: (result: TResult, label: string) => number, opts: ExploreOptions & { dimension?: number; api?: AgenticowApi; }): Promise | null>; //# sourceMappingURL=speculative-exploration.d.ts.map