@equationalapplications/core-llm-wiki 4.23.0 → 5.0.0
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/{chunk-MYZJLVX4.mjs → chunk-W53E3I44.mjs} +75 -13
- package/dist/chunk-W53E3I44.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +173 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -12
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-CBjAuTSl.d.mts → testing-DCRNie7k.d.mts} +118 -10
- package/dist/{testing-CBjAuTSl.d.ts → testing-DCRNie7k.d.ts} +118 -10
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +72 -10
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-MYZJLVX4.mjs.map +0 -1
|
@@ -100,6 +100,39 @@ interface OntologyBackfillResult {
|
|
|
100
100
|
* Stamped with the recheck cooldown, so they reappear as `deferred` next pass. */
|
|
101
101
|
skipped: number;
|
|
102
102
|
}
|
|
103
|
+
/** Result of a single heal run. */
|
|
104
|
+
interface HealResult {
|
|
105
|
+
/**
|
|
106
|
+
* Heal candidates sent to the model this run. Counts candidates whose batch
|
|
107
|
+
* came back unusable and landed in `skipped` as well — they reached the
|
|
108
|
+
* provider too, so this is provider exposure, not successful throughput.
|
|
109
|
+
*/
|
|
110
|
+
scanned: number;
|
|
111
|
+
/**
|
|
112
|
+
* Facts whose confidence was lowered to `tentative` — the stale-inferred SQL
|
|
113
|
+
* pass (`inferred` → `tentative`) plus any model-directed downgrades, deduped.
|
|
114
|
+
*/
|
|
115
|
+
downgraded: number;
|
|
116
|
+
/** Facts soft-deleted — the orphan-marking SQL pass plus model-directed deletes, deduped. */
|
|
117
|
+
deleted: number;
|
|
118
|
+
/** New facts synthesized by heal. */
|
|
119
|
+
newFactsCreated: number;
|
|
120
|
+
/** Candidates a batch could not process even alone, skipped so the pass could finish. */
|
|
121
|
+
skipped: number;
|
|
122
|
+
/**
|
|
123
|
+
* Heal candidates still eligible after this run — convergence signal: loop while > 0.
|
|
124
|
+
*
|
|
125
|
+
* This does NOT mean what it means for OntologyBackfillResult. Untyped facts are
|
|
126
|
+
* a finite backlog that drains permanently; heal's candidates are the live mutable
|
|
127
|
+
* corpus. `remaining === 0` means "every mutable fact is inside the recheck
|
|
128
|
+
* cooldown", not "there is no more work" — it climbs back as the cooldown lapses
|
|
129
|
+
* and as new facts are written. A host loop terminates, but it converges to
|
|
130
|
+
* "corpus swept once this cooldown window", not to a drained queue.
|
|
131
|
+
*/
|
|
132
|
+
remaining: number;
|
|
133
|
+
/** Heal candidates inside the recheck cooldown. */
|
|
134
|
+
deferred: number;
|
|
135
|
+
}
|
|
103
136
|
interface WikiConfig {
|
|
104
137
|
/**
|
|
105
138
|
* Prefix applied to every SQL table/index/trigger name. Must match
|
|
@@ -664,11 +697,14 @@ declare class EntryRepository extends BaseRepository {
|
|
|
664
697
|
findAllByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
665
698
|
/**
|
|
666
699
|
* Fetch live, mutable entries for an entity — everything heal is allowed to
|
|
667
|
-
* downgrade or delete
|
|
668
|
-
*
|
|
669
|
-
*
|
|
700
|
+
* downgrade or delete — oldest first, capped at `limit`, skipping anything
|
|
701
|
+
* stamped inside the recheck cooldown (heal_checked_at > recheckCutoff).
|
|
702
|
+
*
|
|
703
|
+
* Oldest-first rather than newest-first: with a cooldown in place, newest-first
|
|
704
|
+
* would keep re-selecting recently-touched facts every pass while older ones
|
|
705
|
+
* wait indefinitely to even enter a batch.
|
|
670
706
|
*/
|
|
671
|
-
findHealCandidatesByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
707
|
+
findHealCandidatesByEntityId(entityId: string, limit: number, recheckCutoff: number, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
672
708
|
/**
|
|
673
709
|
* Resolve search hits to document anchors. The MiniSearch index holds every
|
|
674
710
|
* fact, not only immutable_document rows, so the source-type restriction has
|
|
@@ -719,8 +755,12 @@ declare class EntryRepository extends BaseRepository {
|
|
|
719
755
|
/**
|
|
720
756
|
* Downgrade stale inferred entries to 'tentative'.
|
|
721
757
|
* Used by MaintenanceService.doRunHeal().
|
|
758
|
+
*
|
|
759
|
+
* Returns the ids it downgraded so the caller can fold them into
|
|
760
|
+
* {@link HealResult.downgraded} without double-counting a fact the model also
|
|
761
|
+
* downgraded in the same pass.
|
|
722
762
|
*/
|
|
723
|
-
downgradeStaleInferred(entityId: string, staleThreshold: number, tx: SQLiteAdapter): Promise<
|
|
763
|
+
downgradeStaleInferred(entityId: string, staleThreshold: number, tx: SQLiteAdapter): Promise<string[]>;
|
|
724
764
|
/**
|
|
725
765
|
* Downgrade specific entries to 'tentative' by IDs.
|
|
726
766
|
* Used by MaintenanceService.doRunHeal().
|
|
@@ -804,6 +844,34 @@ declare class EntryRepository extends BaseRepository {
|
|
|
804
844
|
title: string;
|
|
805
845
|
okf_type: string | null;
|
|
806
846
|
}>>;
|
|
847
|
+
/** Counts live mutable facts: eligible (past cooldown) vs deferred (in cooldown). */
|
|
848
|
+
countHealCandidatesByEntityId(entityId: string, recheckCutoff: number, tx?: SQLiteAdapter): Promise<{
|
|
849
|
+
eligible: number;
|
|
850
|
+
deferred: number;
|
|
851
|
+
}>;
|
|
852
|
+
/**
|
|
853
|
+
* Stamps the heal recheck cooldown. NEVER touches updated_at — import merge
|
|
854
|
+
* resolution is last-write-wins on updated_at and a bump here would make an
|
|
855
|
+
* unchanged local fact beat a genuinely newer remote edit.
|
|
856
|
+
*
|
|
857
|
+
* Soft-deleted rows are skipped: a candidate heal deleted earlier in the same
|
|
858
|
+
* transaction is no longer a candidate under any future pass, so its cooldown
|
|
859
|
+
* value is irrelevant. This means the stamped count can be lower than the
|
|
860
|
+
* offered-candidate count.
|
|
861
|
+
*/
|
|
862
|
+
markHealChecked(ids: string[], entityId: string, now: number, tx: SQLiteAdapter): Promise<void>;
|
|
863
|
+
/**
|
|
864
|
+
* Lightweight full-breadth index (id, title) over all live librarian_inferred
|
|
865
|
+
* facts. This is heal's fuzzy-dedupe corpus, deliberately kept independent of
|
|
866
|
+
* the bounded candidate window: seeding dedupe from a batchSize-limited read
|
|
867
|
+
* would let a synthesized fact duplicating a fact outside the window pass the
|
|
868
|
+
* Jaccard check, and a convergence loop would multiply those duplicates across
|
|
869
|
+
* passes. Mirrors findTitleIndexByEntityId — full breadth, two columns, cheap.
|
|
870
|
+
*/
|
|
871
|
+
findInferredTitlesByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<Array<{
|
|
872
|
+
id: string;
|
|
873
|
+
title: string;
|
|
874
|
+
}>>;
|
|
807
875
|
}
|
|
808
876
|
|
|
809
877
|
declare class MetadataRepository extends BaseRepository {
|
|
@@ -1203,6 +1271,15 @@ declare class EventRepository extends BaseRepository {
|
|
|
1203
1271
|
declare const ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1204
1272
|
declare const ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 40000;
|
|
1205
1273
|
declare const ONTOLOGY_BACKFILL_RECHECK_MS: number;
|
|
1274
|
+
/**
|
|
1275
|
+
* Heal candidates fetched per pass. Bounds the pool runBatched draws from, not
|
|
1276
|
+
* the size of an individual LLM call — runBatched still packs ~10 candidates per
|
|
1277
|
+
* request and splits further on failure. At this default one pass costs roughly
|
|
1278
|
+
* 2-3 provider calls instead of an unbounded count (#67).
|
|
1279
|
+
*/
|
|
1280
|
+
declare const HEAL_BATCH_SIZE = 25;
|
|
1281
|
+
/** Cooldown before an already-healed fact is offered again. Matches ontology backfill. */
|
|
1282
|
+
declare const HEAL_RECHECK_MS: number;
|
|
1206
1283
|
declare class MaintenanceService {
|
|
1207
1284
|
private db;
|
|
1208
1285
|
private prefix;
|
|
@@ -1231,7 +1308,8 @@ declare class MaintenanceService {
|
|
|
1231
1308
|
}): Promise<void>;
|
|
1232
1309
|
runHeal(entityId: string, options?: {
|
|
1233
1310
|
promptOverride?: string;
|
|
1234
|
-
|
|
1311
|
+
batchSize?: number;
|
|
1312
|
+
}): Promise<HealResult>;
|
|
1235
1313
|
runOntologyBackfill(entityId: string, options?: {
|
|
1236
1314
|
promptOverride?: string;
|
|
1237
1315
|
batchSize?: number;
|
|
@@ -1258,8 +1336,17 @@ declare class MaintenanceService {
|
|
|
1258
1336
|
}>;
|
|
1259
1337
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
1260
1338
|
doRunLibrarian(entityId: string, promptOverride?: string): Promise<void>;
|
|
1261
|
-
/**
|
|
1262
|
-
|
|
1339
|
+
/**
|
|
1340
|
+
* Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook.
|
|
1341
|
+
*
|
|
1342
|
+
* Bounded: at most `batchSize` candidates per pass (#67). Loop on
|
|
1343
|
+
* `result.remaining > 0` for convergence — see {@link HealResult.remaining}
|
|
1344
|
+
* for what convergence means here.
|
|
1345
|
+
*/
|
|
1346
|
+
doRunHeal(entityId: string, options?: {
|
|
1347
|
+
promptOverride?: string;
|
|
1348
|
+
batchSize?: number;
|
|
1349
|
+
}): Promise<HealResult>;
|
|
1263
1350
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1264
1351
|
doRunOntologyBackfill(entityId: string, options?: {
|
|
1265
1352
|
promptOverride?: string;
|
|
@@ -1371,6 +1458,12 @@ declare class WriteService {
|
|
|
1371
1458
|
constructor(db: SQLiteAdapter, options: WikiOptions, entryRepo: EntryRepository, eventRepo: EventRepository, metadataRepo: MetadataRepository, jobManager: JobManager, maintenanceService: MaintenanceService);
|
|
1372
1459
|
write(entityId: string, event: Omit<WikiEvent, 'id' | 'entity_id' | 'created_at'>): Promise<void>;
|
|
1373
1460
|
private runLibrarianThenMaybeHeal;
|
|
1461
|
+
/**
|
|
1462
|
+
* Run one bounded auto-heal pass if the heal checkpoint has fallen
|
|
1463
|
+
* `autoHealThreshold` events behind. Called after every write (see
|
|
1464
|
+
* {@link write}) so a partial pass retries on the next write.
|
|
1465
|
+
*/
|
|
1466
|
+
private maybeRunHeal;
|
|
1374
1467
|
}
|
|
1375
1468
|
|
|
1376
1469
|
/**
|
|
@@ -1452,13 +1545,28 @@ declare class WikiMemory {
|
|
|
1452
1545
|
promptOverride?: string;
|
|
1453
1546
|
}): Promise<void>;
|
|
1454
1547
|
/**
|
|
1548
|
+
* Reviews stored facts with the LLM: removes orphans, downgrades stale
|
|
1549
|
+
* inferences, synthesizes corrections.
|
|
1550
|
+
*
|
|
1551
|
+
* Bounded per call: covers at most `batchSize` candidates (default 25), so
|
|
1552
|
+
* one call no longer sweeps the whole entity. Hosts own the cadence; loop
|
|
1553
|
+
* `while (result.remaining > 0)` for convergence.
|
|
1554
|
+
*
|
|
1555
|
+
* `remaining === 0` means "every mutable fact is inside the 7-day recheck
|
|
1556
|
+
* cooldown", not "there is no more work" — heal's candidate set is the live
|
|
1557
|
+
* mutable corpus, not a draining backlog, so it climbs back as the cooldown
|
|
1558
|
+
* lapses and as new facts are written. Do not write a loop expecting a drain.
|
|
1559
|
+
*
|
|
1455
1560
|
* @param options.promptOverride - Applies only to this manual call. Does NOT affect
|
|
1456
1561
|
* WriteService-triggered auto-runs. For persistent prompt customization across auto-runs,
|
|
1457
1562
|
* set `options.config.prompts.healSystemPrompt` at WikiMemory construction time.
|
|
1563
|
+
* @param options.batchSize - Candidates per run (default 25) for providers with
|
|
1564
|
+
* tighter context limits.
|
|
1458
1565
|
*/
|
|
1459
1566
|
runHeal(entityId: string, options?: {
|
|
1460
1567
|
promptOverride?: string;
|
|
1461
|
-
|
|
1568
|
+
batchSize?: number;
|
|
1569
|
+
}): Promise<HealResult>;
|
|
1462
1570
|
/**
|
|
1463
1571
|
* Types already-persisted untyped facts (okf_type IS NULL) in place via one
|
|
1464
1572
|
* librarian-style LLM call. Strictly additive: never creates, deletes, or
|
|
@@ -1552,4 +1660,4 @@ declare class WikiMemory {
|
|
|
1552
1660
|
}): Promise<void>;
|
|
1553
1661
|
}
|
|
1554
1662
|
|
|
1555
|
-
export {
|
|
1663
|
+
export { MaintenanceService as $, WikiBusyError as A, type WikiBusyOperation as B, type WikiCheckpoint as C, type WikiConfig as D, type EntityStatus as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type WikiEdge as I, type WikiEvent as J, type WikiFact as K, type LLMProvider as L, type MemoryBundle as M, type WikiMemoryTestAccess as N, type OntologyManifest as O, type PromptOverrides as P, type WikiOutboxEvent as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiTask as T, WikiTransactionError as U, type VectorRanker as V, type WikiOptions as W, EmbeddingService as X, ImportExportService as Y, IngestionService as Z, JobManager as _, type MemoryDump as a, RetrievalService as a0, SearchService as a1, WriteService as a2, type FormattedMemoryDump as b, WikiMemory as c, type ExtractedFact as d, type ExtractedFactEdge as e, type ExtractedFactWithOntology as f, type ExtractedTask as g, type GraphTraversalOptions as h, HEAL_RECHECK_MS as i, HOOK_TIMEOUT_MARKER as j, type HealResult as k, ONTOLOGY_BACKFILL_BATCH_SIZE as l, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as m, ONTOLOGY_BACKFILL_RECHECK_MS as n, type OntologyBackfillResult as o, type OntologyConfig as p, type OntologyEdgeType as q, type OntologyMode as r, type OntologyNodeType as s, type OntologyPromptContext as t, type OntologyUpdates as u, PromptService as v, PrunePartialFailureError as w, type VectorRankerFallback as x, type VectorRankerRankArgs as y, type VectorRankerSemanticResult as z };
|
|
@@ -100,6 +100,39 @@ interface OntologyBackfillResult {
|
|
|
100
100
|
* Stamped with the recheck cooldown, so they reappear as `deferred` next pass. */
|
|
101
101
|
skipped: number;
|
|
102
102
|
}
|
|
103
|
+
/** Result of a single heal run. */
|
|
104
|
+
interface HealResult {
|
|
105
|
+
/**
|
|
106
|
+
* Heal candidates sent to the model this run. Counts candidates whose batch
|
|
107
|
+
* came back unusable and landed in `skipped` as well — they reached the
|
|
108
|
+
* provider too, so this is provider exposure, not successful throughput.
|
|
109
|
+
*/
|
|
110
|
+
scanned: number;
|
|
111
|
+
/**
|
|
112
|
+
* Facts whose confidence was lowered to `tentative` — the stale-inferred SQL
|
|
113
|
+
* pass (`inferred` → `tentative`) plus any model-directed downgrades, deduped.
|
|
114
|
+
*/
|
|
115
|
+
downgraded: number;
|
|
116
|
+
/** Facts soft-deleted — the orphan-marking SQL pass plus model-directed deletes, deduped. */
|
|
117
|
+
deleted: number;
|
|
118
|
+
/** New facts synthesized by heal. */
|
|
119
|
+
newFactsCreated: number;
|
|
120
|
+
/** Candidates a batch could not process even alone, skipped so the pass could finish. */
|
|
121
|
+
skipped: number;
|
|
122
|
+
/**
|
|
123
|
+
* Heal candidates still eligible after this run — convergence signal: loop while > 0.
|
|
124
|
+
*
|
|
125
|
+
* This does NOT mean what it means for OntologyBackfillResult. Untyped facts are
|
|
126
|
+
* a finite backlog that drains permanently; heal's candidates are the live mutable
|
|
127
|
+
* corpus. `remaining === 0` means "every mutable fact is inside the recheck
|
|
128
|
+
* cooldown", not "there is no more work" — it climbs back as the cooldown lapses
|
|
129
|
+
* and as new facts are written. A host loop terminates, but it converges to
|
|
130
|
+
* "corpus swept once this cooldown window", not to a drained queue.
|
|
131
|
+
*/
|
|
132
|
+
remaining: number;
|
|
133
|
+
/** Heal candidates inside the recheck cooldown. */
|
|
134
|
+
deferred: number;
|
|
135
|
+
}
|
|
103
136
|
interface WikiConfig {
|
|
104
137
|
/**
|
|
105
138
|
* Prefix applied to every SQL table/index/trigger name. Must match
|
|
@@ -664,11 +697,14 @@ declare class EntryRepository extends BaseRepository {
|
|
|
664
697
|
findAllByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
665
698
|
/**
|
|
666
699
|
* Fetch live, mutable entries for an entity — everything heal is allowed to
|
|
667
|
-
* downgrade or delete
|
|
668
|
-
*
|
|
669
|
-
*
|
|
700
|
+
* downgrade or delete — oldest first, capped at `limit`, skipping anything
|
|
701
|
+
* stamped inside the recheck cooldown (heal_checked_at > recheckCutoff).
|
|
702
|
+
*
|
|
703
|
+
* Oldest-first rather than newest-first: with a cooldown in place, newest-first
|
|
704
|
+
* would keep re-selecting recently-touched facts every pass while older ones
|
|
705
|
+
* wait indefinitely to even enter a batch.
|
|
670
706
|
*/
|
|
671
|
-
findHealCandidatesByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
707
|
+
findHealCandidatesByEntityId(entityId: string, limit: number, recheckCutoff: number, tx?: SQLiteAdapter): Promise<WikiFact[]>;
|
|
672
708
|
/**
|
|
673
709
|
* Resolve search hits to document anchors. The MiniSearch index holds every
|
|
674
710
|
* fact, not only immutable_document rows, so the source-type restriction has
|
|
@@ -719,8 +755,12 @@ declare class EntryRepository extends BaseRepository {
|
|
|
719
755
|
/**
|
|
720
756
|
* Downgrade stale inferred entries to 'tentative'.
|
|
721
757
|
* Used by MaintenanceService.doRunHeal().
|
|
758
|
+
*
|
|
759
|
+
* Returns the ids it downgraded so the caller can fold them into
|
|
760
|
+
* {@link HealResult.downgraded} without double-counting a fact the model also
|
|
761
|
+
* downgraded in the same pass.
|
|
722
762
|
*/
|
|
723
|
-
downgradeStaleInferred(entityId: string, staleThreshold: number, tx: SQLiteAdapter): Promise<
|
|
763
|
+
downgradeStaleInferred(entityId: string, staleThreshold: number, tx: SQLiteAdapter): Promise<string[]>;
|
|
724
764
|
/**
|
|
725
765
|
* Downgrade specific entries to 'tentative' by IDs.
|
|
726
766
|
* Used by MaintenanceService.doRunHeal().
|
|
@@ -804,6 +844,34 @@ declare class EntryRepository extends BaseRepository {
|
|
|
804
844
|
title: string;
|
|
805
845
|
okf_type: string | null;
|
|
806
846
|
}>>;
|
|
847
|
+
/** Counts live mutable facts: eligible (past cooldown) vs deferred (in cooldown). */
|
|
848
|
+
countHealCandidatesByEntityId(entityId: string, recheckCutoff: number, tx?: SQLiteAdapter): Promise<{
|
|
849
|
+
eligible: number;
|
|
850
|
+
deferred: number;
|
|
851
|
+
}>;
|
|
852
|
+
/**
|
|
853
|
+
* Stamps the heal recheck cooldown. NEVER touches updated_at — import merge
|
|
854
|
+
* resolution is last-write-wins on updated_at and a bump here would make an
|
|
855
|
+
* unchanged local fact beat a genuinely newer remote edit.
|
|
856
|
+
*
|
|
857
|
+
* Soft-deleted rows are skipped: a candidate heal deleted earlier in the same
|
|
858
|
+
* transaction is no longer a candidate under any future pass, so its cooldown
|
|
859
|
+
* value is irrelevant. This means the stamped count can be lower than the
|
|
860
|
+
* offered-candidate count.
|
|
861
|
+
*/
|
|
862
|
+
markHealChecked(ids: string[], entityId: string, now: number, tx: SQLiteAdapter): Promise<void>;
|
|
863
|
+
/**
|
|
864
|
+
* Lightweight full-breadth index (id, title) over all live librarian_inferred
|
|
865
|
+
* facts. This is heal's fuzzy-dedupe corpus, deliberately kept independent of
|
|
866
|
+
* the bounded candidate window: seeding dedupe from a batchSize-limited read
|
|
867
|
+
* would let a synthesized fact duplicating a fact outside the window pass the
|
|
868
|
+
* Jaccard check, and a convergence loop would multiply those duplicates across
|
|
869
|
+
* passes. Mirrors findTitleIndexByEntityId — full breadth, two columns, cheap.
|
|
870
|
+
*/
|
|
871
|
+
findInferredTitlesByEntityId(entityId: string, tx?: SQLiteAdapter): Promise<Array<{
|
|
872
|
+
id: string;
|
|
873
|
+
title: string;
|
|
874
|
+
}>>;
|
|
807
875
|
}
|
|
808
876
|
|
|
809
877
|
declare class MetadataRepository extends BaseRepository {
|
|
@@ -1203,6 +1271,15 @@ declare class EventRepository extends BaseRepository {
|
|
|
1203
1271
|
declare const ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1204
1272
|
declare const ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 40000;
|
|
1205
1273
|
declare const ONTOLOGY_BACKFILL_RECHECK_MS: number;
|
|
1274
|
+
/**
|
|
1275
|
+
* Heal candidates fetched per pass. Bounds the pool runBatched draws from, not
|
|
1276
|
+
* the size of an individual LLM call — runBatched still packs ~10 candidates per
|
|
1277
|
+
* request and splits further on failure. At this default one pass costs roughly
|
|
1278
|
+
* 2-3 provider calls instead of an unbounded count (#67).
|
|
1279
|
+
*/
|
|
1280
|
+
declare const HEAL_BATCH_SIZE = 25;
|
|
1281
|
+
/** Cooldown before an already-healed fact is offered again. Matches ontology backfill. */
|
|
1282
|
+
declare const HEAL_RECHECK_MS: number;
|
|
1206
1283
|
declare class MaintenanceService {
|
|
1207
1284
|
private db;
|
|
1208
1285
|
private prefix;
|
|
@@ -1231,7 +1308,8 @@ declare class MaintenanceService {
|
|
|
1231
1308
|
}): Promise<void>;
|
|
1232
1309
|
runHeal(entityId: string, options?: {
|
|
1233
1310
|
promptOverride?: string;
|
|
1234
|
-
|
|
1311
|
+
batchSize?: number;
|
|
1312
|
+
}): Promise<HealResult>;
|
|
1235
1313
|
runOntologyBackfill(entityId: string, options?: {
|
|
1236
1314
|
promptOverride?: string;
|
|
1237
1315
|
batchSize?: number;
|
|
@@ -1258,8 +1336,17 @@ declare class MaintenanceService {
|
|
|
1258
1336
|
}>;
|
|
1259
1337
|
/** Core librarian pass (locks handled by {@link runLibrarian}). Package-internal orchestration hook. */
|
|
1260
1338
|
doRunLibrarian(entityId: string, promptOverride?: string): Promise<void>;
|
|
1261
|
-
/**
|
|
1262
|
-
|
|
1339
|
+
/**
|
|
1340
|
+
* Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook.
|
|
1341
|
+
*
|
|
1342
|
+
* Bounded: at most `batchSize` candidates per pass (#67). Loop on
|
|
1343
|
+
* `result.remaining > 0` for convergence — see {@link HealResult.remaining}
|
|
1344
|
+
* for what convergence means here.
|
|
1345
|
+
*/
|
|
1346
|
+
doRunHeal(entityId: string, options?: {
|
|
1347
|
+
promptOverride?: string;
|
|
1348
|
+
batchSize?: number;
|
|
1349
|
+
}): Promise<HealResult>;
|
|
1263
1350
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1264
1351
|
doRunOntologyBackfill(entityId: string, options?: {
|
|
1265
1352
|
promptOverride?: string;
|
|
@@ -1371,6 +1458,12 @@ declare class WriteService {
|
|
|
1371
1458
|
constructor(db: SQLiteAdapter, options: WikiOptions, entryRepo: EntryRepository, eventRepo: EventRepository, metadataRepo: MetadataRepository, jobManager: JobManager, maintenanceService: MaintenanceService);
|
|
1372
1459
|
write(entityId: string, event: Omit<WikiEvent, 'id' | 'entity_id' | 'created_at'>): Promise<void>;
|
|
1373
1460
|
private runLibrarianThenMaybeHeal;
|
|
1461
|
+
/**
|
|
1462
|
+
* Run one bounded auto-heal pass if the heal checkpoint has fallen
|
|
1463
|
+
* `autoHealThreshold` events behind. Called after every write (see
|
|
1464
|
+
* {@link write}) so a partial pass retries on the next write.
|
|
1465
|
+
*/
|
|
1466
|
+
private maybeRunHeal;
|
|
1374
1467
|
}
|
|
1375
1468
|
|
|
1376
1469
|
/**
|
|
@@ -1452,13 +1545,28 @@ declare class WikiMemory {
|
|
|
1452
1545
|
promptOverride?: string;
|
|
1453
1546
|
}): Promise<void>;
|
|
1454
1547
|
/**
|
|
1548
|
+
* Reviews stored facts with the LLM: removes orphans, downgrades stale
|
|
1549
|
+
* inferences, synthesizes corrections.
|
|
1550
|
+
*
|
|
1551
|
+
* Bounded per call: covers at most `batchSize` candidates (default 25), so
|
|
1552
|
+
* one call no longer sweeps the whole entity. Hosts own the cadence; loop
|
|
1553
|
+
* `while (result.remaining > 0)` for convergence.
|
|
1554
|
+
*
|
|
1555
|
+
* `remaining === 0` means "every mutable fact is inside the 7-day recheck
|
|
1556
|
+
* cooldown", not "there is no more work" — heal's candidate set is the live
|
|
1557
|
+
* mutable corpus, not a draining backlog, so it climbs back as the cooldown
|
|
1558
|
+
* lapses and as new facts are written. Do not write a loop expecting a drain.
|
|
1559
|
+
*
|
|
1455
1560
|
* @param options.promptOverride - Applies only to this manual call. Does NOT affect
|
|
1456
1561
|
* WriteService-triggered auto-runs. For persistent prompt customization across auto-runs,
|
|
1457
1562
|
* set `options.config.prompts.healSystemPrompt` at WikiMemory construction time.
|
|
1563
|
+
* @param options.batchSize - Candidates per run (default 25) for providers with
|
|
1564
|
+
* tighter context limits.
|
|
1458
1565
|
*/
|
|
1459
1566
|
runHeal(entityId: string, options?: {
|
|
1460
1567
|
promptOverride?: string;
|
|
1461
|
-
|
|
1568
|
+
batchSize?: number;
|
|
1569
|
+
}): Promise<HealResult>;
|
|
1462
1570
|
/**
|
|
1463
1571
|
* Types already-persisted untyped facts (okf_type IS NULL) in place via one
|
|
1464
1572
|
* librarian-style LLM call. Strictly additive: never creates, deletes, or
|
|
@@ -1552,4 +1660,4 @@ declare class WikiMemory {
|
|
|
1552
1660
|
}): Promise<void>;
|
|
1553
1661
|
}
|
|
1554
1662
|
|
|
1555
|
-
export {
|
|
1663
|
+
export { MaintenanceService as $, WikiBusyError as A, type WikiBusyOperation as B, type WikiCheckpoint as C, type WikiConfig as D, type EntityStatus as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type WikiEdge as I, type WikiEvent as J, type WikiFact as K, type LLMProvider as L, type MemoryBundle as M, type WikiMemoryTestAccess as N, type OntologyManifest as O, type PromptOverrides as P, type WikiOutboxEvent as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiTask as T, WikiTransactionError as U, type VectorRanker as V, type WikiOptions as W, EmbeddingService as X, ImportExportService as Y, IngestionService as Z, JobManager as _, type MemoryDump as a, RetrievalService as a0, SearchService as a1, WriteService as a2, type FormattedMemoryDump as b, WikiMemory as c, type ExtractedFact as d, type ExtractedFactEdge as e, type ExtractedFactWithOntology as f, type ExtractedTask as g, type GraphTraversalOptions as h, HEAL_RECHECK_MS as i, HOOK_TIMEOUT_MARKER as j, type HealResult as k, ONTOLOGY_BACKFILL_BATCH_SIZE as l, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as m, ONTOLOGY_BACKFILL_RECHECK_MS as n, type OntologyBackfillResult as o, type OntologyConfig as p, type OntologyEdgeType as q, type OntologyMode as r, type OntologyNodeType as s, type OntologyPromptContext as t, type OntologyUpdates as u, PromptService as v, PrunePartialFailureError as w, type VectorRankerFallback as x, type VectorRankerRankArgs as y, type VectorRankerSemanticResult as z };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { X as EmbeddingService, Y as ImportExportService, Z as IngestionService, _ as JobManager, _ as JobManagerType, $ as MaintenanceService, a0 as RetrievalService, a1 as SearchService, a1 as SearchServiceType, N as WikiMemoryTestAccess, a2 as WriteService } from './testing-DCRNie7k.mjs';
|
|
2
2
|
import 'minisearch';
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { X as EmbeddingService, Y as ImportExportService, Z as IngestionService, _ as JobManager, _ as JobManagerType, $ as MaintenanceService, a0 as RetrievalService, a1 as SearchService, a1 as SearchServiceType, N as WikiMemoryTestAccess, a2 as WriteService } from './testing-DCRNie7k.js';
|
|
2
2
|
import 'minisearch';
|
package/dist/testing.js
CHANGED
|
@@ -1261,6 +1261,8 @@ var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
1261
1261
|
var HEAL_MAX_ANCHORS = 50;
|
|
1262
1262
|
var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
1263
1263
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
1264
|
+
var HEAL_BATCH_SIZE = 25;
|
|
1265
|
+
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1264
1266
|
var MaintenanceService = class {
|
|
1265
1267
|
constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1266
1268
|
this.db = db;
|
|
@@ -1361,7 +1363,7 @@ var MaintenanceService = class {
|
|
|
1361
1363
|
async runHeal(entityId, options) {
|
|
1362
1364
|
this.jobManager.acquireLock("heal", entityId);
|
|
1363
1365
|
try {
|
|
1364
|
-
await this.doRunHeal(entityId, options
|
|
1366
|
+
return await this.doRunHeal(entityId, options);
|
|
1365
1367
|
} finally {
|
|
1366
1368
|
this.jobManager.releaseLock("heal", entityId);
|
|
1367
1369
|
}
|
|
@@ -1613,9 +1615,21 @@ var MaintenanceService = class {
|
|
|
1613
1615
|
}
|
|
1614
1616
|
this.searchService.evictCache(entityId);
|
|
1615
1617
|
}
|
|
1616
|
-
/**
|
|
1617
|
-
|
|
1618
|
+
/**
|
|
1619
|
+
* Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook.
|
|
1620
|
+
*
|
|
1621
|
+
* Bounded: at most `batchSize` candidates per pass (#67). Loop on
|
|
1622
|
+
* `result.remaining > 0` for convergence — see {@link HealResult.remaining}
|
|
1623
|
+
* for what convergence means here.
|
|
1624
|
+
*/
|
|
1625
|
+
async doRunHeal(entityId, options) {
|
|
1626
|
+
const promptOverride = options?.promptOverride;
|
|
1627
|
+
const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
|
|
1628
|
+
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
1629
|
+
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
1630
|
+
}
|
|
1618
1631
|
const now = Date.now();
|
|
1632
|
+
const recheckCutoff = now - HEAL_RECHECK_MS;
|
|
1619
1633
|
const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
|
|
1620
1634
|
const staleInferredAfterDays = this.options.config?.staleInferredAfterDays !== void 0 ? this.options.config?.staleInferredAfterDays : 60;
|
|
1621
1635
|
const MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -1626,6 +1640,7 @@ var MaintenanceService = class {
|
|
|
1626
1640
|
throw new Error("Invalid staleInferredAfterDays: must be a finite number >= 0 or null");
|
|
1627
1641
|
}
|
|
1628
1642
|
const orphanedIds = [];
|
|
1643
|
+
const staleDowngradedIds = [];
|
|
1629
1644
|
await this.db.withTransactionAsync(async (tx) => {
|
|
1630
1645
|
if (orphanAfterDays !== null) {
|
|
1631
1646
|
const orphanThreshold = now - orphanAfterDays * MS_PER_DAY;
|
|
@@ -1633,7 +1648,7 @@ var MaintenanceService = class {
|
|
|
1633
1648
|
}
|
|
1634
1649
|
if (staleInferredAfterDays !== null) {
|
|
1635
1650
|
const staleThreshold = now - staleInferredAfterDays * MS_PER_DAY;
|
|
1636
|
-
await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx);
|
|
1651
|
+
staleDowngradedIds.push(...await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx));
|
|
1637
1652
|
}
|
|
1638
1653
|
});
|
|
1639
1654
|
for (const factId of orphanedIds) {
|
|
@@ -1643,7 +1658,21 @@ var MaintenanceService = class {
|
|
|
1643
1658
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
|
|
1644
1659
|
}
|
|
1645
1660
|
}
|
|
1646
|
-
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId);
|
|
1661
|
+
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
1662
|
+
if (healCandidates.length === 0) {
|
|
1663
|
+
await this.searchService.sync(entityId);
|
|
1664
|
+
this.searchService.evictCache(entityId);
|
|
1665
|
+
const counts2 = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
|
|
1666
|
+
return {
|
|
1667
|
+
scanned: 0,
|
|
1668
|
+
downgraded: staleDowngradedIds.length,
|
|
1669
|
+
deleted: orphanedIds.length,
|
|
1670
|
+
newFactsCreated: 0,
|
|
1671
|
+
skipped: 0,
|
|
1672
|
+
remaining: counts2.eligible,
|
|
1673
|
+
deferred: counts2.deferred
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1647
1676
|
const allTasks = await this.taskRepo.findAllPending([entityId]);
|
|
1648
1677
|
const recentEvents = await this.eventRepo.getRecent(entityId, 20);
|
|
1649
1678
|
const toPromptShape = (f) => {
|
|
@@ -1696,7 +1725,7 @@ var MaintenanceService = class {
|
|
|
1696
1725
|
const validNewFacts = newFacts.map(validateFact).filter((f) => f !== null);
|
|
1697
1726
|
const insertedFacts = [];
|
|
1698
1727
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
1699
|
-
const healFactsForDedupe =
|
|
1728
|
+
const healFactsForDedupe = (await this.entryRepo.findInferredTitlesByEntityId(entityId)).filter((f) => !safeDeletedSet.has(f.id));
|
|
1700
1729
|
await this.db.withTransactionAsync(async (tx) => {
|
|
1701
1730
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
1702
1731
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
@@ -1705,7 +1734,6 @@ var MaintenanceService = class {
|
|
|
1705
1734
|
let skip = false;
|
|
1706
1735
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
1707
1736
|
for (const existing of healFactsForDedupe) {
|
|
1708
|
-
if (existing.source_type !== "librarian_inferred") continue;
|
|
1709
1737
|
const existingTokens = titleTokens(existing.title);
|
|
1710
1738
|
if (existingTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
1711
1739
|
if (jaccardScore(newTokens, existingTokens) >= FUZZY_THRESHOLD) {
|
|
@@ -1735,8 +1763,14 @@ var MaintenanceService = class {
|
|
|
1735
1763
|
};
|
|
1736
1764
|
await this.entryRepo.upsert(factObj, tx);
|
|
1737
1765
|
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
1738
|
-
healFactsForDedupe.push(
|
|
1766
|
+
healFactsForDedupe.push({ id, title: fact.title });
|
|
1739
1767
|
}
|
|
1768
|
+
await this.entryRepo.markHealChecked(
|
|
1769
|
+
[...healCandidates.map((f) => f.id), ...insertedFacts.map((f) => f.id)],
|
|
1770
|
+
entityId,
|
|
1771
|
+
now,
|
|
1772
|
+
tx
|
|
1773
|
+
);
|
|
1740
1774
|
});
|
|
1741
1775
|
await this.searchService.sync(entityId);
|
|
1742
1776
|
for (const factId of uniqueDeletedFactIds) {
|
|
@@ -1750,6 +1784,20 @@ var MaintenanceService = class {
|
|
|
1750
1784
|
await this.embeddingService.embedFact(fact);
|
|
1751
1785
|
}
|
|
1752
1786
|
this.searchService.evictCache(entityId);
|
|
1787
|
+
let scanned = outcome.skipped.length;
|
|
1788
|
+
for (const batchResult of outcome.results) scanned += batchResult.batch.length;
|
|
1789
|
+
const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
|
|
1790
|
+
const allDeleted = /* @__PURE__ */ new Set([...orphanedIds, ...uniqueDeletedFactIds]);
|
|
1791
|
+
const counts = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
|
|
1792
|
+
return {
|
|
1793
|
+
scanned,
|
|
1794
|
+
downgraded: allDowngraded.size,
|
|
1795
|
+
deleted: allDeleted.size,
|
|
1796
|
+
newFactsCreated: insertedFacts.length,
|
|
1797
|
+
skipped: outcome.skipped.length,
|
|
1798
|
+
remaining: counts.eligible,
|
|
1799
|
+
deferred: counts.deferred
|
|
1800
|
+
};
|
|
1753
1801
|
}
|
|
1754
1802
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1755
1803
|
async doRunOntologyBackfill(entityId, options) {
|
|
@@ -3113,6 +3161,7 @@ var WriteService = class {
|
|
|
3113
3161
|
let shouldRunLibrarian = false;
|
|
3114
3162
|
let librarianCount = 0;
|
|
3115
3163
|
let prevMemoryCheckpoint = 0;
|
|
3164
|
+
let eventCount = 0;
|
|
3116
3165
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3117
3166
|
await this.eventRepo.add(newEvent, tx);
|
|
3118
3167
|
const threshold = this.options.config?.autoLibrarianThreshold || 20;
|
|
@@ -3120,6 +3169,7 @@ var WriteService = class {
|
|
|
3120
3169
|
this.eventRepo.count(entityId, tx),
|
|
3121
3170
|
this.metadataRepo.getCheckpoint(entityId, tx)
|
|
3122
3171
|
]);
|
|
3172
|
+
eventCount = count;
|
|
3123
3173
|
let memoryCheckpoint = cp.memory ?? 0;
|
|
3124
3174
|
if (memoryCheckpoint > count) memoryCheckpoint = 0;
|
|
3125
3175
|
if (count - memoryCheckpoint >= threshold) {
|
|
@@ -3141,6 +3191,8 @@ var WriteService = class {
|
|
|
3141
3191
|
if (!(e instanceof WikiBusyError)) throw e;
|
|
3142
3192
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
|
|
3143
3193
|
}
|
|
3194
|
+
} else if (!this.jobManager.isBlocked("librarian", entityId)) {
|
|
3195
|
+
this.maybeRunHeal(entityId, eventCount).catch(console.error);
|
|
3144
3196
|
}
|
|
3145
3197
|
}
|
|
3146
3198
|
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
|
|
@@ -3151,6 +3203,14 @@ var WriteService = class {
|
|
|
3151
3203
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
|
|
3152
3204
|
throw e;
|
|
3153
3205
|
}
|
|
3206
|
+
await this.maybeRunHeal(entityId, currentEventCount);
|
|
3207
|
+
}
|
|
3208
|
+
/**
|
|
3209
|
+
* Run one bounded auto-heal pass if the heal checkpoint has fallen
|
|
3210
|
+
* `autoHealThreshold` events behind. Called after every write (see
|
|
3211
|
+
* {@link write}) so a partial pass retries on the next write.
|
|
3212
|
+
*/
|
|
3213
|
+
async maybeRunHeal(entityId, currentEventCount) {
|
|
3154
3214
|
const autoHealThreshold = this.options.config?.autoHealThreshold || 100;
|
|
3155
3215
|
const cp = await this.metadataRepo.getCheckpoint(entityId, this.db);
|
|
3156
3216
|
let healCheckpoint = cp.heal ?? 0;
|
|
@@ -3158,8 +3218,10 @@ var WriteService = class {
|
|
|
3158
3218
|
const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
|
|
3159
3219
|
if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
|
|
3160
3220
|
try {
|
|
3161
|
-
await this.maintenanceService.doRunHeal(entityId);
|
|
3162
|
-
|
|
3221
|
+
const result = await this.maintenanceService.doRunHeal(entityId);
|
|
3222
|
+
if (result.remaining === 0) {
|
|
3223
|
+
await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
|
|
3224
|
+
}
|
|
3163
3225
|
} finally {
|
|
3164
3226
|
this.jobManager.releaseLock("heal", entityId);
|
|
3165
3227
|
}
|