khive-fold 0.4.0

Cognitive primitives — Fold, Anchor, Objective, Selector
Documentation

khive-fold

Cognitive primitives shared across khive's runtime: Fold (streaming state reduction), Anchor (causal provenance graphs), Objective (deterministic candidate scoring and selection), and Selector (budget-constrained packing). Depends only on khive-types and khive-score — see ADR-024.

Usage

use khive_fold::{fold_fn, Fold, FoldContext};

let count_positive = fold_fn(
    |_ctx: &FoldContext| 0usize,
    |count, entry: &i32, _ctx| if *entry > 0 { count + 1 } else { count },
);

let entries = [1, -2, 3, 4, -5];
let outcome = count_positive.derive(entries.iter(), &FoldContext::new());
assert_eq!(outcome.state, 3);
assert_eq!(outcome.entries_processed, 5);

Fold<L, S> reduces a stream of &L entries into a state S via init + reduce (+ optional finalize); fold_fn(initial, step) builds one from two closures without a custom type. derive runs the fold to completion and returns a FoldOutcome<S> (state, entries_processed); derive_filtered adds a predicate that skips entries before reduce sees them. TryFold is the fallible counterpart, returning FoldFailure on error. CountFold, FilterCountFold, and SumI64Fold are ready-made folds for the common counting/summing cases.

FoldContext::new() defaults as_of to the Unix epoch, not wall-clock time — this crate never calls a clock. Callers that need "as of now" build a context explicitly with FoldContext::at(timestamp).

Anchor — causal provenance

AnchorGraph is an in-memory graph of AnchorRef nodes (id, kind, stable_id) connected by labeled edges. Anchor::trace follows forward edges from a starting anchor up to max_depth hops; Anchor::credit walks backward from an outcome anchor, returning (AnchorRef, weight) pairs with the contribution weight halving per hop. BfsAnchor is the provided BFS implementation of both.

Objective — deterministic scoring

Objective<T> scores and filters candidates (score, passes_score, batch_score, select, select_top); DeterministicObjective<T> extends it for T: HasId, breaking score ties by ID so select_top_deterministic gives the same ranking regardless of input order. Built-in objectives (RelevanceObjective, RecencyObjective, SalienceObjective, ThresholdObjective, MaxScoreObjective, FirstMatchObjective) and combinators (WeightedObjective, ConsensusObjective, PriorityObjective, UnionObjective, NegateObjective, ScaleObjective) compose via the objective module; objective_fn builds one from a closure.

Selector — budget-constrained packing

Selector<T>::select(inputs: Vec<SelectorInput<T>>, budget, weights) collapses N scored, sized candidates into a SelectorOutput<T> that fits budget. GreedySelector filters by SelectorWeights.min_score, applies category_weights multipliers, then packs by effective score (score plus an optional epistemic_weight * information_gain term) with deterministic size-then-ID tie-breaking; diversity_bias > 0 switches to a pick-best-remaining loop that penalizes repeated categories.

Checkpoint — durable fold snapshots

Checkpoint<S>::new(id, state, uuid, entries_processed, context, fold_version) wraps a serializable fold state with a BLAKE3 content hash (khive_types::Hash32) computed from the state, verified on load. Like FoldContext, it never calls a clock — created_at defaults to the epoch; callers set it explicitly if wall-clock time matters. CheckpointStore / InMemoryCheckpointStore provide a save/load contract for callers that persist checkpoints across restarts.

Where this sits

Built on khive-types and khive-score only — no other khive-* dependency, by design (ADR-024's boundary). Used by higher layers of the runtime that need deterministic state reduction, provenance tracing, or budget-constrained context packing without pulling in storage or query machinery.

License

Apache-2.0.