soulbridge 0.0.26 → 0.0.98
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/LICENSE +202 -202
- package/dist/packages/cli/src/cli.js +0 -0
- package/dist/packages/core/src/config/dimensions.d.ts +18 -0
- package/dist/packages/core/src/config/dimensions.js +33 -0
- package/dist/packages/core/src/config/domains.d.ts +70 -0
- package/dist/packages/core/src/config/domains.js +70 -0
- package/dist/packages/core/src/config/labels.d.ts +22 -0
- package/dist/packages/core/src/config/labels.js +21 -0
- package/dist/packages/core/src/config/status.d.ts +51 -0
- package/dist/packages/core/src/config/status.js +47 -0
- package/dist/packages/core/src/consolidate.d.ts +170 -0
- package/dist/packages/core/src/consolidate.js +311 -0
- package/dist/packages/core/src/context.d.ts +2 -0
- package/dist/packages/core/src/context.js +48 -17
- package/dist/packages/core/src/dimension-text.d.ts +6 -0
- package/dist/packages/core/src/dimension-text.js +24 -0
- package/dist/packages/core/src/dimensions.d.ts +28 -0
- package/dist/packages/core/src/dimensions.js +53 -0
- package/dist/packages/core/src/extract.d.ts +140 -0
- package/dist/packages/core/src/extract.js +611 -0
- package/dist/packages/core/src/index.d.ts +362 -10
- package/dist/packages/core/src/index.js +983 -127
- package/dist/packages/core/src/inject-text.d.ts +16 -0
- package/dist/packages/core/src/inject-text.js +24 -0
- package/dist/packages/core/src/profile-prompt.d.ts +18 -0
- package/dist/packages/core/src/profile-prompt.js +103 -0
- package/dist/packages/core/src/prompts.d.ts +122 -0
- package/dist/packages/core/src/prompts.generated.d.ts +9 -0
- package/dist/packages/core/src/prompts.generated.js +9 -0
- package/dist/packages/core/src/prompts.js +293 -0
- package/dist/packages/core/src/reflect.d.ts +7 -0
- package/dist/packages/core/src/reflect.js +25 -27
- package/dist/packages/core/src/retrieval.d.ts +39 -0
- package/dist/packages/core/src/retrieval.js +105 -0
- package/dist/packages/core/src/roles.d.ts +18 -0
- package/dist/packages/core/src/roles.js +17 -0
- package/dist/packages/core/src/rules.d.ts +69 -0
- package/dist/packages/core/src/rules.js +152 -0
- package/dist/packages/core/src/sqlite.d.ts +26 -5
- package/dist/packages/core/src/sqlite.js +944 -33
- package/dist/packages/core/src/store.d.ts +92 -3
- package/dist/packages/core/src/types.d.ts +442 -1
- package/dist/packages/core/src/types.js +1 -0
- package/package.json +5 -3
|
@@ -11,16 +11,31 @@ export { buildContext, styleDirectives, DEFAULT_MAX_CHARS, AFFECT_BUDGET, AFFECT
|
|
|
11
11
|
export { reflect, ruleExtract, nextEmotion, isCorrection, correctionRule, detectCorrection, isQuestion, } from './reflect.ts';
|
|
12
12
|
export type { CorrectionLayer, CorrectionSignal, CorrectionVerdict } from './reflect.ts';
|
|
13
13
|
export { rank, scoreMemory, tokenize, relevance, bm25, buildCorpusStats } from './scoring.ts';
|
|
14
|
+
export { parseThreadLine } from './extract.ts';
|
|
15
|
+
export { renderConsolidationPrompt, renderVerificationPrompt, renderRelationPrompt, renderRuleClassifyPrompt, renderProfileMergePrompt, renderExtractionPrompt, } from './prompts.ts';
|
|
16
|
+
export type { ConsolidationPromptInput, VerificationPromptInput, RelationPromptInput, RuleClassifyPromptInput, ProfileMergePromptInput, ExtractionPromptInput, } from './prompts.ts';
|
|
17
|
+
export { gateConclusion, conclusionSlots, normalizeStatement, parseConclusionDraft, parseVerificationDraft, parseRelationDraft, readVerification, parseProfileMerge, groupProfileCandidates, isDerivedProfileLine, PROFILE_DIMENSIONS, } from './consolidate.ts';
|
|
18
|
+
export type { VerificationDraft, ConclusionRelation } from './consolidate.ts';
|
|
19
|
+
export type { ProfileLine, ProfileCandidate, ProfileGroup } from './consolidate.ts';
|
|
20
|
+
import type { ProfileLine } from './consolidate.ts';
|
|
21
|
+
export { fires, parseRuleClassifications, parseTrigger, selectRules } from './rules.ts';
|
|
22
|
+
export type { Rule, RuleTrigger, RuleClassification } from './rules.ts';
|
|
23
|
+
export { planRetrieval } from './retrieval.ts';
|
|
24
|
+
export type { RetrievalPlan, RetrievalPrimitive } from './retrieval.ts';
|
|
14
25
|
export * from './affect/index.ts';
|
|
26
|
+
export { escapeBlockText, escapeAttr } from './inject-text.ts';
|
|
15
27
|
export { measureStyle } from './style/measure.ts';
|
|
16
28
|
export type { StyleMetrics } from './style/measure.ts';
|
|
17
29
|
export * from './storage/config.ts';
|
|
18
30
|
export * from './storage/merge.ts';
|
|
19
31
|
export * from './storage/steering.ts';
|
|
20
32
|
export * from './storage/transcript.ts';
|
|
21
|
-
import type { AffectSignal, ContextRequest, Correction, Memory, ContextResult, Emotion, LlmFn, MemoryType, ObserveResult, ReflectResult, ScoredMemory, Turn, TurnPair, Visibility } from './types.ts';
|
|
33
|
+
import type { AffectSignal, ContextRequest, Correction, Memory, ContextResult, Emotion, LlmFn, MemoryType, ObserveResult, ReflectResult, ScoredMemory, Turn, CommitResult, Entity, ExtractDraft, ConclusionDraft, Fact, ThreadAttribution, TurnThread, FactDetail, FactSpec, Gap, GapSpec, SessionHandoff, TurnFindResult, SessionSummary, TurnPair, TurnStep, Visibility } from './types.ts';
|
|
22
34
|
import type { SoulStore } from './store.ts';
|
|
23
35
|
import type { AffectAnalyzer } from './affect/port.ts';
|
|
36
|
+
import type { Rule } from './rules.ts';
|
|
37
|
+
import type { RetrievalPlan } from './retrieval.ts';
|
|
38
|
+
import type { ExtractionContext } from './extract.ts';
|
|
24
39
|
export { gateDrafts } from './reflect.ts';
|
|
25
40
|
export interface SoulOptions {
|
|
26
41
|
store: SoulStore;
|
|
@@ -71,6 +86,12 @@ export declare class Soul {
|
|
|
71
86
|
* Audit view: every non-deleted row, including superseded and expired ones.
|
|
72
87
|
* Prompt builders use `context`, which filters both out.
|
|
73
88
|
*/
|
|
89
|
+
/**
|
|
90
|
+
* The recent holdings, as the shape hosts already consume.
|
|
91
|
+
*
|
|
92
|
+
* Reads facts: the legacy table is inert after the migration, and a list that kept
|
|
93
|
+
* reading it would look empty while the system clearly remembers things.
|
|
94
|
+
*/
|
|
74
95
|
list(limit?: number): Memory[];
|
|
75
96
|
/** Tombstone one memory so it never reaches a prompt again. */
|
|
76
97
|
forget(id: string): boolean;
|
|
@@ -86,6 +107,23 @@ export declare class Soul {
|
|
|
86
107
|
rule?: string;
|
|
87
108
|
}): string;
|
|
88
109
|
corrections(limit?: number): Correction[];
|
|
110
|
+
/**
|
|
111
|
+
* Was this turn one the user corrected?
|
|
112
|
+
*
|
|
113
|
+
* A correction is the highest-signal moment a lesson has: being told you were
|
|
114
|
+
* wrong is the consequence the experience comes with (brief 0023, T1). The link
|
|
115
|
+
* is the user's own words — corrections do not carry a turn id, and inventing a
|
|
116
|
+
* time window would misattribute a nearby turn.
|
|
117
|
+
*/
|
|
118
|
+
wasCorrectedTurn(turnId: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* The experiences of one turn that were flagged as lessons, and nothing else.
|
|
121
|
+
*
|
|
122
|
+
* Promotion reads the flag, never the statement: whether a sentence is "about how
|
|
123
|
+
* to work" is the semantic call the extractor already made, and re-deriving it in
|
|
124
|
+
* code would be the guess ADR 0027 forbids.
|
|
125
|
+
*/
|
|
126
|
+
promotionCandidates(turnId: string): Fact[];
|
|
89
127
|
/**
|
|
90
128
|
* Store one exchange (RFC 0007). Idempotent on `(sessionId, turn)`: a repeat is
|
|
91
129
|
* ignored by the store, so this returns nothing — there is no id worth handing
|
|
@@ -98,24 +136,322 @@ export declare class Soul {
|
|
|
98
136
|
assistantText: string;
|
|
99
137
|
at?: number;
|
|
100
138
|
metadata?: Record<string, unknown>;
|
|
101
|
-
|
|
139
|
+
/**
|
|
140
|
+
* The real shape of the reply: reasoning / text / tool steps in order.
|
|
141
|
+
*
|
|
142
|
+
* Stored, never injected. Reasoning is a draft, so recall and prompts read
|
|
143
|
+
* `kind === 'text'` only; it is kept because "why did you say that" and
|
|
144
|
+
* consolidation both need it.
|
|
145
|
+
*/
|
|
146
|
+
steps?: TurnStep[];
|
|
147
|
+
}): string;
|
|
148
|
+
/**
|
|
149
|
+
* One turn by id, for adapters that only kept the id (extraction queue,
|
|
150
|
+
* handoff). Returns null when the row is gone, so callers can drop the item
|
|
151
|
+
* instead of retrying forever.
|
|
152
|
+
*/
|
|
153
|
+
turn(id: string): TurnPair | null;
|
|
102
154
|
/** Recall exchanges, scored against `query` when it is non-empty. */
|
|
103
155
|
recallTurnPairs(query: string, limit?: number): TurnPair[];
|
|
104
156
|
countTurnPairs(): number;
|
|
105
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* Every turn that matches, not the best few: for "how many times" and "what
|
|
159
|
+
* did we decide overall" the count matters more than the ranking, and the
|
|
160
|
+
* model does the counting (ADR 0027). `truncated` tells the caller the answer
|
|
161
|
+
* would be incomplete.
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* Turn text → drafts.
|
|
165
|
+
*
|
|
166
|
+
* Extraction is the model's job (ADR 0029: Generative Agents, Mem0 and
|
|
167
|
+
* Graphiti all extract with an LLM and keep code for validation and storage).
|
|
168
|
+
* The rules are a floor, not a ceiling: they matter when no model is
|
|
169
|
+
* configured and they cross-check the answer when one is. A model answer with
|
|
170
|
+
* no usable draft falls back to the rules instead of losing the turn.
|
|
171
|
+
*/
|
|
172
|
+
extract(text: string, options?: {
|
|
173
|
+
at?: number;
|
|
174
|
+
llm?: (prompt: string) => Promise<string> | string;
|
|
175
|
+
/** 'auto' (default) uses the model then falls back; 'rules' forces rules. */
|
|
176
|
+
mode?: 'auto' | 'rules';
|
|
177
|
+
/** What the model may see; defaults to the recent store state. */
|
|
178
|
+
context?: ExtractionContext;
|
|
179
|
+
/**
|
|
180
|
+
* My reply in the same turn. The unit of memory is the exchange (ADR 0035):
|
|
181
|
+
* without this half, "my promises" is a rule the model can never use.
|
|
182
|
+
*/
|
|
183
|
+
assistantText?: string;
|
|
184
|
+
}): Promise<ExtractDraft>;
|
|
185
|
+
/** The slice of the store the extractor is allowed to know about. */
|
|
186
|
+
extractionContext(limit?: number, options?: {
|
|
187
|
+
turnNo?: number;
|
|
188
|
+
sessionId?: string;
|
|
189
|
+
}): ExtractionContext;
|
|
190
|
+
/**
|
|
191
|
+
* Put a turn into a thread.
|
|
192
|
+
*
|
|
193
|
+
* Default is continuity (ADR 0034): a named thread joins when it exists and is open;
|
|
194
|
+
* an unreadable answer, a hallucinated id, or no answer at all joins the previous
|
|
195
|
+
* turn's thread instead of opening a new one — fragmentation is what starves
|
|
196
|
+
* consolidation, and threads can be split later while a lost topic cannot.
|
|
197
|
+
*/
|
|
198
|
+
applyThread(attribution: ThreadAttribution | undefined, spec: {
|
|
199
|
+
sessionId: string;
|
|
200
|
+
turnNo: number;
|
|
201
|
+
turnId: string;
|
|
202
|
+
at: number;
|
|
203
|
+
fallbackTopic?: string;
|
|
204
|
+
/** The thread the previous turn belonged to; the default when unsure. */
|
|
205
|
+
previousThreadId?: string;
|
|
206
|
+
}): {
|
|
207
|
+
threadId: string;
|
|
208
|
+
created: boolean;
|
|
209
|
+
topic: string;
|
|
210
|
+
joinReason: string;
|
|
211
|
+
topicUpdated: boolean;
|
|
212
|
+
};
|
|
213
|
+
private openThreadFor;
|
|
214
|
+
/**
|
|
215
|
+
* Drafts → database.
|
|
216
|
+
*
|
|
217
|
+
* The gate is a labeller, not a filter. A sentence that parses is kept: an
|
|
218
|
+
* undeclared entity in a slot is kept as text (no entity is created), an unknown
|
|
219
|
+
* holder falls back to the world layer, and only an unreadable statement or an
|
|
220
|
+
* exact duplicate is refused. Everything degraded or refused is reported, so the
|
|
221
|
+
* loss is visible instead of silent.
|
|
222
|
+
*/
|
|
223
|
+
commitDrafts(draft: ExtractDraft, options?: {
|
|
224
|
+
turnId?: string;
|
|
225
|
+
at?: number;
|
|
226
|
+
}): CommitResult;
|
|
227
|
+
/** Remember a person/thing/place; calling again just refreshes "last mentioned". */
|
|
228
|
+
rememberEntity(name: string, type: Entity['type'], options?: {
|
|
229
|
+
aliases?: string[];
|
|
230
|
+
attrs?: Record<string, unknown>;
|
|
231
|
+
seenAt?: number;
|
|
232
|
+
}): Entity;
|
|
233
|
+
findEntities(name: string, limit?: number): Entity[];
|
|
234
|
+
/** Write one connection between things. */
|
|
235
|
+
recordFact(spec: Omit<FactSpec, 'soulId' | 'userId'>): Fact;
|
|
236
|
+
/**
|
|
237
|
+
* Store one conclusion if the deterministic gate accepts it.
|
|
238
|
+
*
|
|
239
|
+
* A conclusion is not a new kind of record: it is a fact whose type is 结论,
|
|
240
|
+
* held by the soul itself, with one `fact` slot per citation. That way recall,
|
|
241
|
+
* export and supersede keep working, and the evidence can be followed back to
|
|
242
|
+
* the observation that produced it.
|
|
243
|
+
*/
|
|
244
|
+
recordConclusion(draft: ConclusionDraft, options: {
|
|
245
|
+
allowed: string[];
|
|
246
|
+
mergeInto?: string;
|
|
247
|
+
}): {
|
|
248
|
+
ok: boolean;
|
|
249
|
+
reason?: string;
|
|
250
|
+
fact?: Fact;
|
|
251
|
+
superseded?: string;
|
|
252
|
+
merged?: string;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* The rules we hold (ADR 0033): facts whose type is 规则 and whose 触发 slot parses.
|
|
256
|
+
*
|
|
257
|
+
* A rule without a usable trigger is skipped, not injected — an always-on rule is
|
|
258
|
+
* wallpaper, and wallpaper is how a lesson gets ignored.
|
|
259
|
+
*/
|
|
260
|
+
rules(): Rule[];
|
|
261
|
+
/**
|
|
262
|
+
* Apply a rule classification (ADR 0033): retag as 规则 and store its trigger.
|
|
263
|
+
*
|
|
264
|
+
* The trigger is what makes the rule injectable later; a rule without one would
|
|
265
|
+
* have to be always-on, which is exactly what this split exists to avoid.
|
|
266
|
+
*/
|
|
267
|
+
markRule(factId: string, classification: {
|
|
268
|
+
trigger: string;
|
|
269
|
+
action?: string;
|
|
270
|
+
}): void;
|
|
271
|
+
/** Apply a profile classification: it is about the person, not a triggered rule. */
|
|
272
|
+
markProfile(factId: string, options?: {
|
|
273
|
+
dimension?: string;
|
|
274
|
+
}): void;
|
|
275
|
+
/**
|
|
276
|
+
* Park a lesson that could not be classified.
|
|
277
|
+
*
|
|
278
|
+
* Neither bucket is safe for it: as a rule it would fire every turn, and as a
|
|
279
|
+
* profile line it would put an imperative into the description of a person. It
|
|
280
|
+
* goes to 待修, which nothing injects, and shows up in a review count.
|
|
281
|
+
*/
|
|
282
|
+
markUnclassified(factId: string): void;
|
|
283
|
+
/** The judgments about the person that are waiting to be merged. */
|
|
284
|
+
profileItems(limit?: number): Fact[];
|
|
285
|
+
/**
|
|
286
|
+
* Replace a pile of judgments with the merged ones.
|
|
287
|
+
*
|
|
288
|
+
* Each merged line becomes a 画像 fact citing its sources, and every source it
|
|
289
|
+
* covers is superseded — not deleted, so the audit trail survives the compression.
|
|
290
|
+
*/
|
|
291
|
+
applyProfileMerge(lines: ProfileLine[]): Fact[];
|
|
292
|
+
/** The merged profile, strongest first — what is injected every turn. */
|
|
293
|
+
profile(limit?: number): Fact[];
|
|
294
|
+
/** Which primitive this question needs (0020).
|
|
295
|
+
*
|
|
296
|
+
* The subject is resolved against the entities we hold, so "上次和 a 吃饭" can
|
|
297
|
+
* never be answered with the last closed topic.
|
|
298
|
+
*/
|
|
299
|
+
planRetrieval(text: string): RetrievalPlan;
|
|
300
|
+
/** The entity timeline: what is known about one person/thing, newest first. */
|
|
301
|
+
latestAbout(entityName: string, options?: {
|
|
302
|
+
predicate?: string;
|
|
303
|
+
query?: string;
|
|
304
|
+
limit?: number;
|
|
305
|
+
}): Fact[];
|
|
306
|
+
/** Mark facts as surfaced; retrieval practice slows their decay (0019). */
|
|
307
|
+
recordRecalls(factIds: string[], at?: number): void;
|
|
308
|
+
/**
|
|
309
|
+
* Close topics the conversation has moved past.
|
|
310
|
+
*
|
|
311
|
+
* Three signals, in order of strength: the host's archive list (the user said
|
|
312
|
+
* this session is done), turns elapsed inside the running session, and — only
|
|
313
|
+
* as a fallback — silence in another session.
|
|
314
|
+
*/
|
|
315
|
+
closeIdleThreads(sessionId: string, options?: {
|
|
316
|
+
idleTurns?: number;
|
|
317
|
+
idleMs?: number;
|
|
318
|
+
archivedSessionIds?: string[];
|
|
319
|
+
at?: number;
|
|
320
|
+
}): Array<{
|
|
321
|
+
id: string;
|
|
322
|
+
reason: 'archived' | 'quiet-turns' | 'quiet-time';
|
|
323
|
+
}>;
|
|
324
|
+
/** The thread the previous turn belonged to, for the attribution candidate list. */
|
|
325
|
+
threadBefore(sessionId: string, turnNo: number): string | null;
|
|
326
|
+
/**
|
|
327
|
+
* Everything a closed thread needs for consolidation: the topic, the turns in
|
|
328
|
+
* order (as text), and the facts extracted from those turns.
|
|
329
|
+
*
|
|
330
|
+
* The turns are what makes this a chapter rather than a batch: A B A means the
|
|
331
|
+
* facts alone have lost the resume.
|
|
332
|
+
*/
|
|
333
|
+
threadContext(threadId: string): {
|
|
334
|
+
thread: TurnThread;
|
|
335
|
+
turns: Array<{
|
|
336
|
+
turnNo: number;
|
|
337
|
+
userText: string;
|
|
338
|
+
assistantText: string;
|
|
339
|
+
}>;
|
|
340
|
+
facts: Fact[];
|
|
341
|
+
} | null;
|
|
342
|
+
/**
|
|
343
|
+
* What "where were we" means: the last topic that actually finished, its
|
|
344
|
+
* conclusion, and the last couple of turns.
|
|
345
|
+
*
|
|
346
|
+
* A session boundary is not a topic boundary, so this is keyed on the closed
|
|
347
|
+
* thread, not on the previous session.
|
|
348
|
+
*/
|
|
349
|
+
lastClosedThread(): {
|
|
350
|
+
thread: TurnThread;
|
|
351
|
+
conclusion: Fact | null;
|
|
352
|
+
/** Both sides of the last turns (ADR 0035): "where were we" includes what I said. */
|
|
353
|
+
turns: Array<{
|
|
354
|
+
turnNo: number;
|
|
355
|
+
userText: string;
|
|
356
|
+
assistantText: string;
|
|
357
|
+
}>;
|
|
358
|
+
} | null;
|
|
359
|
+
/** The entities a set of facts mentions — the key a conclusion is recalled by. */
|
|
360
|
+
entitiesOfFacts(factIds: string[]): string[];
|
|
361
|
+
/**
|
|
362
|
+
* Conclusions worth comparing a new one against.
|
|
363
|
+
*
|
|
364
|
+
* Ordered by shared evidence first, then recency: two conclusions drawn from
|
|
365
|
+
* the same facts are the likeliest restatements, and the recency tail catches
|
|
366
|
+
* the ones whose evidence was reworded.
|
|
367
|
+
*/
|
|
368
|
+
conclusionCandidates(evidence: string[], limit?: number): Array<{
|
|
369
|
+
id: string;
|
|
370
|
+
statement: string;
|
|
371
|
+
evidence: number;
|
|
372
|
+
}>;
|
|
373
|
+
/**
|
|
374
|
+
* Fold new evidence into a held conclusion.
|
|
375
|
+
*
|
|
376
|
+
* The statement is not rewritten: keeping it makes the merge auditable, and a
|
|
377
|
+
* richer statement is the model's job at the next natural revision, not this
|
|
378
|
+
* function's.
|
|
379
|
+
*/
|
|
380
|
+
mergeEvidence(into: string, evidence: string[]): {
|
|
381
|
+
ok: boolean;
|
|
382
|
+
reason?: string;
|
|
383
|
+
added?: number;
|
|
384
|
+
};
|
|
385
|
+
/** Current conclusions, newest first. Superseded ones are history, not state. */
|
|
386
|
+
conclusions(limit?: number): Fact[];
|
|
387
|
+
/**
|
|
388
|
+
* The newest facts, newest first — the input batch for consolidation.
|
|
389
|
+
*
|
|
390
|
+
* Observations only: conclusions share the same table, and feeding a conclusion
|
|
391
|
+
* back as evidence would let the model cite its own guess.
|
|
392
|
+
*/
|
|
393
|
+
recentFacts(limit?: number): Fact[];
|
|
394
|
+
/** How many observations exist; consolidation triggers on growth, not time. */
|
|
395
|
+
factTotal(): number;
|
|
396
|
+
/**
|
|
397
|
+
* Everything that happened to or around one entity — the "about her" path.
|
|
398
|
+
*
|
|
399
|
+
* `order: 'asc'` gives the earliest fact, which is how "when we first met"
|
|
400
|
+
* is answered without storing a special label: the model reads the timeline.
|
|
401
|
+
*/
|
|
402
|
+
factsAbout(entityId: string, options?: {
|
|
403
|
+
since?: number;
|
|
404
|
+
until?: number;
|
|
405
|
+
limit?: number;
|
|
406
|
+
order?: 'asc' | 'desc';
|
|
407
|
+
}): Fact[];
|
|
408
|
+
/** A fact plus the conversation behind it: hook first, detail on demand. */
|
|
409
|
+
factDetail(factId: string): FactDetail | null;
|
|
410
|
+
/** Open questions, highest tension first. */
|
|
411
|
+
openGaps(limit?: number): Gap[];
|
|
412
|
+
recordGap(spec: Omit<GapSpec, 'soulId' | 'userId'>): Gap;
|
|
413
|
+
closeGap(id: string, closedByFactId?: string): void;
|
|
414
|
+
findTurns(text: string, options?: {
|
|
415
|
+
sessionId?: string;
|
|
416
|
+
since?: number;
|
|
417
|
+
until?: number;
|
|
418
|
+
limit?: number;
|
|
419
|
+
}): TurnFindResult;
|
|
420
|
+
/** Conversations, most recent first, derived from the recorded exchanges. */
|
|
421
|
+
sessions(limit?: number): SessionSummary[];
|
|
422
|
+
/**
|
|
423
|
+
* What a new session inherits from the previous conversation.
|
|
424
|
+
*
|
|
425
|
+
* Measured: naive first/last picked "ok" and "停" — boundaries are not the
|
|
426
|
+
* same as signals. So the topic is the longest user message of that session
|
|
427
|
+
* (shortest messages carry no topic), and the tail is the last few exchanges
|
|
428
|
+
* whose user message has substance.
|
|
429
|
+
*/
|
|
430
|
+
handoff(currentSessionId: string, tail?: number): SessionHandoff | null;
|
|
431
|
+
/**
|
|
432
|
+
* Verb 3: on demand.
|
|
433
|
+
*
|
|
434
|
+
* Reads facts, not the legacy `memories` table: after the migration that table is
|
|
435
|
+
* inert, and a recall that still read it would stay empty forever.
|
|
436
|
+
*/
|
|
106
437
|
recall(query: string, limit?: number): ScoredMemory[];
|
|
107
438
|
/** Pure analysis, no storage. Async because the analyzer may call a model. */
|
|
108
439
|
analyze(text: string): Promise<AffectSignal>;
|
|
109
440
|
/**
|
|
110
|
-
* Verb 2: after each turn.
|
|
441
|
+
* Verb 2: after each turn. Affect and emotion only.
|
|
111
442
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
443
|
+
* Memory writing moved to `extract()` + `commitDrafts()` (facts), which is the one
|
|
444
|
+
* representation the rest of the system reads. This method used to run a second
|
|
445
|
+
* model pass (`llmExtractTurn`) behind a second gate (`gateDrafts`) into the legacy
|
|
446
|
+
* `memories` table — two prompts, two gates, two model calls for the same message.
|
|
114
447
|
*/
|
|
115
448
|
observe(turn: Turn): Promise<ObserveResult>;
|
|
116
449
|
/**
|
|
117
450
|
* Explicit form of `observe`: the host decided this is worth keeping.
|
|
118
|
-
*
|
|
451
|
+
*
|
|
452
|
+
* Writes a fact, like every other write path. This used to be a row in the legacy
|
|
453
|
+
* `memories` table, which meant "remember this" produced something the rest of the
|
|
454
|
+
* system (anchors, recall, conclusions) had to be told about separately.
|
|
119
455
|
*/
|
|
120
456
|
remember(content: string, options?: {
|
|
121
457
|
type?: MemoryType;
|
|
@@ -123,9 +459,12 @@ export declare class Soul {
|
|
|
123
459
|
visibility?: Visibility;
|
|
124
460
|
}): string;
|
|
125
461
|
/**
|
|
126
|
-
* Explicit write requested by a host or its model (RFC 0005).
|
|
127
|
-
*
|
|
128
|
-
*
|
|
462
|
+
* Explicit write requested by a host or its model (RFC 0005).
|
|
463
|
+
*
|
|
464
|
+
* Writes a fact and keeps the old dedupe contract: exact duplicate returns the
|
|
465
|
+
* existing id, and a paraphrase inside the dedupe window is treated as the same
|
|
466
|
+
* statement. Shape checks are the statement checks (non-empty, short enough);
|
|
467
|
+
* the old `gateDrafts` layer existed only to protect the retired table.
|
|
129
468
|
*/
|
|
130
469
|
rememberExplicit(input: {
|
|
131
470
|
content: string;
|
|
@@ -137,6 +476,19 @@ export declare class Soul {
|
|
|
137
476
|
duplicate?: boolean;
|
|
138
477
|
reason?: string;
|
|
139
478
|
};
|
|
479
|
+
/**
|
|
480
|
+
* One-off: move the legacy `memories` rows into facts (ADR 0032), then retire them.
|
|
481
|
+
*
|
|
482
|
+
* The old rows are the only description of the soul's own lessons that exists, so
|
|
483
|
+
* they are copied — never rewritten — and marked superseded pointing at the new
|
|
484
|
+
* fact. Re-running is safe: a statement that already has a fact is linked, not
|
|
485
|
+
* copied again.
|
|
486
|
+
*/
|
|
487
|
+
migrateLegacyMemories(): {
|
|
488
|
+
migrated: number;
|
|
489
|
+
linked: number;
|
|
490
|
+
skipped: number;
|
|
491
|
+
};
|
|
140
492
|
/** Verb 4: session end. */
|
|
141
493
|
reflect(turns: Turn[], options?: {
|
|
142
494
|
updateEmotion?: boolean;
|