@ponraaj/dimple-sdk 0.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +13 -0
- package/dist/dimple.d.ts +101 -0
- package/dist/errors.d.ts +63 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8430 -0
- package/dist/indexer.d.ts +43 -0
- package/dist/internal/config/index.d.ts +4 -0
- package/dist/internal/config/loader.d.ts +9 -0
- package/dist/internal/config/schema.d.ts +176 -0
- package/dist/internal/config/service.d.ts +10 -0
- package/dist/internal/core/core.d.ts +95 -0
- package/dist/internal/core/index.d.ts +3 -0
- package/dist/internal/core/layer.d.ts +21 -0
- package/dist/internal/embedder/index.d.ts +4 -0
- package/dist/internal/embedder/openai.d.ts +8 -0
- package/dist/internal/embedder/provider.d.ts +29 -0
- package/dist/internal/embedder/registry.d.ts +10 -0
- package/dist/internal/embedder/transformers.d.ts +18 -0
- package/dist/internal/extractor/code-extractor.d.ts +15 -0
- package/dist/internal/extractor/content-handler.d.ts +17 -0
- package/dist/internal/extractor/index.d.ts +4 -0
- package/dist/internal/extractor/languages/index.d.ts +24 -0
- package/dist/internal/extractor/languages/javascript.d.ts +22 -0
- package/dist/internal/extractor/languages/typescript.d.ts +20 -0
- package/dist/internal/extractor/models/extraction.d.ts +53 -0
- package/dist/internal/llm/dream-classifier.d.ts +14 -0
- package/dist/internal/llm/index.d.ts +4 -0
- package/dist/internal/llm/llm.d.ts +54 -0
- package/dist/internal/llm/provider.d.ts +14 -0
- package/dist/internal/llm/service.d.ts +29 -0
- package/dist/internal/logger/format.d.ts +6 -0
- package/dist/internal/logger/index.d.ts +4 -0
- package/dist/internal/logger/ring.d.ts +33 -0
- package/dist/internal/logger/service.d.ts +50 -0
- package/dist/internal/logger/types.d.ts +39 -0
- package/dist/internal/parser/code-parser.d.ts +12 -0
- package/dist/internal/parser/index.d.ts +7 -0
- package/dist/internal/parser/language-store.d.ts +12 -0
- package/dist/internal/parser/models/parseRequest.d.ts +17 -0
- package/dist/internal/parser/models/treeHandle.d.ts +23 -0
- package/dist/internal/parser/tree-sitter-engine.d.ts +15 -0
- package/dist/internal/retriever/hybrid.d.ts +48 -0
- package/dist/internal/retriever/index.d.ts +3 -0
- package/dist/internal/retriever/rrf.d.ts +11 -0
- package/dist/internal/shared/errors/index.d.ts +2 -0
- package/dist/internal/shared/errors/parser.d.ts +21 -0
- package/dist/internal/shared/errors/wasm.d.ts +28 -0
- package/dist/internal/shared/types/index.d.ts +1 -0
- package/dist/internal/shared/types/language.d.ts +3 -0
- package/dist/internal/store/audit.d.ts +21 -0
- package/dist/internal/store/db-backend.d.ts +111 -0
- package/dist/internal/store/drizzle-schema.d.ts +1 -0
- package/dist/internal/store/error.d.ts +19 -0
- package/dist/internal/store/fnv1a32.d.ts +14 -0
- package/dist/internal/store/generated-migrations.d.ts +15 -0
- package/dist/internal/store/graph/candidates.d.ts +93 -0
- package/dist/internal/store/graph/concepts.d.ts +169 -0
- package/dist/internal/store/graph/dream.d.ts +171 -0
- package/dist/internal/store/graph/errors.d.ts +25 -0
- package/dist/internal/store/graph/index.d.ts +15 -0
- package/dist/internal/store/graph/observability.d.ts +37 -0
- package/dist/internal/store/graph/repair.d.ts +85 -0
- package/dist/internal/store/graph/retrieval-post-pass.d.ts +58 -0
- package/dist/internal/store/graph/traversal.d.ts +39 -0
- package/dist/internal/store/index.d.ts +38 -0
- package/dist/internal/store/libsql-backend.d.ts +18 -0
- package/dist/internal/store/models.d.ts +122 -0
- package/dist/internal/store/schema/graph.d.ts +970 -0
- package/dist/internal/store/schema/index.d.ts +7 -0
- package/dist/internal/store/schema/libsql.d.ts +2356 -0
- package/dist/internal/store/schema/metadata.d.ts +51 -0
- package/dist/internal/store/schema/topics.d.ts +1966 -0
- package/dist/internal/store/service.d.ts +162 -0
- package/dist/internal/store/store.d.ts +70 -0
- package/dist/internal/store/topics/errors.d.ts +39 -0
- package/dist/internal/store/topics/index.d.ts +24 -0
- package/dist/internal/store/topics/jobs.d.ts +149 -0
- package/dist/internal/store/topics/lifecycle.d.ts +126 -0
- package/dist/internal/store/topics/maintenance.d.ts +110 -0
- package/dist/internal/store/topics/observability.d.ts +102 -0
- package/dist/internal/store/topics/query.d.ts +43 -0
- package/dist/internal/store/topics/routing.d.ts +90 -0
- package/dist/internal/store/topics/topic-vectors.d.ts +24 -0
- package/dist/internal/store/topics/tree.d.ts +181 -0
- package/dist/internal/wasm/grammar-store.d.ts +14 -0
- package/dist/internal/wasm/index.d.ts +5 -0
- package/dist/internal/wasm/wasm-cache.d.ts +17 -0
- package/dist/internal/wasm/wasm-downloader.d.ts +10 -0
- package/dist/internal/wasm/wasm-registry.d.ts +16 -0
- package/dist/internal/workflow/config.d.ts +39 -0
- package/dist/internal/workflow/engine.d.ts +54 -0
- package/dist/internal/workflow/errors.d.ts +77 -0
- package/dist/internal/workflow/executor.d.ts +53 -0
- package/dist/internal/workflow/index.d.ts +13 -0
- package/dist/internal/workflow/persistence.d.ts +122 -0
- package/dist/internal/workflow/queue-store.d.ts +5 -0
- package/dist/internal/workflow/queue.d.ts +298 -0
- package/dist/internal/workflow/registry.d.ts +57 -0
- package/dist/internal/workflow/runtime.d.ts +161 -0
- package/dist/make.d.ts +27 -0
- package/package.json +45 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED — do not edit by hand. Run `bun run scripts/generate-migrations.ts`
|
|
3
|
+
* after changing packages/store/drizzle. The migration SQL is compiled into
|
|
4
|
+
* the bundle (opencode's migrations-as-code pattern): runtime migrations
|
|
5
|
+
* never read a folder from disk.
|
|
6
|
+
*/
|
|
7
|
+
export interface EmbeddedMigration {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** sha256 of the SQL — identical to drizzle-kit's journal hashes, so
|
|
10
|
+
* databases migrated by the folder-based runner skip already-applied
|
|
11
|
+
* entries and new inserts stay journal-compatible. */
|
|
12
|
+
readonly hash: string;
|
|
13
|
+
readonly sql: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const EMBEDDED_MIGRATIONS: readonly EmbeddedMigration[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write-path edge candidates — the pure, deterministic rules that scan a
|
|
3
|
+
* routed topic leaf after a memory's embedding lands and propose
|
|
4
|
+
* `artifact_edges` rows.
|
|
5
|
+
*
|
|
6
|
+
* No Effect, no DB: the store loads the leaf members and this module
|
|
7
|
+
* decides. Every rule is a pure function of the inputs (plus the conflict
|
|
8
|
+
* bands), and the output is deterministic — members are sorted by memory
|
|
9
|
+
* id and each member yields at MOST ONE candidate (the strongest
|
|
10
|
+
* applicable rule wins).
|
|
11
|
+
*
|
|
12
|
+
* ## Rules (per leaf member, evaluated in priority order)
|
|
13
|
+
*
|
|
14
|
+
* 1. Identity — the member has the same logical key as the new memory:
|
|
15
|
+
* CONFIRMED `memory_supersedes` (confidence 0.5). Same-key memories are
|
|
16
|
+
* the same logical artifact, so the newer one wins immediately; the
|
|
17
|
+
* store flips the old memory to `superseded` in the same transaction as
|
|
18
|
+
* the edge insert. Pending candidates never mutate state — this rule is
|
|
19
|
+
* the one exception because the key match is exact, not a similarity
|
|
20
|
+
* guess.
|
|
21
|
+
* 2. Near-duplicate — cosine similarity above the `nearDuplicate` band
|
|
22
|
+
* edge: PENDING `memory_supersedes` (confidence = raw cosine). Waits
|
|
23
|
+
* for confirmation; a false positive is rejected, never harmful.
|
|
24
|
+
* 3. Conflict band — cosine inside `(conflictLow, nearDuplicate]` AND the
|
|
25
|
+
* two memories are related (same logical-key family — version-suffixed
|
|
26
|
+
* variants of one key — or a shared metadata `topics` tag): PENDING
|
|
27
|
+
* `memory_contradicts` (confidence = raw cosine). The metadata blob is
|
|
28
|
+
* parsed defensively — a malformed blob matches nothing.
|
|
29
|
+
*
|
|
30
|
+
* Direction is canonical: the OLDER memory (by `createdAt`; a timestamp
|
|
31
|
+
* tie keeps the existing member, so the just-written memory never flips
|
|
32
|
+
* itself) is `fromMemoryId` — the endpoint confirmation supersedes — and
|
|
33
|
+
* the newer one is `toMemoryId`.
|
|
34
|
+
*/
|
|
35
|
+
import type { EdgeKind, EdgeSource } from "../schema/graph.ts";
|
|
36
|
+
/** One leaf member as seen by the candidate rules. */
|
|
37
|
+
export interface CandidateMember {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly vector: Float32Array;
|
|
40
|
+
readonly createdAt: number;
|
|
41
|
+
readonly logicalKey?: string;
|
|
42
|
+
readonly metadata?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** Which rule produced a candidate (the provenance on `EdgeCandidate`). */
|
|
45
|
+
export type CandidateKind = "near-duplicate" | "conflict-band" | "identity";
|
|
46
|
+
/** One rule-derived edge proposal from `leafLocalCandidates`. */
|
|
47
|
+
export interface EdgeCandidate {
|
|
48
|
+
readonly fromMemoryId: string;
|
|
49
|
+
readonly toMemoryId: string;
|
|
50
|
+
readonly edgeKind: "memory_supersedes" | "memory_contradicts";
|
|
51
|
+
readonly source: "rule";
|
|
52
|
+
readonly status: "pending" | "confirmed";
|
|
53
|
+
readonly confidence: number;
|
|
54
|
+
readonly reason: CandidateKind;
|
|
55
|
+
}
|
|
56
|
+
/** One pending candidate surfaced by `Store.graphCandidates` (an
|
|
57
|
+
* `artifact_edges` row — the status is `pending` by construction). */
|
|
58
|
+
export interface EdgeCandidateInfo {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly fromMemoryId: string;
|
|
61
|
+
readonly toMemoryId: string;
|
|
62
|
+
readonly edgeKind: EdgeKind;
|
|
63
|
+
readonly source: EdgeSource;
|
|
64
|
+
readonly confidence: number | null;
|
|
65
|
+
readonly createdAt: number;
|
|
66
|
+
}
|
|
67
|
+
/** One isolated memory (no live incident edge on either endpoint). */
|
|
68
|
+
export interface IsolatedMemory {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly createdAt: number;
|
|
71
|
+
}
|
|
72
|
+
/** `Store.graphCandidates` result: the newest pending candidates plus the
|
|
73
|
+
* isolated-memory survey (count + oldest examples). */
|
|
74
|
+
export interface GraphCandidatesResult {
|
|
75
|
+
readonly pending: readonly EdgeCandidateInfo[];
|
|
76
|
+
readonly isolatedCount: number;
|
|
77
|
+
readonly oldestIsolated: readonly IsolatedMemory[];
|
|
78
|
+
}
|
|
79
|
+
/** Options for `Store.graphCandidates`. */
|
|
80
|
+
export interface GraphCandidatesOptions {
|
|
81
|
+
/** Max pending candidates returned (clamped to [1, 200]; default 50). */
|
|
82
|
+
readonly limit?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The leaf-local candidate scan: compare the new memory against its
|
|
86
|
+
* leaf-mates (per the module rules) and return at most one candidate per
|
|
87
|
+
* member, in member-id order (deterministic — the input order is
|
|
88
|
+
* irrelevant). The new memory itself is never a candidate target.
|
|
89
|
+
*/
|
|
90
|
+
export declare const leafLocalCandidates: (newId: string, newVector: Float32Array, newCreatedAt: number, newLogicalKey: string | undefined, newMetadata: string | null | undefined, members: readonly CandidateMember[], bands: {
|
|
91
|
+
readonly nearDuplicate: number;
|
|
92
|
+
readonly conflictLow: number;
|
|
93
|
+
}) => readonly EdgeCandidate[];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inductive concept clustering — k-means over memory embeddings, plus the
|
|
3
|
+
* `concept.cluster` job handler that materializes concepts + annotations.
|
|
4
|
+
*
|
|
5
|
+
* ## The clustering pipeline
|
|
6
|
+
*
|
|
7
|
+
* The pure core is a deterministic k-means (>2 groups) over L2-normalized
|
|
8
|
+
* vectors, built from the same primitives as the topic-tree splitter
|
|
9
|
+
* (`topics/tree.ts` — never modified):
|
|
10
|
+
*
|
|
11
|
+
* 1. `farthestPointSeeds` — farthest-first seeding: the first two seeds
|
|
12
|
+
* are the farthest pair, each next seed is the vector farthest from
|
|
13
|
+
* its nearest existing seed.
|
|
14
|
+
* 2. `kGroupLloyd` — nearest-centroid assignment + centroid recomputation,
|
|
15
|
+
* iterated for a fixed number of passes or until a fixpoint.
|
|
16
|
+
* 3. `clusterMembers` — the full pipeline on the content-canonical order
|
|
17
|
+
* (see `canonicalOrder`), so the result is a function of the vector
|
|
18
|
+
* SET, never of the caller's array order. Group assignments are
|
|
19
|
+
* un-permuted back to the caller's index space.
|
|
20
|
+
*
|
|
21
|
+
* A surviving cluster (≥ `clusterMinSize` members) becomes one concept row:
|
|
22
|
+
* content-addressed id `concept_<fnv1a32 of the centroid's floats>`, null
|
|
23
|
+
* name (inductive clusters are anonymous — the name column is nullable), the
|
|
24
|
+
* centroid as embedding, and one `memory_concepts` row per member with
|
|
25
|
+
* confidence = cosine similarity to the centroid.
|
|
26
|
+
*
|
|
27
|
+
* ## Determinism
|
|
28
|
+
*
|
|
29
|
+
* Same DB state + same config ⇒ identical clusters: canonical ordering,
|
|
30
|
+
* index tie-breaks (smaller wins) everywhere, float32 accumulation in
|
|
31
|
+
* canonical order, ids hashed from centroid floats only. Audit timestamps
|
|
32
|
+
* come from the injectable Effect Clock (observability only).
|
|
33
|
+
*
|
|
34
|
+
* ## Dimension assumption
|
|
35
|
+
*
|
|
36
|
+
* The prototype assumes the ACTIVE model is 384-dimensional: the
|
|
37
|
+
* `concepts.embedding` column is a fixed `F32_BLOB(384)`, so centroids from
|
|
38
|
+
* any other dimension cannot be stored. Cluster jobs are only safe while
|
|
39
|
+
* the active model stays 384-dim.
|
|
40
|
+
*/
|
|
41
|
+
import { Effect } from "effect";
|
|
42
|
+
import { JobFailure, type Handler, type JobContext } from "./internal/workflow/index.js";
|
|
43
|
+
import type { EmbeddingModelConfig } from "./internal/config/index.js";
|
|
44
|
+
import { Service as DbBackend } from "../db-backend.ts";
|
|
45
|
+
import type { Interface as DbBackendInterface } from "../db-backend.ts";
|
|
46
|
+
import { StoreError } from "../error.ts";
|
|
47
|
+
import type { GraphConfig } from "../schema/graph.ts";
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic farthest-first seeding for k groups.
|
|
50
|
+
*
|
|
51
|
+
* The first two seeds are `farthestPair`'s result (its contract: `a = 0` is
|
|
52
|
+
* the first vector — callers must pass the CONTENT-CANONICAL order, exactly
|
|
53
|
+
* as `splitPlan` does). Each next seed is the unchosen vector whose minimum
|
|
54
|
+
* dot product against the existing seeds is smallest — the vector farthest
|
|
55
|
+
* from its nearest seed under the normalized-vector invariant. Ties break to
|
|
56
|
+
* the smaller index; already-chosen indices are never re-picked (a seed is
|
|
57
|
+
* distance 0 from itself, so without the exclusion the tie-break could
|
|
58
|
+
* return a seed).
|
|
59
|
+
*
|
|
60
|
+
* `k` clamps to the vector count (an empty input yields `[]`); fewer than 2
|
|
61
|
+
* vectors yield the degenerate `[0]` / `[a, b]` forms of `farthestPair`.
|
|
62
|
+
* Never mutates inputs.
|
|
63
|
+
*/
|
|
64
|
+
export declare const farthestPointSeeds: (vectors: readonly Float32Array[], k: number) => readonly number[];
|
|
65
|
+
/**
|
|
66
|
+
* Deterministic k-group Lloyd refinement: assign every vector to the group
|
|
67
|
+
* with the highest dot product against the group's centroid (initial seeds,
|
|
68
|
+
* then recomputed means), iterating for `passes` iterations. Ties go to the
|
|
69
|
+
* smaller GROUP index (position in `seeds`). Stops early at a fixpoint —
|
|
70
|
+
* further passes are no-ops, so the result is identical either way.
|
|
71
|
+
*
|
|
72
|
+
* Centroid math follows `tree.ts`'s `lloydRefine`: group CF-triplets via
|
|
73
|
+
* `cfFromVectors`, float64 `dot(x, ls) / count` scoring for reassignment,
|
|
74
|
+
* float32 `centroidOf` for the reported centroids. Empty groups score 0 and
|
|
75
|
+
* stay empty (their centroid is the zero vector).
|
|
76
|
+
*
|
|
77
|
+
* Never mutates inputs. `seeds` holds indices into `vectors` (the seed
|
|
78
|
+
* vectors themselves — the initial assignment is nearest-seed, not
|
|
79
|
+
* seed-region).
|
|
80
|
+
*/
|
|
81
|
+
export declare const kGroupLloyd: (vectors: readonly Float32Array[], seeds: readonly number[], passes: number) => {
|
|
82
|
+
groups: Uint32Array;
|
|
83
|
+
centroids: readonly Float32Array[];
|
|
84
|
+
};
|
|
85
|
+
/** Result of {@link clusterMembers}: per-vector group assignment + centroids. */
|
|
86
|
+
export interface ClusterResult {
|
|
87
|
+
/** `groups[i]` = the group of `vectors[i]` (caller index space). */
|
|
88
|
+
readonly groups: Uint32Array;
|
|
89
|
+
/** One centroid per group, in group-index order. */
|
|
90
|
+
readonly centroids: readonly Float32Array[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Deterministic k-means over one vector set. Runs the whole pipeline on the
|
|
94
|
+
* content-canonical order (mirroring `splitPlan`'s v3 determinism): canonical
|
|
95
|
+
* permutation → farthest-first seeds → k-group Lloyd → un-permute the
|
|
96
|
+
* assignment back to the caller's index space. `k` clamps to the vector
|
|
97
|
+
* count. The result is a function of the vector SET — never of the caller's
|
|
98
|
+
* array order. Never mutates inputs.
|
|
99
|
+
*/
|
|
100
|
+
export declare const clusterMembers: (vectors: readonly Float32Array[], k: number, passes: number) => ClusterResult;
|
|
101
|
+
/**
|
|
102
|
+
* Content-addressed concept id: `concept_` + FNV-1a 32-bit hex over the
|
|
103
|
+
* JSON of the centroid's float32 values. The centroid is computed on the
|
|
104
|
+
* canonical order, so identical embeddings yield identical ids across runs
|
|
105
|
+
* and DBs (a hash collision merges two clusters into one concept row — the
|
|
106
|
+
* same 32-bit collision odds as the topic child keys).
|
|
107
|
+
*/
|
|
108
|
+
export declare const clusterConceptId: (centroid: Float32Array) => string;
|
|
109
|
+
/**
|
|
110
|
+
* Cosine similarity of a member vector to its cluster centroid. Vectors are
|
|
111
|
+
* L2-normalized (the tree.ts invariant), so the cosine is
|
|
112
|
+
* `dot(x, c) / |c|`; a zero centroid yields 0. Clamped to [0, 1] — a
|
|
113
|
+
* negative cosine means "opposite direction", not a confident membership.
|
|
114
|
+
*/
|
|
115
|
+
export declare const conceptConfidence: (vector: Float32Array, centroid: Float32Array) => number;
|
|
116
|
+
/** Result of one cluster pass: how many concepts + annotations were written. */
|
|
117
|
+
export interface ClusterWriteResult {
|
|
118
|
+
/** Concepts upserted (clusters that met `clusterMinSize`). */
|
|
119
|
+
readonly concepts: number;
|
|
120
|
+
/** `memory_concepts` rows inserted/refreshed. */
|
|
121
|
+
readonly annotated: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Cluster ONE topic leaf's members into concepts: load the model-scoped
|
|
125
|
+
* memberships, fetch the members' vectors from the model's per-model vector
|
|
126
|
+
* table, cluster, and write the surviving clusters. A topic with no
|
|
127
|
+
* memberships (or no vector rows) writes nothing — the idempotent no-op
|
|
128
|
+
* that makes stale job re-offers harmless.
|
|
129
|
+
*/
|
|
130
|
+
export declare const clusterLeaf: (topicKey: string, model: EmbeddingModelConfig, config: GraphConfig) => Effect.Effect<ClusterWriteResult, StoreError, DbBackend>;
|
|
131
|
+
/**
|
|
132
|
+
* The CORPUS pass: cluster a deterministic sample of the memory store and
|
|
133
|
+
* annotate the sampled memories. The sample is every Nth memory ordered by
|
|
134
|
+
* `memory_id` (N chosen to reach ~5000 samples; N = 1 when the store is
|
|
135
|
+
* smaller), active memories with an embedding only. Embeddings come from the
|
|
136
|
+
* `memories.embedding` column — the ACTIVE model's embeddings, written by
|
|
137
|
+
* the write hook (see the module doc's dimension assumption).
|
|
138
|
+
*/
|
|
139
|
+
export declare const clusterCorpus: (model: EmbeddingModelConfig, config: GraphConfig) => Effect.Effect<ClusterWriteResult, StoreError, DbBackend>;
|
|
140
|
+
/** Dependencies for {@link conceptClusterHandler}. */
|
|
141
|
+
export interface ConceptClusterHandlerDeps {
|
|
142
|
+
/** Resolves a model key to its config (the model registry). */
|
|
143
|
+
readonly getModelConfig: (modelKey: string) => EmbeddingModelConfig | undefined;
|
|
144
|
+
/** Graph config — `clusterK` / `clusterMinSize` drive the clustering. */
|
|
145
|
+
readonly config: GraphConfig;
|
|
146
|
+
/** Backend captured at registration (see {@link topicSplitHandler}). */
|
|
147
|
+
readonly backend?: DbBackendInterface;
|
|
148
|
+
}
|
|
149
|
+
/** The `concept.cluster` handler shape (see jobs.ts). */
|
|
150
|
+
export type ConceptClusterHandler = (payload: unknown, ctx: JobContext) => Effect.Effect<unknown, JobFailure, DbBackend>;
|
|
151
|
+
/**
|
|
152
|
+
* Factory for the `concept.cluster` job handler. Two modes, selected by the
|
|
153
|
+
* payload:
|
|
154
|
+
*
|
|
155
|
+
* - `{ modelKey, topicKey }` — cluster ONE topic leaf's members (per-tree
|
|
156
|
+
* inductive pass).
|
|
157
|
+
* - `{ modelKey }` — the CORPUS pass: cluster a deterministic sample of the
|
|
158
|
+
* memory store.
|
|
159
|
+
*
|
|
160
|
+
* The success schema is `Schema.Void` — the outcome lives in the DB
|
|
161
|
+
* (concepts + `memory_concepts` rows), not the job result. A pass that
|
|
162
|
+
* writes nothing (no members, no clusters meeting `clusterMinSize`) is a
|
|
163
|
+
* success, never an error — the idempotent no-op that makes stale re-offers
|
|
164
|
+
* harmless.
|
|
165
|
+
*/
|
|
166
|
+
export declare function conceptClusterHandler(deps: ConceptClusterHandlerDeps & {
|
|
167
|
+
readonly backend: DbBackendInterface;
|
|
168
|
+
}): Handler;
|
|
169
|
+
export declare function conceptClusterHandler(deps: ConceptClusterHandlerDeps): ConceptClusterHandler;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consolidation dream job — `memory.dream`. One run per model key:
|
|
3
|
+
* gather candidate memory pairs (rule-derived pending edges + a fresh
|
|
4
|
+
* near-duplicate scan over memories created since the watermark), ask the
|
|
5
|
+
* injected LLM classifier to judge them, then persist its relations and
|
|
6
|
+
* derived memories.
|
|
7
|
+
*
|
|
8
|
+
* The store never imports an AI SDK: {@link DreamClassifier} is the injected
|
|
9
|
+
* contract — the app root composes the real implementation from the llm
|
|
10
|
+
* package. The handler fails the job on unknown-model, persistence, AND
|
|
11
|
+
* classifier errors (a silent "success" would hide a classifier outage
|
|
12
|
+
* from the caller; the watermark never advances past an unjudged batch).
|
|
13
|
+
*
|
|
14
|
+
* ## Run flow
|
|
15
|
+
*
|
|
16
|
+
* The run proceeds in stages. It first loads the model's `dream_state`
|
|
17
|
+
* row (the watermark) and counts memories created since it; the gates
|
|
18
|
+
* (`minHours` elapsed since the last dream AND `minNewMemories` new
|
|
19
|
+
* memories) must both pass, else the run returns early with
|
|
20
|
+
* `skipped: true` and a reason. Next it gathers candidate pairs from two
|
|
21
|
+
* sources, deduplicated and bounded by `maxPairs` in deterministic order:
|
|
22
|
+
* pending `artifact_edges` rows written by the write-path rules (status
|
|
23
|
+
* `pending`, source `rule`), and a fresh near-duplicate scan
|
|
24
|
+
* (`leafLocalCandidates`) of each new memory against its topic-leaf
|
|
25
|
+
* mates. Both memories' contents are loaded for every pair. Then it calls
|
|
26
|
+
* `classify(pairs, { maxDerived })` — any classifier error FAILS the job
|
|
27
|
+
* (the watermark stays untouched, so the retried run judges the batch
|
|
28
|
+
* again). Finally it persists
|
|
29
|
+
* relations and derived memories (see below) and advances the watermark
|
|
30
|
+
* in ONE upsert, only after every write succeeded. A persistence error
|
|
31
|
+
* propagates as a job failure — the watermark stays put, so the retried
|
|
32
|
+
* run redoes the writes, which are all check-first idempotent.
|
|
33
|
+
*
|
|
34
|
+
* ## Persistence rules
|
|
35
|
+
*
|
|
36
|
+
* - Relations: an edge is skipped when either endpoint memory is pinned
|
|
37
|
+
* (`MemoryMetadata.pinned`) or no longer exists. `memory_supersedes`
|
|
38
|
+
* with confidence ≥ 0.8 is written CONFIRMED and flips the older
|
|
39
|
+
* (`from`) memory to `superseded` in the same transaction (the shared
|
|
40
|
+
* active→superseded flip + `superseded` event, mirroring the edge API);
|
|
41
|
+
* everything else (`memory_supersedes` below the bar,
|
|
42
|
+
* `memory_contradicts`, `memory_supports`) stays pending. All dream edges
|
|
43
|
+
* carry source `llm`. Source memories are NEVER modified except that
|
|
44
|
+
* supersede flip.
|
|
45
|
+
* - Derived memories: provenance enforcement — at least one declared source
|
|
46
|
+
* must still exist, else the derived memory is skipped. The memory is
|
|
47
|
+
* written content-addressed (`derived:<fnv1a32(content)>`, active-key
|
|
48
|
+
* dedupe makes re-derivation idempotent), its metadata blob records the
|
|
49
|
+
* surviving source ids, and one pending `memory_supports` edge is written
|
|
50
|
+
* from each surviving source.
|
|
51
|
+
*
|
|
52
|
+
* ## Determinism
|
|
53
|
+
*
|
|
54
|
+
* Every iteration derives from persisted state: pending edges by
|
|
55
|
+
* `created_at` then `id`, new memories by `memory_id`, the combined pair
|
|
56
|
+
* set sorted lexicographically, relations/derived processed in the
|
|
57
|
+
* classifier's output order. Audit timestamps come from the injectable
|
|
58
|
+
* Effect Clock (observability only).
|
|
59
|
+
*/
|
|
60
|
+
import { Effect } from "effect";
|
|
61
|
+
import { JobFailure, type Handler, type JobContext } from "./internal/workflow/index.js";
|
|
62
|
+
import type { EmbeddingModelConfig } from "./internal/config/index.js";
|
|
63
|
+
import { Service as DbBackend } from "../db-backend.ts";
|
|
64
|
+
import type { Interface as DbBackendInterface } from "../db-backend.ts";
|
|
65
|
+
/** One memory pair judged by the classifier (older first — the direction
|
|
66
|
+
* the graph edge API expects: the older memory is the supersede target). */
|
|
67
|
+
export interface DreamPair {
|
|
68
|
+
readonly olderId: string;
|
|
69
|
+
readonly newerId: string;
|
|
70
|
+
readonly olderContent: string;
|
|
71
|
+
readonly newerContent: string;
|
|
72
|
+
}
|
|
73
|
+
/** One judged relation between two existing memories. */
|
|
74
|
+
export interface DreamRelation {
|
|
75
|
+
readonly fromMemoryId: string;
|
|
76
|
+
readonly toMemoryId: string;
|
|
77
|
+
readonly edgeKind: "memory_supersedes" | "memory_contradicts" | "memory_supports";
|
|
78
|
+
readonly confidence: number;
|
|
79
|
+
}
|
|
80
|
+
/** One new memory to synthesize from the judged sources. */
|
|
81
|
+
export interface DreamDerived {
|
|
82
|
+
readonly content: string;
|
|
83
|
+
readonly sources: readonly string[];
|
|
84
|
+
}
|
|
85
|
+
/** The classifier's judgement of one pair batch. */
|
|
86
|
+
export interface DreamClassification {
|
|
87
|
+
readonly relations: readonly DreamRelation[];
|
|
88
|
+
readonly derived: readonly DreamDerived[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The injected classifier contract. The store never imports an AI SDK —
|
|
92
|
+
* the app root provides an implementation that calls the model with the
|
|
93
|
+
* pair contents and returns relations + derived memories. Errors are
|
|
94
|
+
* swallowed by the handler (logged, watermark untouched); the channel is
|
|
95
|
+
* `unknown` precisely because the store cannot name the implementation's
|
|
96
|
+
* error type.
|
|
97
|
+
*/
|
|
98
|
+
export interface DreamClassifier {
|
|
99
|
+
readonly classify: (pairs: readonly DreamPair[], opts: {
|
|
100
|
+
readonly maxDerived: number;
|
|
101
|
+
}) => Effect.Effect<DreamClassification, unknown>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Dream settings used when the graph config carries none (the config schema
|
|
105
|
+
* has no dream fields — these local defaults are the source of truth):
|
|
106
|
+
* at least `minHours` between dream runs, at least `minNewMemories` new
|
|
107
|
+
* memories to bother the classifier, at most `maxPairs` judged per run, and
|
|
108
|
+
* at most `maxDerived` synthesized memories per run.
|
|
109
|
+
*/
|
|
110
|
+
export declare const dreamStateDefaults: {
|
|
111
|
+
readonly minHours: 24;
|
|
112
|
+
readonly minNewMemories: 20;
|
|
113
|
+
readonly maxPairs: 200;
|
|
114
|
+
readonly maxDerived: 10;
|
|
115
|
+
};
|
|
116
|
+
/** Outcome of one dream run. The handler returns this value even though the
|
|
117
|
+
* job's success schema is void — the result is for direct callers/tests;
|
|
118
|
+
* the persisted job result stays void (the watermark row is the durable
|
|
119
|
+
* state). */
|
|
120
|
+
export interface DreamOutcome {
|
|
121
|
+
/** True when the gates rejected the run before any work. */
|
|
122
|
+
readonly skipped: boolean;
|
|
123
|
+
/** Skip explanation (gates). */
|
|
124
|
+
readonly reason?: string;
|
|
125
|
+
/** Pairs sent to the classifier. */
|
|
126
|
+
readonly classified: number;
|
|
127
|
+
/** Derived memories actually inserted. */
|
|
128
|
+
readonly created: number;
|
|
129
|
+
/** Memories flipped active → superseded by confirmed supersede edges. */
|
|
130
|
+
readonly superseded: number;
|
|
131
|
+
}
|
|
132
|
+
/** Dependencies for {@link graphDreamHandler}. */
|
|
133
|
+
export interface DreamHandlerDeps {
|
|
134
|
+
/** Resolves a model key to its config (the model registry). */
|
|
135
|
+
readonly getModelConfig: (modelKey: string) => EmbeddingModelConfig | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* The injected classifier that judges candidate pairs. The app root
|
|
138
|
+
* implements it over any LLM (for example `makeDreamClassifier` from
|
|
139
|
+
* `@dimple/llm`); the store never imports an AI SDK. The classifier
|
|
140
|
+
* receives the gathered pairs plus a `maxDerived` budget and must return
|
|
141
|
+
* relations (canonical direction: supersede/contradict edges point
|
|
142
|
+
* old → new) and optional derived memories with provenance sources.
|
|
143
|
+
*/
|
|
144
|
+
readonly classify: DreamClassifier;
|
|
145
|
+
/** Backend captured at registration (see {@link topicSplitHandler}). */
|
|
146
|
+
readonly backend?: DbBackendInterface;
|
|
147
|
+
}
|
|
148
|
+
/** The `memory.dream` handler shape (see jobs.ts). */
|
|
149
|
+
export type DreamHandler = (payload: unknown, ctx: JobContext) => Effect.Effect<unknown, JobFailure, DbBackend>;
|
|
150
|
+
/**
|
|
151
|
+
* Factory for the `memory.dream` job handler. Payload: `{ modelKey }`.
|
|
152
|
+
* Runs the four phases (orient → gather → consolidate → prune) for that
|
|
153
|
+
* model's tree, then advances the model's `dream_state` watermark in ONE
|
|
154
|
+
* upsert — only after every write succeeded.
|
|
155
|
+
*
|
|
156
|
+
* Gates (from `dreamStateDefaults`): the run skips unless at least
|
|
157
|
+
* `minHours` passed since the last dream AND at least `minNewMemories`
|
|
158
|
+
* memories were created since the watermark. A fresh model (no state row)
|
|
159
|
+
* passes both gates trivially.
|
|
160
|
+
*
|
|
161
|
+
* The success schema is `Schema.Void` — the durable outcome lives in the
|
|
162
|
+
* `dream_state` row + the written edges/memories; the returned
|
|
163
|
+
* {@link DreamOutcome} is for direct callers. Skipped and
|
|
164
|
+
* classifier-failed runs are successes, never errors (a skipped run is
|
|
165
|
+
* normal cadence; a failed classifier must not crash the worker or retry
|
|
166
|
+
* forever — the watermark stays put so the batch is judged again next run).
|
|
167
|
+
*/
|
|
168
|
+
export declare function graphDreamHandler(deps: DreamHandlerDeps & {
|
|
169
|
+
readonly backend: DbBackendInterface;
|
|
170
|
+
}): Handler;
|
|
171
|
+
export declare function graphDreamHandler(deps: DreamHandlerDeps): DreamHandler;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const EdgeNotFound_base: Schema.Class<EdgeNotFound, Schema.TaggedStruct<"graph.EdgeNotFound", {
|
|
3
|
+
readonly id: Schema.String;
|
|
4
|
+
}>, import("effect/Cause").YieldableError>;
|
|
5
|
+
/**
|
|
6
|
+
* Graph error types (mirror of `topics/errors.ts`). Each distinct failure
|
|
7
|
+
* mode gets its own `Schema.TaggedErrorClass` so callers can `catchTag`
|
|
8
|
+
* precisely; they ride the public `Store` error channel alongside
|
|
9
|
+
* `StoreError`.
|
|
10
|
+
*/
|
|
11
|
+
/** A status/soft-delete transition targeted an edge id that does not exist. */
|
|
12
|
+
export declare class EdgeNotFound extends EdgeNotFound_base {
|
|
13
|
+
}
|
|
14
|
+
declare const EdgeExists_base: Schema.Class<EdgeExists, Schema.TaggedStruct<"graph.EdgeExists", {
|
|
15
|
+
readonly fromMemoryId: Schema.String;
|
|
16
|
+
readonly toMemoryId: Schema.String;
|
|
17
|
+
readonly edgeKind: Schema.String;
|
|
18
|
+
}>, import("effect/Cause").YieldableError>;
|
|
19
|
+
/**
|
|
20
|
+
* `Store.addEdge` hit the `(from_memory_id, to_memory_id, edge_kind)`
|
|
21
|
+
* unique constraint (the composite PK) — the edge already exists.
|
|
22
|
+
*/
|
|
23
|
+
export declare class EdgeExists extends EdgeExists_base {
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph barrel. The artifact graph's store-level surface: canonical errors
|
|
3
|
+
* (`EdgeNotFound` / `EdgeExists`), the traversal row schema + the sanctioned
|
|
4
|
+
* boundary cast (`fromNeighborRows`), and the graph schema (`artifact_edges`
|
|
5
|
+
* table, the typed `graph_edges` view handle, `GraphConfig`).
|
|
6
|
+
*/
|
|
7
|
+
export * from "./errors.ts";
|
|
8
|
+
export * from "./traversal.ts";
|
|
9
|
+
export * from "./retrieval-post-pass.ts";
|
|
10
|
+
export * from "./concepts.ts";
|
|
11
|
+
export * from "./dream.ts";
|
|
12
|
+
export * from "./candidates.ts";
|
|
13
|
+
export * from "./observability.ts";
|
|
14
|
+
export * from "./repair.ts";
|
|
15
|
+
export * from "../schema/graph.ts";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph coverage — the observability snapshot of the artifact graph's
|
|
3
|
+
* reach over one model's memory population.
|
|
4
|
+
*
|
|
5
|
+
* The memory counts are scoped to the model: "the model's memories" are
|
|
6
|
+
* the active `memories` rows whose `embedding_model` column names the
|
|
7
|
+
* model (set by `Store.updateEmbedding` — the moment a memory joins a
|
|
8
|
+
* model's vector space). Edges and concepts are global tables, so the
|
|
9
|
+
* incident-edge survey and the pending-candidate count are computed over
|
|
10
|
+
* the whole graph, but the denominators (total / covered / isolated) are
|
|
11
|
+
* model-scoped.
|
|
12
|
+
*
|
|
13
|
+
* All reads are plain Drizzle builder queries (one id-column scan per
|
|
14
|
+
* count) plus a single `count(*)` aggregate for the pending-candidate
|
|
15
|
+
* backlog — no raw SQL, no backend methods.
|
|
16
|
+
*/
|
|
17
|
+
import { Effect } from "effect";
|
|
18
|
+
import { Service as DbBackend } from "../db-backend.ts";
|
|
19
|
+
import { StoreError } from "../error.ts";
|
|
20
|
+
/** One coverage snapshot for a model's memory population. */
|
|
21
|
+
export interface GraphCoverage {
|
|
22
|
+
/** Active memories embedded by the model. */
|
|
23
|
+
readonly totalMemories: number;
|
|
24
|
+
/** Of those, with at least one live incident edge (confirmed or pending, not expired). */
|
|
25
|
+
readonly coveredMemories: number;
|
|
26
|
+
/** `coveredMemories / totalMemories` as a percentage, rounded to one decimal (0 when empty). */
|
|
27
|
+
readonly coveragePercent: number;
|
|
28
|
+
/** Pending, non-expired `artifact_edges` rows — the write-path candidate backlog. */
|
|
29
|
+
readonly pendingCandidates: number;
|
|
30
|
+
/** Total concept rows (concepts are global, not per-model). */
|
|
31
|
+
readonly conceptCount: number;
|
|
32
|
+
/** Distinct memories with at least one concept annotation. */
|
|
33
|
+
readonly annotatedMemories: number;
|
|
34
|
+
/** Model memories with no live incident edge (the `graphCandidates` survey mirror). */
|
|
35
|
+
readonly isolatedMemories: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const graphCoverage: (modelKey: string) => Effect.Effect<GraphCoverage, StoreError, DbBackend>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph repair job — `graph.repair`. One pass per model key that
|
|
3
|
+
* sweeps the graph tables for rows that should not exist and removes them.
|
|
4
|
+
*
|
|
5
|
+
* The scan is global (edges, concepts and annotations are not per-model
|
|
6
|
+
* tables); the payload's model key is validated against the model registry
|
|
7
|
+
* so the job's idempotency token (`repair:<modelKey>`) always refers to a
|
|
8
|
+
* real model, exactly like the other graph jobs.
|
|
9
|
+
*
|
|
10
|
+
* ## What the pass fixes
|
|
11
|
+
*
|
|
12
|
+
* 1. ORPHANED EDGES — `artifact_edges` rows whose `from` or `to` memory
|
|
13
|
+
* row is gone. Normally impossible (the schema declares cascade
|
|
14
|
+
* deletes), but a crash window or a foreign-key-disabled database can
|
|
15
|
+
* leave them behind; they are dead weight for traversal and the
|
|
16
|
+
* candidates review. Detected with a double left join against
|
|
17
|
+
* `memories`; deleted outright.
|
|
18
|
+
* 2. STALE CONCEPT ANNOTATIONS — `memory_concepts` rows whose concept row
|
|
19
|
+
* or memory row is gone (the same crash-window class). Deleted by
|
|
20
|
+
* their composite key.
|
|
21
|
+
* 3. EXPIRED EDGES (OPTIONAL) — soft-deleted edges (`expired_at` set)
|
|
22
|
+
* older than a grace window. The soft-delete keeps history around for
|
|
23
|
+
* review; the purge makes it a hard delete. Gated by the handler
|
|
24
|
+
* option `includePurgeExpired` (default false) — the conservative
|
|
25
|
+
* default keeps the pass read-only on edge history.
|
|
26
|
+
*
|
|
27
|
+
* ## Logging and idempotence
|
|
28
|
+
*
|
|
29
|
+
* Every fix is recorded as a `memory_events` row with event type
|
|
30
|
+
* `graph_repair` and a JSON `reason` describing the action — attached to
|
|
31
|
+
* a surviving endpoint memory where one exists. An orphan whose BOTH
|
|
32
|
+
* endpoints are gone has no surviving memory to attach an event to (the
|
|
33
|
+
* events table's memory id is a foreign key to `memories`), so those
|
|
34
|
+
* deletions are the one case that is not logged — the row itself was the
|
|
35
|
+
* only trace. All reads and writes run in ONE transaction, so a
|
|
36
|
+
* mid-pass failure rolls back every fix and the counts; a re-run then
|
|
37
|
+
* finds the same state and repeats cleanly (idempotent — the second run
|
|
38
|
+
* simply finds nothing to fix).
|
|
39
|
+
*/
|
|
40
|
+
import { Effect } from "effect";
|
|
41
|
+
import { JobFailure, type Handler, type JobContext } from "./internal/workflow/index.js";
|
|
42
|
+
import type { EmbeddingModelConfig } from "./internal/config/index.js";
|
|
43
|
+
import { Service as DbBackend } from "../db-backend.ts";
|
|
44
|
+
import type { Interface as DbBackendInterface } from "../db-backend.ts";
|
|
45
|
+
/** How many rows of each class one repair pass fixed. */
|
|
46
|
+
export interface RepairResult {
|
|
47
|
+
/** `artifact_edges` rows whose endpoint memory was gone. */
|
|
48
|
+
readonly orphanedEdges: number;
|
|
49
|
+
/** `memory_concepts` rows whose concept or memory was gone. */
|
|
50
|
+
readonly staleAnnotations: number;
|
|
51
|
+
/** Soft-deleted edges hard-purged past the grace window (only when enabled). */
|
|
52
|
+
readonly expiredEdges: number;
|
|
53
|
+
}
|
|
54
|
+
/** Dependencies for {@link graphRepairHandler}. */
|
|
55
|
+
export interface GraphRepairHandlerDeps {
|
|
56
|
+
/** Resolves a model key to its config (the model registry). */
|
|
57
|
+
readonly getModelConfig: (modelKey: string) => EmbeddingModelConfig | undefined;
|
|
58
|
+
/** Backend captured at registration (see {@link topicSplitHandler}). */
|
|
59
|
+
readonly backend?: DbBackendInterface;
|
|
60
|
+
/** When true, hard-purge soft-deleted edges older than `expiredGraceMs`.
|
|
61
|
+
* Default false — the expired sweep is skipped entirely. */
|
|
62
|
+
readonly includePurgeExpired?: boolean;
|
|
63
|
+
/** Grace window for the expired purge in ms (default one week). */
|
|
64
|
+
readonly expiredGraceMs?: number;
|
|
65
|
+
}
|
|
66
|
+
/** The `graph.repair` handler shape (see jobs.ts). */
|
|
67
|
+
export type GraphRepairHandler = (payload: unknown, ctx: JobContext) => Effect.Effect<unknown, JobFailure, DbBackend>;
|
|
68
|
+
/**
|
|
69
|
+
* Factory for the `graph.repair` job handler. Payload: `{ modelKey }`.
|
|
70
|
+
* Validates the model key, then runs the repair transaction: orphaned
|
|
71
|
+
* `artifact_edges` (endpoints missing — normally impossible via FK
|
|
72
|
+
* cascade, the backstop for crash windows), stale `memory_concepts`
|
|
73
|
+
* annotations, and — only when `includePurgeExpired` is set — hard-purge
|
|
74
|
+
* of soft-deleted edges older than `expiredGraceMs`. Every fix is logged
|
|
75
|
+
* to `memory_events` (`event_type = "graph_repair"`). Idempotent: a
|
|
76
|
+
* second pass finds nothing and writes nothing.
|
|
77
|
+
*
|
|
78
|
+
* The success schema is `Schema.Void` — the durable outcome is the
|
|
79
|
+
* deleted rows + the `graph_repair` event log; the returned
|
|
80
|
+
* {@link RepairResult} is for direct callers/tests.
|
|
81
|
+
*/
|
|
82
|
+
export declare function graphRepairHandler(deps: GraphRepairHandlerDeps & {
|
|
83
|
+
readonly backend: DbBackendInterface;
|
|
84
|
+
}): Handler;
|
|
85
|
+
export declare function graphRepairHandler(deps: GraphRepairHandlerDeps): GraphRepairHandler;
|