Clusters phases B+C: labeling records, the naming call, snapshot payload
Core-survival labelings (vision section 1, DECIDED 2026-09-13): ClusterLabeling records in a new append-only ledger (data/graph/cluster-labels.jsonl), immutable, id = clu_ + sha256 of the sorted core so re-naming races and re-runs dedupe at the store seam. matchClusterLabelings (core/clusters.ts, pure): a community keeps a record's label while at least half its core survives in it; both sides single-successor (a split keeps one name; greedy by overlap, ties to the newer record). The namer (src/agent/subagents/cluster-namer.ts): ONE singular cheap LLM call (new 'labeler' role, glm-flash/low) names every new/changed community at once, given member names + statements (capped at 12 members per cluster). refreshClusterLabels runs POST-EXTRACTION (user-decided) from both front doors, fire-and-forget — naming failure never fails the run; refreshes serialize behind a chain so parallel imports never mint duplicate labels. import-notes.ts awaits clusterNamingSettled before exit. Payload: GraphSnapshot.clusters (>= MIN_CLUSTER_SIZE 3) joins detection with labelings at read time — claimed clusters carry the record's label and id; unlabeled ones carry a member-hash id and null label. Detection is deterministic so no memo in v1 (ms at current scale; revisit at vault scale). Tests (372/372): matcher half-rule/split/merge/greedy/tie cases; naming end-to-end through importNotes (one call, one record, claim on re-refresh), naming failure non-fatal; snapshot derivation with labeling join; 11 test pipelines now pin an explicit labeler seam.
This commit is contained in:
@@ -270,6 +270,37 @@ export interface AuditResult {
|
||||
}
|
||||
|
||||
/** The GET /api/graph payload — everything the home view's PURE renderer needs. */
|
||||
/** cluster-labels.jsonl line — one LLM-named community (vision §1,
|
||||
* DECIDED 2026-09-13). Append-only history: records are immutable, never
|
||||
* edited or deleted; dead labelings remain as history. IDENTITY is the
|
||||
* sorted member set ("core"), hashed into the id, so re-naming the same
|
||||
* community dedupes and a labeling survives graph edits as long as at
|
||||
* least half its core survives in some single community (core-survival;
|
||||
* under half → the community is renamed, splits can keep only one
|
||||
* successor). */
|
||||
export interface ClusterLabeling {
|
||||
/** "clu_" + content hash of the sorted core. */
|
||||
id: string;
|
||||
/** The short domain label (1–4 words), e.g. "Differential forms". */
|
||||
label: string;
|
||||
/** The member node ids the label was minted for, sorted — the survival core. */
|
||||
core: string[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** One rendered cluster in the snapshot payload (vision §1). Members are
|
||||
* node ids; the renderer draws the hull from member positions at draw
|
||||
* time and gates the label inversely on the node-label zoom control. */
|
||||
export interface Cluster {
|
||||
/** The labeling record id when claimed; unlabeled clusters still
|
||||
* appear (hull, no name) with a member-hash id. */
|
||||
id: string;
|
||||
/** null until the naming call has minted/claimed a labeling. */
|
||||
label: string | null;
|
||||
/** Node ids, sorted. Hull + centroid are renderer concerns. */
|
||||
members: string[];
|
||||
}
|
||||
|
||||
export interface GraphSnapshot {
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
@@ -286,6 +317,10 @@ export interface GraphSnapshot {
|
||||
* shown next to the session list — the confidence history). Multiple
|
||||
* notes for one (node, chat) collapse to the latest. */
|
||||
nodeObservations: Record<string, Record<string, string>>;
|
||||
/** Structural communities (Louvain over follows+supports), size ≥ 3,
|
||||
* joined with persisted LLM labelings. Purely structural — never
|
||||
* depends on layout. */
|
||||
clusters: Cluster[];
|
||||
}
|
||||
|
||||
/** Derive a short chat title from the learner's first message (pure).
|
||||
|
||||
Reference in New Issue
Block a user