@happyvertical/smrt-core 0.38.18 → 0.38.19

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/AGENTS.md CHANGED
@@ -11,6 +11,7 @@ ORM, code generation, AI integration, and the DispatchBus. Everything else build
11
11
  | ObjectRegistry | `src/registry.ts` | Global singleton (globalThis) — class metadata, fields, STI chains, manifests |
12
12
  | DispatchBus | `src/dispatch/bus.ts` | Inter-agent messaging — emit, subscribe (persistent), process |
13
13
  | GlobalInterceptors | `src/interceptors.ts` | Plugin system — beforeList/Get/Save/Delete hooks (used by tenancy) |
14
+ | LearningMemory | `src/learning/memory.ts` | Confidence-scored recall/capture over `_smrt_contexts` + embeddings (#1886) |
14
15
 
15
16
  ## SmrtObject Lifecycle
16
17
 
@@ -22,6 +23,25 @@ ORM, code generation, AI integration, and the DispatchBus. Everything else build
22
23
  - `getSlug()`: auto-generates from name → title → label → id
23
24
  - `loadRelated(fieldName)`: lazy-loads relationships (cached in `_loadedRelationships` Map)
24
25
 
26
+ ## LearningMemory (#1886)
27
+
28
+ Confidence-scored, self-correcting memory over the existing `_smrt_contexts` (keyed recall) and `_smrt_embeddings` (semantic recall) substrate. Wires the reinforcement columns that ship on `_smrt_contexts` but were never written (`success_count`, `failure_count`, and a `last_used_at` that recall now refreshes). This is L1 of the tenant-learning-agents epic; the opt-in `Learning` trait in `@happyvertical/smrt-agents` composes it into the agent lifecycle.
29
+
30
+ ```typescript
31
+ const memory = new LearningMemory({ db: obj.systemDb, ownerClass: 'InvoiceAgent', ownerId: obj.id, tenantId });
32
+
33
+ // recall — union of keyed-context lookup + (optional) semantic search, confidence-filtered
34
+ const [hit] = await memory.recall('parser/acme', { key: docUrl }); // or { query } with a wired semanticSearch
35
+ const strategy = hit?.value ?? (await generate());
36
+
37
+ // capture — reinforce the outcome
38
+ await memory.capture({ scope: 'parser/acme', key: docUrl, value: strategy }, { success: ok });
39
+ ```
40
+
41
+ - **`capture(episode, outcome)`**: success strengthens `confidence` toward 1.0 + increments `success_count`; failure decays toward `failureConfidence` (default 0.3) + increments `failure_count`. Defaults (`minConfidence` 0.7, `successConfidence` 0.9, `reinforcement` 0.5) mean a single failure drops a confident memory below the reuse floor. Seeds a new row when none exists and the episode carries a `value` (a failed first attempt is retained at low confidence for self-correction).
42
+ - **`recall(scope, opts)`**: owner-scoped keyed lookup (thus tenant-isolated) filtered by the confidence floor, expiry, and optional time-decay, with hierarchical scope fallback; unions an injected `semanticSearch` arm when a `query` is given (tenant-scoped via its `where`). Refreshes `last_used_at` on returned rows.
43
+ - Injected `semanticSearch` matches `SmrtCollection.semanticSearch`, so `LearningMemory` never reaches into a collection's internals.
44
+
25
45
  ## SmrtCollection Query
26
46
 
27
47
  ```typescript
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ export { type JunctionAttachOptions, type JunctionFilterOptions, SmrtJunction, }
37
37
  export * from './knowledge';
38
38
  export type { ConfigResolver, LazyConfigSentinel, ResolveLazyConfigOptions, } from './lazy-config';
39
39
  export { getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver, } from './lazy-config';
40
+ export { DEFAULT_LEARNING_CONFIG, type LearningEpisode, LearningMemory, type LearningMemoryConfig, type LearningMemoryOptions, type LearningMemoryRecord, type LearningOutcome, type LearningRecallOptions, type LearningSemanticSearch, } from './learning/index';
40
41
  export * from './manifest/index';
41
42
  export type { DiffOptions } from './migrations/differ';
42
43
  export { generateSchemaDiff, getSQLFromDiff, hasActionableChanges, SchemaComparer, } from './migrations/differ';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -55,6 +55,7 @@ import { SmrtHierarchical } from "./hierarchical.js";
55
55
  import { SmrtJunction } from "./junction.js";
56
56
  import { buildDomainKnowledgeManifest } from "./knowledge.js";
57
57
  import { getClassConfigResolvers, getConfigResolver, isLazyConfigSentinel, listConfigResolvers, registerConfigResolver, resetConfigResolvers, resolveLazyConfig, unregisterConfigResolver } from "./lazy-config.js";
58
+ import { DEFAULT_LEARNING_CONFIG, LearningMemory } from "./learning/memory.js";
58
59
  import { ManifestBuilder } from "./manifest/generator.js";
59
60
  import manifest, { getManifest, staticManifest } from "./manifest/index.js";
60
61
  import { SchemaComparer, generateSchemaDiff, getSQLFromDiff, hasActionableChanges } from "./migrations/differ.js";
@@ -68,4 +69,4 @@ import "./system/index.js";
68
69
  import { getTestDatabase } from "./testing/database.js";
69
70
  import "./tools/index.js";
70
71
  import { smrtPlugin } from "./vite-plugin/index.js";
71
- export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, broadcastCacheInvalidation, buildChangeEventStream, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, changeEventSubscribersAtCapacity, childAccessorName, clone, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, normalizeEventsMaxSubscribers, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
72
+ export { AIError, APIGenerator, AiUsageCollector, AiUsagePersistenceHandler, CACHE_INVALIDATION_CHANNEL, CHANGE_FEED_INTERCEPTOR_NAME, CHANGE_FEED_TABLE, CHANGE_SIGNAL_CHANNEL, CLIGenerator, ConfigurationError, ContentHasher, CosineSimilarity, DEFAULT_AI_COST_RATES, DEFAULT_CHANGES_LIMIT, DEFAULT_EMBEDDING_CONFIG, DEFAULT_EVENTS_HEARTBEAT_MS, DEFAULT_EVENTS_MAX_SUBSCRIBERS, DEFAULT_EVENTS_RETRY_AFTER_SECONDS, DEFAULT_LEARNING_CONFIG, DatabaseError, Dispatch, DispatchBus, DispatchCollection, DispatchSubscription, DispatchSubscriptionCollection, EmbeddingProvider, EmbeddingStorage, ErrorUtils, FilesystemError, GlobalInterceptors, LearningMemory, MAX_CHANGES_LIMIT, MAX_SYNC_APPLY_BATCH_SIZE, MCPGenerator, ManifestBuilder, ManifestGenerator, ManifestManager, MetricsAdapter, NetworkError, ObjectRegistry, PRIVATE_READ_CACHE_CONTROL, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SYNC_APPLY_ROUTE_SEGMENTS, SYNC_APPLY_UUID_PATTERN, SchemaComparer, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtMCPServer, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, appendChange, applyOneToManyChildAccessors, applyPendingDecoratorRegistrations, applySyncWritablePolicy, broadcastCacheInvalidation, buildChangeEventStream, buildDomainKnowledgeManifest, bumpChangeFeed, canonicalReadRepresentation, changeEventSubscribersAtCapacity, childAccessorName, clone, computeBodyEtag, computeRuntimeWebManifestHash, computeTableVersionEtag, conditionalJsonResponse, config, convertTypeToJsonSchema, createDispatchBus, createInterceptorContext, createMCPServer, createQualifiedName, createRestServer, createSmrtClient, createSmrtServer, crossPackageRef, detectEngine, discoverManifestEntry, ensureCacheInvalidationListener, ensureChangeFeedTable, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, estimateAiUsageCost, eventStreamCapacityExceededResponse, executeToolCall, executeToolCalls, field, findManifestEntryByQualifiedName, foreignKey, formatToolResults, generateDDLForEngine, generateOpenAPISpec, generateSchemaDiff, generateToolFromMethod, generateToolManifest, getAdapterInfo, getCLIHandler, getCacheGeneration, getChangesSince, getClassConfigResolvers, getClassName, getConfigResolver, getManifest, getPackageFromQualifiedName, getSQLFromDiff, getTableVersion, getTenantScopedChangesSince, getTestDatabase, hasActionableChanges, ifNoneMatchHasConcreteMatch, ifNoneMatchSatisfied, invalidateCollectionCache, isDatabaseInterface, isFromPackage, isLazyConfigSentinel, isQualifiedName, isSmrtCollectionExtendsName, isStaleWrite, isTenantScopedClassResolved, isType, isValid, listConfigResolvers, loadExternalManifest, loadExternalManifestSync, loadLocalTestManifestSync, loadManifestFromPathSync, manifest, manyToMany, meta, normalizeEventsMaxSubscribers, oneToMany, parse, parseQualifiedName, parseSyncApplyBatch, payloadMatchesRow, processSyncApplyBatch, pruneChangeFeed, qualifiedNamesEqual, registerChangeFeedWriter, registerCompatibleFieldDecorator, registerConfigResolver, resetChangeFeedWarnings, resetChangeSignals, resetCollectionCache, resetConfigResolvers, resetVerifiedTables, resolveDatabase, resolveDbCacheKey, resolveDispatchTenantId, resolveDispatchTenantScope, resolveLazyConfig, resolveReadCacheControl, resolveTenantEtagDiscriminator, runWithTenantGate, safeParse, safeStringify, setDispatchTenantResolver, setTenantEntryPointRunner, setTenantScopedClassResolver, setupCLI, setupSwaggerUI, shouldIncludeMethod, signalVisibleToTenant, smrt, smrtPlugin, smrt as smrtRegistry, startRestServer, staticManifest, stopCacheInvalidationListeners, stopChangeSignalListeners, stringify, subscribeToChangeSignals, tableExists, tryReserveChangeEventSubscriberSlot, unregisterChangeFeedWriter, unregisterConfigResolver, validateSyncApplyItem, validateToolCall, versionConditionalResponse, warnIfSharedCacheNeutralized };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Learning primitives — confidence-scored, self-correcting memory (L1 of the
3
+ * tenant-learning-agents epic, #1885 / #1886).
4
+ *
5
+ * @module
6
+ */
7
+ export { DEFAULT_LEARNING_CONFIG, type LearningEpisode, LearningMemory, type LearningMemoryConfig, type LearningMemoryOptions, type LearningMemoryRecord, type LearningOutcome, type LearningRecallOptions, type LearningSemanticSearch, } from './memory.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/learning/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,uBAAuB,EACvB,KAAK,eAAe,EACpB,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC"}
@@ -0,0 +1,226 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ /**
3
+ * A relevance-scored memory returned by {@link LearningMemory.recall}.
4
+ *
5
+ * Records sourced from keyed `_smrt_contexts` lookups carry reinforcement
6
+ * counters; records sourced from semantic embedding search carry a
7
+ * `similarity` score (mirrored into `confidence` for uniform ranking).
8
+ */
9
+ export interface LearningMemoryRecord {
10
+ /** Row id in `_smrt_contexts`, or the matched object id for semantic hits. */
11
+ id: string;
12
+ /** Hierarchical scope the memory is filed under. */
13
+ scope: string;
14
+ /** Lookup key within the scope. */
15
+ key: string;
16
+ /** The stored value (JSON-parsed for context rows; the matched object for semantic hits). */
17
+ value: unknown;
18
+ /** Confidence in `[0, 1]`. For semantic hits this equals `similarity`. */
19
+ confidence: number;
20
+ /** Number of captured successes reinforcing this memory. */
21
+ successCount: number;
22
+ /** Number of captured failures decaying this memory. */
23
+ failureCount: number;
24
+ /** Last time this memory was recalled or reinforced. */
25
+ lastUsedAt: Date | null;
26
+ /** Where the record came from. */
27
+ source: 'context' | 'semantic';
28
+ /** Cosine similarity for semantic hits (absent for context rows). */
29
+ similarity?: number;
30
+ }
31
+ /**
32
+ * The outcome of acting on a recalled (or freshly generated) memory.
33
+ *
34
+ * A boolean `success` signal is the common case; a numeric `metric` delta is
35
+ * supported as a convenience (positive → success, non-positive → failure).
36
+ */
37
+ export type LearningOutcome = {
38
+ success: boolean;
39
+ error?: string;
40
+ } | {
41
+ metric: number;
42
+ error?: string;
43
+ };
44
+ /**
45
+ * The episode being reinforced — the scope/key of the memory the agent acted
46
+ * on, plus (for a first capture) the value to persist.
47
+ */
48
+ export interface LearningEpisode {
49
+ /** Scope the memory is filed under (e.g. `'sample/parse-invoice'`). */
50
+ scope: string;
51
+ /** Lookup key within the scope. */
52
+ key: string;
53
+ /**
54
+ * The value (strategy) this outcome pertains to. Seeds a new memory when none
55
+ * exists for `(scope, key)`, and refreshes the stored value when one does — so
56
+ * a regenerated strategy supersedes a decayed one. Omit to reinforce an
57
+ * existing memory's confidence without touching its stored value.
58
+ */
59
+ value?: unknown;
60
+ /** Optional metadata to persist on a first capture. */
61
+ metadata?: Record<string, unknown>;
62
+ /** Optional expiry after which recall ignores the memory. */
63
+ expiresAt?: Date;
64
+ }
65
+ /**
66
+ * Tunable thresholds for the reinforcement loop. Defaults mirror the proven
67
+ * `praeco` behaviour.
68
+ */
69
+ export interface LearningMemoryConfig {
70
+ /** Reuse floor — recall omits memories below this confidence. Default 0.7. */
71
+ minConfidence: number;
72
+ /** Confidence a brand-new memory is seeded at on a first success. Default 0.9. */
73
+ successConfidence: number;
74
+ /** Target a memory decays toward on failure. Default 0.3. */
75
+ failureConfidence: number;
76
+ /**
77
+ * Blend weight in `[0, 1]` for each reinforcement step. At 0.5 a single
78
+ * failure from any confident value (>= 0.7) always lands below the 0.7 floor
79
+ * (`0.5·c + 0.15 <= 0.65`). Default 0.5.
80
+ */
81
+ reinforcement: number;
82
+ /**
83
+ * Optional half-life (ms) for time-based confidence decay. When set, recall
84
+ * discounts a memory's stored confidence by `0.5 ^ (age / halfLife)` based on
85
+ * `last_used_at`, so stale memory falls below the floor over time even
86
+ * without an explicit failure. Off (no time decay) when undefined.
87
+ */
88
+ decayHalfLifeMs?: number;
89
+ }
90
+ /** Default reinforcement thresholds. */
91
+ export declare const DEFAULT_LEARNING_CONFIG: LearningMemoryConfig;
92
+ /**
93
+ * A semantic search over stored embeddings, injected so `LearningMemory` never
94
+ * reaches into a collection's internals. Matches the shape of
95
+ * `SmrtCollection.semanticSearch`.
96
+ */
97
+ export type LearningSemanticSearch = (query: string, options: {
98
+ limit?: number;
99
+ minSimilarity?: number;
100
+ where?: Record<string, unknown>;
101
+ }) => Promise<Array<{
102
+ id?: string;
103
+ _similarity: number;
104
+ } & Record<string, unknown>>>;
105
+ /** Options for {@link LearningMemory.recall}. */
106
+ export interface LearningRecallOptions {
107
+ /** Exact key to look up within the scope. Omit for a scope-wide recall. */
108
+ key?: string;
109
+ /** Free-text query for semantic recall (only used when a searcher is wired). */
110
+ query?: string;
111
+ /** Override the reuse floor for this call. */
112
+ minConfidence?: number;
113
+ /** Walk up the scope hierarchy when no match is found at `scope`. Default true. */
114
+ includeAncestors?: boolean;
115
+ /** Cap on returned records. */
116
+ limit?: number;
117
+ /** Cap on semantic hits requested from the searcher. Default 10. */
118
+ semanticLimit?: number;
119
+ /** Minimum cosine similarity for semantic hits. Default 0. */
120
+ minSimilarity?: number;
121
+ /** Reference time for decay/expiry evaluation (injectable for tests). */
122
+ now?: Date;
123
+ }
124
+ /** Constructor options for {@link LearningMemory}. */
125
+ export interface LearningMemoryOptions {
126
+ /** System database handle (an owning object's `systemDb`). */
127
+ db: DatabaseInterface;
128
+ /** `owner_class` the memories are filed under. */
129
+ ownerClass: string;
130
+ /** `owner_id` the memories are filed under (isolates memory per owner). */
131
+ ownerId: string;
132
+ /**
133
+ * Optional tenant id. When set it is threaded into the semantic searcher's
134
+ * `where` filter so embedding recall stays tenant-scoped. Keyed-context
135
+ * recall is already isolated by `owner_id`.
136
+ */
137
+ tenantId?: string | null;
138
+ /** Optional embedding search for the semantic recall arm. */
139
+ semanticSearch?: LearningSemanticSearch;
140
+ /** Threshold overrides; merged over {@link DEFAULT_LEARNING_CONFIG}. */
141
+ config?: Partial<LearningMemoryConfig>;
142
+ }
143
+ /**
144
+ * Confidence-scored, self-correcting memory over `_smrt_contexts` and
145
+ * (optionally) semantic embedding search.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const memory = new LearningMemory({
150
+ * db: agent.systemDb,
151
+ * ownerClass: 'InvoiceAgent',
152
+ * ownerId: agent.id,
153
+ * tenantId: agent.tenantId,
154
+ * });
155
+ *
156
+ * // Recall a confident strategy for this task.
157
+ * const [hit] = await memory.recall('parser/acme', { key: docUrl });
158
+ * const strategy = hit?.value ?? (await generateStrategy(docUrl));
159
+ *
160
+ * // ...act on `strategy`, then reinforce the outcome.
161
+ * await memory.capture(
162
+ * { scope: 'parser/acme', key: docUrl, value: strategy },
163
+ * { success: extracted.length > 0 },
164
+ * );
165
+ * ```
166
+ */
167
+ export declare class LearningMemory {
168
+ private readonly db;
169
+ private readonly ownerClass;
170
+ private readonly ownerId;
171
+ private readonly tenantId;
172
+ private readonly semanticSearch?;
173
+ private readonly config;
174
+ constructor(options: LearningMemoryOptions);
175
+ /** The resolved reinforcement thresholds (defaults merged with overrides). */
176
+ get thresholds(): LearningMemoryConfig;
177
+ /**
178
+ * Recall confidence-filtered, relevant memories for a scope.
179
+ *
180
+ * Unions two arms:
181
+ * 1. **Keyed context recall** over `_smrt_contexts` — owner-scoped (and thus
182
+ * tenant-isolated), filtered by the confidence floor, expiry, and optional
183
+ * time-decay, with hierarchical scope fallback. Surviving rows have their
184
+ * `last_used_at` refreshed.
185
+ * 2. **Semantic recall** over stored embeddings (only when a searcher was
186
+ * wired and a `query` is given) — tenant-scoped via the searcher's `where`.
187
+ *
188
+ * @returns Records sorted by confidence (then similarity) descending.
189
+ */
190
+ recall(scope: string, options?: LearningRecallOptions): Promise<LearningMemoryRecord[]>;
191
+ /**
192
+ * Reinforce a memory from an observed outcome.
193
+ *
194
+ * - Wires the dormant reinforcement columns: increments `success_count` on
195
+ * success, `failure_count` on failure.
196
+ * - Adjusts `confidence`: success strengthens toward 1.0; failure decays
197
+ * toward `failureConfidence` (default 0.3), dropping a confident memory
198
+ * below the reuse floor in a single step.
199
+ * - Refreshes `last_used_at` and `updated_at`, and (when the episode carries a
200
+ * `value`) the stored value — so a regenerated strategy supersedes a decayed
201
+ * one instead of the old value resurfacing on the next success.
202
+ * - Seeds a new memory when none exists for `(scope, key)` and the episode
203
+ * carries a `value` (a failed first attempt is retained at low confidence
204
+ * for self-correction context).
205
+ *
206
+ * @returns The updated (or seeded) record, or `null` when there was nothing
207
+ * to reinforce (no existing memory and no value to seed).
208
+ */
209
+ capture(episode: LearningEpisode, outcome: LearningOutcome): Promise<LearningMemoryRecord | null>;
210
+ /**
211
+ * Compute the next confidence from the current value and an outcome.
212
+ *
213
+ * Success strengthens toward 1.0; failure blends toward `failureConfidence`.
214
+ * Exposed as a pure step so callers/tests can reason about the curve.
215
+ */
216
+ nextConfidence(current: number, success: boolean): number;
217
+ private recallContexts;
218
+ private selectContextRows;
219
+ private filterAndRank;
220
+ private applyTimeDecay;
221
+ private recallSemantic;
222
+ private touch;
223
+ private scopeChain;
224
+ private parseMetadata;
225
+ }
226
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/learning/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,340 @@
1
+ //#region src/learning/memory.ts
2
+ /** Default reinforcement thresholds. */
3
+ var DEFAULT_LEARNING_CONFIG = {
4
+ minConfidence: .7,
5
+ successConfidence: .9,
6
+ failureConfidence: .3,
7
+ reinforcement: .5
8
+ };
9
+ var CONTEXTS_TABLE = "_smrt_contexts";
10
+ var CONFLICT_COLUMNS = [
11
+ "owner_class",
12
+ "owner_id",
13
+ "scope",
14
+ "key",
15
+ "version"
16
+ ];
17
+ /**
18
+ * The single `_smrt_contexts` version LearningMemory reads and writes. Kept
19
+ * explicit so capture and recall agree on the row identity.
20
+ */
21
+ var MEMORY_VERSION = 1;
22
+ function clamp01(value) {
23
+ if (Number.isNaN(value)) return 0;
24
+ if (value < 0) return 0;
25
+ if (value > 1) return 1;
26
+ return value;
27
+ }
28
+ function normalizeOutcome(outcome) {
29
+ if ("success" in outcome) return {
30
+ success: outcome.success,
31
+ error: outcome.error
32
+ };
33
+ return {
34
+ success: outcome.metric > 0,
35
+ error: outcome.error
36
+ };
37
+ }
38
+ function toDate(value) {
39
+ if (value == null) return null;
40
+ if (value instanceof Date) return value;
41
+ const parsed = new Date(value);
42
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
43
+ }
44
+ function parseValue(raw) {
45
+ if (typeof raw !== "string") return raw ?? null;
46
+ try {
47
+ return JSON.parse(raw);
48
+ } catch {
49
+ return raw;
50
+ }
51
+ }
52
+ /**
53
+ * Confidence-scored, self-correcting memory over `_smrt_contexts` and
54
+ * (optionally) semantic embedding search.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const memory = new LearningMemory({
59
+ * db: agent.systemDb,
60
+ * ownerClass: 'InvoiceAgent',
61
+ * ownerId: agent.id,
62
+ * tenantId: agent.tenantId,
63
+ * });
64
+ *
65
+ * // Recall a confident strategy for this task.
66
+ * const [hit] = await memory.recall('parser/acme', { key: docUrl });
67
+ * const strategy = hit?.value ?? (await generateStrategy(docUrl));
68
+ *
69
+ * // ...act on `strategy`, then reinforce the outcome.
70
+ * await memory.capture(
71
+ * { scope: 'parser/acme', key: docUrl, value: strategy },
72
+ * { success: extracted.length > 0 },
73
+ * );
74
+ * ```
75
+ */
76
+ var LearningMemory = class {
77
+ db;
78
+ ownerClass;
79
+ ownerId;
80
+ tenantId;
81
+ semanticSearch;
82
+ config;
83
+ constructor(options) {
84
+ this.db = options.db;
85
+ this.ownerClass = options.ownerClass;
86
+ this.ownerId = options.ownerId;
87
+ this.tenantId = options.tenantId ?? null;
88
+ this.semanticSearch = options.semanticSearch;
89
+ this.config = {
90
+ ...DEFAULT_LEARNING_CONFIG,
91
+ ...options.config ?? {}
92
+ };
93
+ }
94
+ /** The resolved reinforcement thresholds (defaults merged with overrides). */
95
+ get thresholds() {
96
+ return { ...this.config };
97
+ }
98
+ /**
99
+ * Recall confidence-filtered, relevant memories for a scope.
100
+ *
101
+ * Unions two arms:
102
+ * 1. **Keyed context recall** over `_smrt_contexts` — owner-scoped (and thus
103
+ * tenant-isolated), filtered by the confidence floor, expiry, and optional
104
+ * time-decay, with hierarchical scope fallback. Surviving rows have their
105
+ * `last_used_at` refreshed.
106
+ * 2. **Semantic recall** over stored embeddings (only when a searcher was
107
+ * wired and a `query` is given) — tenant-scoped via the searcher's `where`.
108
+ *
109
+ * @returns Records sorted by confidence (then similarity) descending.
110
+ */
111
+ async recall(scope, options = {}) {
112
+ const floor = options.minConfidence ?? this.config.minConfidence;
113
+ const now = options.now ?? /* @__PURE__ */ new Date();
114
+ const includeAncestors = options.includeAncestors ?? true;
115
+ const contextRecords = await this.recallContexts(scope, {
116
+ key: options.key,
117
+ floor,
118
+ now,
119
+ includeAncestors
120
+ });
121
+ await this.touch(contextRecords.map((r) => r.id), now);
122
+ let semanticRecords = [];
123
+ if (options.query && this.semanticSearch) semanticRecords = await this.recallSemantic(options.query, {
124
+ floor: options.minSimilarity ?? floor,
125
+ limit: options.semanticLimit ?? 10
126
+ });
127
+ const merged = [...contextRecords, ...semanticRecords].sort((a, b) => {
128
+ if (b.confidence !== a.confidence) return b.confidence - a.confidence;
129
+ return (b.similarity ?? 0) - (a.similarity ?? 0);
130
+ });
131
+ return options.limit != null ? merged.slice(0, options.limit) : merged;
132
+ }
133
+ /**
134
+ * Reinforce a memory from an observed outcome.
135
+ *
136
+ * - Wires the dormant reinforcement columns: increments `success_count` on
137
+ * success, `failure_count` on failure.
138
+ * - Adjusts `confidence`: success strengthens toward 1.0; failure decays
139
+ * toward `failureConfidence` (default 0.3), dropping a confident memory
140
+ * below the reuse floor in a single step.
141
+ * - Refreshes `last_used_at` and `updated_at`, and (when the episode carries a
142
+ * `value`) the stored value — so a regenerated strategy supersedes a decayed
143
+ * one instead of the old value resurfacing on the next success.
144
+ * - Seeds a new memory when none exists for `(scope, key)` and the episode
145
+ * carries a `value` (a failed first attempt is retained at low confidence
146
+ * for self-correction context).
147
+ *
148
+ * @returns The updated (or seeded) record, or `null` when there was nothing
149
+ * to reinforce (no existing memory and no value to seed).
150
+ */
151
+ async capture(episode, outcome) {
152
+ const { success, error } = normalizeOutcome(outcome);
153
+ const now = /* @__PURE__ */ new Date();
154
+ const existing = await this.db.get(CONTEXTS_TABLE, {
155
+ owner_class: this.ownerClass,
156
+ owner_id: this.ownerId,
157
+ scope: episode.scope,
158
+ key: episode.key,
159
+ version: MEMORY_VERSION
160
+ });
161
+ if (existing) {
162
+ const currentConfidence = Number(existing.confidence ?? 1);
163
+ const nextConfidence = this.nextConfidence(currentConfidence, success);
164
+ const successCount = Number(existing.success_count ?? 0) + (success ? 1 : 0);
165
+ const failureCount = Number(existing.failure_count ?? 0) + (success ? 0 : 1);
166
+ const data = {
167
+ confidence: nextConfidence,
168
+ success_count: successCount,
169
+ failure_count: failureCount,
170
+ last_used_at: now,
171
+ updated_at: now
172
+ };
173
+ if (episode.value !== void 0) data.value = JSON.stringify(episode.value);
174
+ if (episode.metadata !== void 0) data.metadata = JSON.stringify({
175
+ ...this.parseMetadata(existing.metadata),
176
+ ...episode.metadata,
177
+ ...error ? { lastError: error } : {}
178
+ });
179
+ else if (error) data.metadata = JSON.stringify({
180
+ ...this.parseMetadata(existing.metadata),
181
+ lastError: error
182
+ });
183
+ await this.db.update(CONTEXTS_TABLE, { id: existing.id }, data);
184
+ return {
185
+ id: String(existing.id),
186
+ scope: episode.scope,
187
+ key: episode.key,
188
+ value: episode.value !== void 0 ? episode.value : parseValue(existing.value),
189
+ confidence: nextConfidence,
190
+ successCount,
191
+ failureCount,
192
+ lastUsedAt: now,
193
+ source: "context"
194
+ };
195
+ }
196
+ if (episode.value === void 0) return null;
197
+ const id = crypto.randomUUID();
198
+ const confidence = success ? this.config.successConfidence : this.config.failureConfidence;
199
+ const metadata = episode.metadata || error ? JSON.stringify({
200
+ ...episode.metadata ?? {},
201
+ ...error ? { lastError: error } : {}
202
+ }) : null;
203
+ await this.db.upsert(CONTEXTS_TABLE, CONFLICT_COLUMNS, {
204
+ id,
205
+ owner_class: this.ownerClass,
206
+ owner_id: this.ownerId,
207
+ scope: episode.scope,
208
+ key: episode.key,
209
+ value: JSON.stringify(episode.value),
210
+ metadata,
211
+ version: MEMORY_VERSION,
212
+ confidence,
213
+ success_count: success ? 1 : 0,
214
+ failure_count: success ? 0 : 1,
215
+ created_at: now,
216
+ updated_at: now,
217
+ last_used_at: now,
218
+ expires_at: episode.expiresAt ?? null
219
+ });
220
+ return {
221
+ id,
222
+ scope: episode.scope,
223
+ key: episode.key,
224
+ value: episode.value,
225
+ confidence,
226
+ successCount: success ? 1 : 0,
227
+ failureCount: success ? 0 : 1,
228
+ lastUsedAt: now,
229
+ source: "context"
230
+ };
231
+ }
232
+ /**
233
+ * Compute the next confidence from the current value and an outcome.
234
+ *
235
+ * Success strengthens toward 1.0; failure blends toward `failureConfidence`.
236
+ * Exposed as a pure step so callers/tests can reason about the curve.
237
+ */
238
+ nextConfidence(current, success) {
239
+ const r = this.config.reinforcement;
240
+ if (success) return clamp01(current + r * (1 - current));
241
+ return clamp01(current + r * (this.config.failureConfidence - current));
242
+ }
243
+ async recallContexts(scope, opts) {
244
+ const scopes = opts.includeAncestors ? this.scopeChain(scope) : [scope];
245
+ for (const candidateScope of scopes) {
246
+ const rows = await this.selectContextRows(candidateScope, opts.key);
247
+ const records = this.filterAndRank(rows, opts.floor, opts.now);
248
+ if (records.length > 0) return records;
249
+ }
250
+ return [];
251
+ }
252
+ async selectContextRows(scope, key) {
253
+ if (key !== void 0) {
254
+ const { rows } = await this.db.query(`SELECT * FROM ${CONTEXTS_TABLE}
255
+ WHERE owner_class = ? AND owner_id = ? AND scope = ? AND key = ?
256
+ AND version = ?`, this.ownerClass, this.ownerId, scope, key, MEMORY_VERSION);
257
+ return rows;
258
+ }
259
+ const { rows } = await this.db.query(`SELECT * FROM ${CONTEXTS_TABLE}
260
+ WHERE owner_class = ? AND owner_id = ? AND scope = ? AND version = ?`, this.ownerClass, this.ownerId, scope, MEMORY_VERSION);
261
+ return rows;
262
+ }
263
+ filterAndRank(rows, floor, now) {
264
+ const records = [];
265
+ for (const row of rows) {
266
+ const expiresAt = toDate(row.expires_at);
267
+ if (expiresAt && expiresAt.getTime() <= now.getTime()) continue;
268
+ const lastUsedAt = toDate(row.last_used_at);
269
+ const stored = Number(row.confidence ?? 1);
270
+ const effective = this.applyTimeDecay(stored, lastUsedAt, now);
271
+ if (effective < floor) continue;
272
+ records.push({
273
+ id: String(row.id),
274
+ scope: String(row.scope),
275
+ key: String(row.key),
276
+ value: parseValue(row.value),
277
+ confidence: effective,
278
+ successCount: Number(row.success_count ?? 0),
279
+ failureCount: Number(row.failure_count ?? 0),
280
+ lastUsedAt,
281
+ source: "context"
282
+ });
283
+ }
284
+ return records.sort((a, b) => b.confidence - a.confidence);
285
+ }
286
+ applyTimeDecay(confidence, lastUsedAt, now) {
287
+ const halfLife = this.config.decayHalfLifeMs;
288
+ if (!halfLife || halfLife <= 0 || !lastUsedAt) return confidence;
289
+ const age = now.getTime() - lastUsedAt.getTime();
290
+ if (age <= 0) return confidence;
291
+ return clamp01(confidence * .5 ** (age / halfLife));
292
+ }
293
+ async recallSemantic(query, opts) {
294
+ if (!this.semanticSearch) return [];
295
+ const where = this.tenantId != null ? { tenant_id: this.tenantId } : void 0;
296
+ return (await this.semanticSearch(query, {
297
+ limit: opts.limit,
298
+ minSimilarity: opts.floor,
299
+ where
300
+ })).map((hit) => ({
301
+ id: hit.id != null ? String(hit.id) : crypto.randomUUID(),
302
+ scope: "semantic",
303
+ key: hit.id != null ? String(hit.id) : query,
304
+ value: hit,
305
+ confidence: hit._similarity,
306
+ successCount: 0,
307
+ failureCount: 0,
308
+ lastUsedAt: null,
309
+ source: "semantic",
310
+ similarity: hit._similarity
311
+ }));
312
+ }
313
+ async touch(ids, now) {
314
+ if (ids.length === 0) return;
315
+ const placeholders = ids.map(() => "?").join(", ");
316
+ await this.db.query(`UPDATE ${CONTEXTS_TABLE} SET last_used_at = ? WHERE id IN (${placeholders})`, now.toISOString(), ...ids);
317
+ }
318
+ scopeChain(scope) {
319
+ const chain = [scope];
320
+ const parts = scope.split("/");
321
+ while (parts.length > 0) {
322
+ parts.pop();
323
+ chain.push(parts.join("/") || "global");
324
+ }
325
+ return [...new Set(chain)];
326
+ }
327
+ parseMetadata(raw) {
328
+ if (typeof raw !== "string" || raw.length === 0) return {};
329
+ try {
330
+ const parsed = JSON.parse(raw);
331
+ return parsed && typeof parsed === "object" ? parsed : {};
332
+ } catch {
333
+ return {};
334
+ }
335
+ }
336
+ };
337
+ //#endregion
338
+ export { DEFAULT_LEARNING_CONFIG, LearningMemory };
339
+
340
+ //# sourceMappingURL=memory.js.map