Incremental clustering sessions

Updated Jul 16, 2026

Prefer build_session() over Chutoro::run() when the application needs a long-lived, incrementally updated index instead of a one-shot batch clustering run.

The public session surface exposes three types. SessionConfig carries the validated min_cluster_size, HnswParams, and SessionRefreshPolicy. SessionRefreshPolicy represents either manual refresh or an append-threshold trigger. ClusteringSession<D> owns the live session state.

Construct a session through ChutoroBuilder:

use std::sync::Arc;
use chutoro_core::{
    ChutoroBuilder, DataSource, DataSourceError, MetricDescriptor,
    SessionRefreshPolicy,
};

# struct Dummy(Vec<f32>);
# impl DataSource for Dummy {
#     fn len(&self) -> usize { self.0.len() }
#     fn name(&self) -> &str { "dummy" }
#     fn distance(&self, i: usize, j: usize) -> Result<f32, DataSourceError> {
#         let a = self.0.get(i).ok_or(DataSourceError::OutOfBounds { index: i })?;
#         let b = self.0.get(j).ok_or(DataSourceError::OutOfBounds { index: j })?;
#         Ok((a - b).abs())
#     }
#     fn metric_descriptor(&self) -> MetricDescriptor {
#         MetricDescriptor::new("abs")
#     }
# }
#
# fn example(source: Arc<Dummy>) -> Result<(), chutoro_core::ChutoroError> {
let mut session = ChutoroBuilder::new()
    .with_min_cluster_size(10)
    .with_session_refresh_policy(SessionRefreshPolicy::manual())
    .build_session(source)?;

assert_eq!(session.point_count(), 0);
assert_eq!(session.snapshot_version(), 0);

session.append(&[0, 1])?;
session.recompute_core_distances()?;

assert_eq!(session.point_count(), 2);
assert!(session.core_distance(0).is_some());
assert_eq!(session.snapshot_version(), 0);
# Ok(())
# }

Sessions are CPU-only, so ExecutionStrategy::GpuPreferred is rejected during build_session(). Empty and undersized sources are accepted at construction time because session creation does not seed HNSW or run the batch bootstrap path.

After append, newly inserted points have dirty core distances. Calling core_distance(i) before a recompute returns None for those points. Call recompute_core_distances() after append batches to compute core distances for new points and for existing points that appeared near those new points in HNSW. Treat these recomputed values as provisional until each point has at least min_cluster_size non-self neighbours. Before that neighbourhood saturation point, the fallback core-distance rule can still increase; monotonic non-increase only applies after saturation. Call recompute_core_distances_full() when the session must re-establish parity with a from-scratch batch core-distance pass; it searches every inserted point and is more expensive than the incremental path.

Use append(&[...]) to insert source indices that already exist in the backing DataSource. The session does not copy or extend source storage; the caller owns that storage contract. Each index is inserted into the live HNSW index through the edge-harvesting path, and harvested candidate edges are kept internally for the later refresh workflow.

append is fail-fast with partial progress. If a slice contains [0, 1, bad] and the first two inserts succeed, those points remain in the session and their harvested edges remain pending when the error for bad is returned. Out-of-bounds indices surface as ChutoroError::DataSource; duplicate indices and HNSW structural failures surface as ChutoroError::CpuHnswFailure.

Limitations

  • The v1 incremental design is append-oriented; deletion and arbitrary in-place mutation are not part of the public session surface.
  • Stable cluster identity across snapshots is not guaranteed until roadmap item 12.3.1 lands.
  • Refresh is intended to operate as a micro-batched workflow rather than a per-item online relabelling path.
  • A refresh can relabel existing points as well as newly appended points.

Refresh and full batch bootstrap are not yet available on the public session surface. Those workflows remain future roadmap work. The cpu feature must be enabled to access build_session(), append(&[usize]), SessionRefreshPolicy, SessionConfig, and ClusteringSession<D>.