@titan-design/memory 0.1.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/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +661 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henry Jewkes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @titan-design/memory
|
|
2
|
+
|
|
3
|
+
A decaying rule playbook for coding agents: what worked, what did not, with the
|
|
4
|
+
confidence of each rule derived from an append-only feedback log. The data model and
|
|
5
|
+
math follow cass-memory's `PlaybookBullet` and its ACE-style pipeline, rebuilt on the
|
|
6
|
+
titan-platform store kit so a playbook shares one SQLite file with the session graph
|
|
7
|
+
that produces its evidence.
|
|
8
|
+
|
|
9
|
+
Tier 2 of the titan-platform DAG. Depends on `store-sqlite`, `embed`, and `retrieval`.
|
|
10
|
+
Designed against active-work AW-31 (TP-13); not a port of brain's unused `memory_entries`.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { PlaybookStore, curate, recall, memoryMigration } from "@titan-design/memory";
|
|
14
|
+
import { openDatabase, runMigrations } from "@titan-design/store-sqlite";
|
|
15
|
+
|
|
16
|
+
const db = openDatabase("state.sqlite3");
|
|
17
|
+
runMigrations(db, [memoryMigration(1)]);
|
|
18
|
+
const store = new PlaybookStore(db);
|
|
19
|
+
|
|
20
|
+
// Zero-LLM path: the agent that just learned something writes it down.
|
|
21
|
+
curate(store, [{ type: "add", content: "Pin npm to 11 in release jobs", tags: ["ci"] }], {
|
|
22
|
+
provenance: { sessionRef: "session:abc", byteOffset: 4096 },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Before the next task: what does the playbook say about this?
|
|
26
|
+
const { bullets, antiPatterns, deprecatedWarnings, degraded } = await recall(store, "release job npm");
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Model
|
|
30
|
+
|
|
31
|
+
- **Bullet**: content, category, tags, scope, type (`rule` | `anti-pattern`), kind, source,
|
|
32
|
+
state (`draft` | `active` | `retired`), maturity (`candidate` | `established` | `proven` |
|
|
33
|
+
`deprecated`), pinned, half-life, provenance (`{ sessionRef, byteOffset }`).
|
|
34
|
+
- **Feedback** is an immutable log of `helpful` / `harmful` events. Counts and scores are
|
|
35
|
+
computed at read time, so changing the decay parameters re-scores history for free.
|
|
36
|
+
- **Storage** is the kit: bullets are interval entities, `supersedes` is an edge,
|
|
37
|
+
embeddings live in `cache_blob` keyed by content hash, per-session progress is a watermark.
|
|
38
|
+
|
|
39
|
+
## Scoring (`scoring.ts`)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
decayedValue(event) = 0.5 ^ (ageDays / halfLifeDays) halfLifeDays = 90 by default
|
|
43
|
+
effectiveScore = (decayedHelpful - 4 * decayedHarmful) * { candidate .5, established 1, proven 1.5, deprecated 0 }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Maturity: under three events is a candidate; over 30% harmful is deprecated; ten helpful
|
|
47
|
+
with under 10% harmful is proven; otherwise established. Promotion jumps to the earned
|
|
48
|
+
rung, demotion drops one rung per pass, a score under -3 deprecates outright, and pinned
|
|
49
|
+
bullets never move. Staleness is separate from decay: silence for 180 days flags a bullet
|
|
50
|
+
for re-validation.
|
|
51
|
+
|
|
52
|
+
## Curation (`curate`)
|
|
53
|
+
|
|
54
|
+
Deterministic, no model involved. Deltas are `add` | `helpful` | `harmful` | `replace` |
|
|
55
|
+
`deprecate` | `merge` (zod-validated, `PlaybookDeltaSchema`). The curator:
|
|
56
|
+
|
|
57
|
+
1. drops duplicate deltas within the batch (one vote per bullet per batch);
|
|
58
|
+
2. refuses any `add` near a human-blocked pattern (`store.block`);
|
|
59
|
+
3. folds an exact or Jaccard >= 0.85 duplicate `add` into a `helpful` on the existing
|
|
60
|
+
bullet, unless the polarity differs, in which case it is added and flagged as a conflict;
|
|
61
|
+
4. applies replacements and merges by adding a successor and retiring the originals with
|
|
62
|
+
`supersedes` edges;
|
|
63
|
+
5. inverts a non-pinned rule whose decayed harm is at least 3 and more than twice its
|
|
64
|
+
help into an `AVOID:` anti-pattern;
|
|
65
|
+
6. runs the maturity pass.
|
|
66
|
+
|
|
67
|
+
Provenance is an option on `curate`, never a field on a delta, so a model cannot claim a
|
|
68
|
+
source it did not have.
|
|
69
|
+
|
|
70
|
+
## Recall (`recall`)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
keywordScore = 3 per exact token + 1 per substring + 5 per tag match
|
|
74
|
+
relevanceScore = keyword * (1 - w) + similarity * w w = 0.6 when a semantic index is supplied
|
|
75
|
+
finalScore = relevanceScore * max(0.1, effectiveScore)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Filtered on relevance (not final score), so a topical bullet with low confidence still
|
|
79
|
+
surfaces, near the bottom. Returns `bullets`, `antiPatterns`, `deprecatedWarnings`, and a
|
|
80
|
+
`degraded` list explaining why semantic scoring fell back to keywords, if it did.
|
|
81
|
+
`MemoryVectors` builds the semantic index from `cache_blob` with any `Embedder`.
|
|
82
|
+
|
|
83
|
+
## Reflection (`reflectSession`)
|
|
84
|
+
|
|
85
|
+
The one stage that may call a model. You supply a `Reflector` that turns a session diary
|
|
86
|
+
into proposed deltas; the loop feeds the growing playbook and prior deltas back in for up to
|
|
87
|
+
three iterations, stops on nothing new or at 50 deltas, validates every item, then curates
|
|
88
|
+
with the session's provenance and advances the watermark. Invalid output is reported in
|
|
89
|
+
`rejected`, never thrown.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Migration, Db, WatermarkTable } from '@titan-design/store-sqlite';
|
|
3
|
+
import { Embedder } from '@titan-design/embed';
|
|
4
|
+
import { VectorIndex, Degradation } from '@titan-design/retrieval';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The playbook data model, borrowed from cass-memory's `PlaybookBullet`. Counts
|
|
8
|
+
* are never stored: they are derived from the append-only feedback log at read
|
|
9
|
+
* time, so decay parameters can change without re-running reflection.
|
|
10
|
+
*/
|
|
11
|
+
declare const BULLET_SCOPES: readonly ["global", "workspace", "language", "framework", "task"];
|
|
12
|
+
declare const BULLET_TYPES: readonly ["rule", "anti-pattern"];
|
|
13
|
+
declare const BULLET_KINDS: readonly ["project_convention", "stack_pattern", "workflow_rule", "anti_pattern"];
|
|
14
|
+
declare const BULLET_SOURCES: readonly ["learned", "community", "manual", "custom"];
|
|
15
|
+
declare const BULLET_STATES: readonly ["draft", "active", "retired"];
|
|
16
|
+
declare const MATURITIES: readonly ["candidate", "established", "proven", "deprecated"];
|
|
17
|
+
type BulletScope = (typeof BULLET_SCOPES)[number];
|
|
18
|
+
type BulletType = (typeof BULLET_TYPES)[number];
|
|
19
|
+
type BulletKind = (typeof BULLET_KINDS)[number];
|
|
20
|
+
type BulletSource = (typeof BULLET_SOURCES)[number];
|
|
21
|
+
type BulletState = (typeof BULLET_STATES)[number];
|
|
22
|
+
type Maturity = (typeof MATURITIES)[number];
|
|
23
|
+
type FeedbackType = "helpful" | "harmful";
|
|
24
|
+
/** Where a bullet's evidence lives: a session ref plus a byte offset into its transcript. */
|
|
25
|
+
interface Provenance {
|
|
26
|
+
sessionRef: string;
|
|
27
|
+
byteOffset?: number;
|
|
28
|
+
}
|
|
29
|
+
interface Bullet {
|
|
30
|
+
id: string;
|
|
31
|
+
content: string;
|
|
32
|
+
category: string;
|
|
33
|
+
tags: string[];
|
|
34
|
+
scope: BulletScope;
|
|
35
|
+
type: BulletType;
|
|
36
|
+
kind: BulletKind;
|
|
37
|
+
isNegative: boolean;
|
|
38
|
+
source: BulletSource;
|
|
39
|
+
state: BulletState;
|
|
40
|
+
maturity: Maturity;
|
|
41
|
+
pinned: boolean;
|
|
42
|
+
pinnedReason: string | null;
|
|
43
|
+
/** Set when retired in favour of another bullet; mirrored by a `supersedes` edge. */
|
|
44
|
+
replacedBy: string | null;
|
|
45
|
+
deprecatedAt: string | null;
|
|
46
|
+
deprecationReason: string | null;
|
|
47
|
+
halfLifeDays: number;
|
|
48
|
+
sourceSessions: Provenance[];
|
|
49
|
+
reasoning: string | null;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
}
|
|
53
|
+
interface NewBullet {
|
|
54
|
+
id?: string;
|
|
55
|
+
content: string;
|
|
56
|
+
category?: string;
|
|
57
|
+
tags?: string[];
|
|
58
|
+
scope?: BulletScope;
|
|
59
|
+
type?: BulletType;
|
|
60
|
+
kind?: BulletKind;
|
|
61
|
+
isNegative?: boolean;
|
|
62
|
+
source?: BulletSource;
|
|
63
|
+
state?: BulletState;
|
|
64
|
+
pinned?: boolean;
|
|
65
|
+
pinnedReason?: string | null;
|
|
66
|
+
halfLifeDays?: number;
|
|
67
|
+
sourceSessions?: Provenance[];
|
|
68
|
+
reasoning?: string | null;
|
|
69
|
+
}
|
|
70
|
+
/** One line of the immutable feedback log. */
|
|
71
|
+
interface FeedbackEvent {
|
|
72
|
+
id: number;
|
|
73
|
+
bulletId: string;
|
|
74
|
+
type: FeedbackType;
|
|
75
|
+
at: string;
|
|
76
|
+
sessionRef: string | null;
|
|
77
|
+
reason: string | null;
|
|
78
|
+
}
|
|
79
|
+
interface DecayedCounts {
|
|
80
|
+
helpful: number;
|
|
81
|
+
harmful: number;
|
|
82
|
+
}
|
|
83
|
+
interface ScoredBullet extends Bullet {
|
|
84
|
+
helpfulCount: number;
|
|
85
|
+
harmfulCount: number;
|
|
86
|
+
decayedHelpful: number;
|
|
87
|
+
decayedHarmful: number;
|
|
88
|
+
effectiveScore: number;
|
|
89
|
+
/** Latest feedback, or creation when there is none; the input to staleness. */
|
|
90
|
+
lastEvidenceAt: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* What a reflector proposes. Provenance is deliberately absent: the curator
|
|
94
|
+
* stamps it from the session it is actually processing, never from the model.
|
|
95
|
+
*/
|
|
96
|
+
declare const addDelta: z.ZodObject<{
|
|
97
|
+
type: z.ZodLiteral<"add">;
|
|
98
|
+
content: z.ZodString;
|
|
99
|
+
category: z.ZodDefault<z.ZodString>;
|
|
100
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
101
|
+
scope: z.ZodDefault<z.ZodEnum<{
|
|
102
|
+
global: "global";
|
|
103
|
+
workspace: "workspace";
|
|
104
|
+
language: "language";
|
|
105
|
+
framework: "framework";
|
|
106
|
+
task: "task";
|
|
107
|
+
}>>;
|
|
108
|
+
kind: z.ZodDefault<z.ZodEnum<{
|
|
109
|
+
project_convention: "project_convention";
|
|
110
|
+
stack_pattern: "stack_pattern";
|
|
111
|
+
workflow_rule: "workflow_rule";
|
|
112
|
+
anti_pattern: "anti_pattern";
|
|
113
|
+
}>>;
|
|
114
|
+
isNegative: z.ZodDefault<z.ZodBoolean>;
|
|
115
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
116
|
+
}, z.core.$strip>;
|
|
117
|
+
declare const PlaybookDeltaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
118
|
+
type: z.ZodLiteral<"add">;
|
|
119
|
+
content: z.ZodString;
|
|
120
|
+
category: z.ZodDefault<z.ZodString>;
|
|
121
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
122
|
+
scope: z.ZodDefault<z.ZodEnum<{
|
|
123
|
+
global: "global";
|
|
124
|
+
workspace: "workspace";
|
|
125
|
+
language: "language";
|
|
126
|
+
framework: "framework";
|
|
127
|
+
task: "task";
|
|
128
|
+
}>>;
|
|
129
|
+
kind: z.ZodDefault<z.ZodEnum<{
|
|
130
|
+
project_convention: "project_convention";
|
|
131
|
+
stack_pattern: "stack_pattern";
|
|
132
|
+
workflow_rule: "workflow_rule";
|
|
133
|
+
anti_pattern: "anti_pattern";
|
|
134
|
+
}>>;
|
|
135
|
+
isNegative: z.ZodDefault<z.ZodBoolean>;
|
|
136
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
137
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
138
|
+
type: z.ZodLiteral<FeedbackType>;
|
|
139
|
+
bulletId: z.ZodString;
|
|
140
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
141
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
142
|
+
type: z.ZodLiteral<FeedbackType>;
|
|
143
|
+
bulletId: z.ZodString;
|
|
144
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
145
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
146
|
+
type: z.ZodLiteral<"replace">;
|
|
147
|
+
bulletId: z.ZodString;
|
|
148
|
+
content: z.ZodString;
|
|
149
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
150
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
151
|
+
type: z.ZodLiteral<"deprecate">;
|
|
152
|
+
bulletId: z.ZodString;
|
|
153
|
+
reason: z.ZodString;
|
|
154
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
155
|
+
type: z.ZodLiteral<"merge">;
|
|
156
|
+
bulletIds: z.ZodArray<z.ZodString>;
|
|
157
|
+
content: z.ZodString;
|
|
158
|
+
reasoning: z.ZodOptional<z.ZodString>;
|
|
159
|
+
}, z.core.$strip>], "type">;
|
|
160
|
+
/** What callers and reflectors write; defaults may be omitted. */
|
|
161
|
+
type PlaybookDelta = z.input<typeof PlaybookDeltaSchema>;
|
|
162
|
+
/** What the curator works on, after parsing fills the defaults in. */
|
|
163
|
+
type ParsedDelta = z.output<typeof PlaybookDeltaSchema>;
|
|
164
|
+
type AddDelta = z.output<typeof addDelta>;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Four kit tables plus one domain table. Bullets are interval entities, the
|
|
168
|
+
* `supersedes` relation lives in the edge table, embeddings in cache_blob keyed
|
|
169
|
+
* by content hash, and per-session progress in the watermark table.
|
|
170
|
+
*/
|
|
171
|
+
interface MemoryTables {
|
|
172
|
+
entity: string;
|
|
173
|
+
edge: string;
|
|
174
|
+
cacheBlob: string;
|
|
175
|
+
watermark: string;
|
|
176
|
+
feedback: string;
|
|
177
|
+
}
|
|
178
|
+
declare const DEFAULT_MEMORY_TABLES: MemoryTables;
|
|
179
|
+
declare const SUPERSEDES = "supersedes";
|
|
180
|
+
declare function feedbackTableDdl(name?: string): string;
|
|
181
|
+
declare function memoryDdl(tables?: MemoryTables): string;
|
|
182
|
+
/** Drop into a product's migration list so the playbook shares its database. */
|
|
183
|
+
declare function memoryMigration(version: number, tables?: MemoryTables): Migration;
|
|
184
|
+
|
|
185
|
+
/** cass-memory's exact numbers; a bullet may override the half-life, nothing else. */
|
|
186
|
+
declare const DEFAULT_HALF_LIFE_DAYS = 90;
|
|
187
|
+
declare const HARMFUL_WEIGHT = 4;
|
|
188
|
+
declare const HARD_DEPRECATE_SCORE = -3;
|
|
189
|
+
declare const MATURITY_MULTIPLIER: Record<Maturity, number>;
|
|
190
|
+
declare function decayedValue(at: string, now: Date, halfLifeDays?: number): number;
|
|
191
|
+
declare function decayedCounts(events: readonly FeedbackEvent[], now: Date, halfLifeDays?: number): DecayedCounts;
|
|
192
|
+
declare function effectiveScore(counts: DecayedCounts, maturity: Maturity): number;
|
|
193
|
+
/** The maturity the decayed counts alone would assign, ignoring history. */
|
|
194
|
+
declare function maturityFor(counts: DecayedCounts): Maturity;
|
|
195
|
+
/**
|
|
196
|
+
* Promotion jumps straight to the earned rung; demotion drops one rung per
|
|
197
|
+
* pass, except that a score below the hard floor deprecates outright. Pinned
|
|
198
|
+
* bullets never move.
|
|
199
|
+
*/
|
|
200
|
+
declare function nextMaturity(current: Maturity, counts: DecayedCounts, pinned: boolean): Maturity;
|
|
201
|
+
interface StalenessOptions {
|
|
202
|
+
/** Days without any feedback before a bullet is flagged for re-validation. */
|
|
203
|
+
staleAfterDays?: number;
|
|
204
|
+
}
|
|
205
|
+
/** Staleness is about silence, not decay: a bullet nobody has confirmed or refuted lately. */
|
|
206
|
+
declare function isStale(lastEvidenceAt: string, now: Date, { staleAfterDays }?: StalenessOptions): boolean;
|
|
207
|
+
|
|
208
|
+
/** Lowercase alphanumeric tokens of two or more characters, as a set. */
|
|
209
|
+
declare function tokenize(text: string): Set<string>;
|
|
210
|
+
/** Case- and whitespace-insensitive form used for exact-duplicate detection. */
|
|
211
|
+
declare function normalizeContent(text: string): string;
|
|
212
|
+
declare function contentKey(text: string): string;
|
|
213
|
+
declare function jaccard(a: string, b: string): number;
|
|
214
|
+
|
|
215
|
+
declare const bulletRef: (id: string) => string;
|
|
216
|
+
interface PlaybookStoreOptions {
|
|
217
|
+
tables?: MemoryTables;
|
|
218
|
+
now?: () => Date;
|
|
219
|
+
}
|
|
220
|
+
interface FeedbackInput {
|
|
221
|
+
sessionRef?: string | null;
|
|
222
|
+
reason?: string | null;
|
|
223
|
+
at?: string;
|
|
224
|
+
}
|
|
225
|
+
interface BlockedPattern {
|
|
226
|
+
pattern: string;
|
|
227
|
+
reason: string;
|
|
228
|
+
blockedAt: string;
|
|
229
|
+
}
|
|
230
|
+
declare class BulletNotFound extends Error {
|
|
231
|
+
constructor(id: string);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Bullets are interval entities (`kind = bullet`, name = category, attrs = the
|
|
235
|
+
* rest), so retirement is an attrs change plus a `supersedes` edge, never a
|
|
236
|
+
* delete. Feedback is an append-only log; nothing here ever updates or removes
|
|
237
|
+
* an event.
|
|
238
|
+
*/
|
|
239
|
+
declare class PlaybookStore {
|
|
240
|
+
readonly tables: MemoryTables;
|
|
241
|
+
private readonly entities;
|
|
242
|
+
private readonly edges;
|
|
243
|
+
private readonly now;
|
|
244
|
+
private readonly insertFeedback;
|
|
245
|
+
private readonly feedbackByBullet;
|
|
246
|
+
private readonly allFeedback;
|
|
247
|
+
constructor(db: Db, options?: PlaybookStoreOptions);
|
|
248
|
+
add(input: NewBullet): Bullet;
|
|
249
|
+
get(id: string): Bullet | undefined;
|
|
250
|
+
require(id: string): Bullet;
|
|
251
|
+
/** Live bullets only, unless `includeRetired`; ordered by id for determinism. */
|
|
252
|
+
list({ includeRetired }?: {
|
|
253
|
+
includeRetired?: boolean;
|
|
254
|
+
}): Bullet[];
|
|
255
|
+
update(id: string, patch: Partial<Omit<Bullet, "id" | "createdAt">>): Bullet;
|
|
256
|
+
/** Retire a bullet. With `replacedBy`, also assert `replacedBy -supersedes-> id`. */
|
|
257
|
+
deprecate(id: string, reason: string, replacedBy?: string | null): Bullet;
|
|
258
|
+
/** Ids of the bullets this one replaced, following live `supersedes` edges. */
|
|
259
|
+
supersededBy(id: string): string[];
|
|
260
|
+
recordFeedback(id: string, type: FeedbackType, input?: FeedbackInput): FeedbackEvent;
|
|
261
|
+
feedbackFor(id: string): FeedbackEvent[];
|
|
262
|
+
/** Every event grouped by bullet id, one query, for scoring the whole playbook. */
|
|
263
|
+
feedbackByBulletId(): Map<string, FeedbackEvent[]>;
|
|
264
|
+
/** A content pattern a human has banned; the curator refuses to re-learn anything near it. */
|
|
265
|
+
block(pattern: string, reason: string): BlockedPattern;
|
|
266
|
+
blockedPatterns(): BlockedPattern[];
|
|
267
|
+
private write;
|
|
268
|
+
}
|
|
269
|
+
declare function newBulletId(now?: Date): string;
|
|
270
|
+
|
|
271
|
+
declare function scoreBullet(bullet: Bullet, events: readonly FeedbackEvent[], now: Date): ScoredBullet;
|
|
272
|
+
interface ScoreOptions {
|
|
273
|
+
includeRetired?: boolean;
|
|
274
|
+
}
|
|
275
|
+
/** Every bullet with its decayed counts and score as of `now`. One feedback query for the whole playbook. */
|
|
276
|
+
declare function scorePlaybook(store: PlaybookStore, now: Date, options?: ScoreOptions): ScoredBullet[];
|
|
277
|
+
interface MaturityChange {
|
|
278
|
+
bulletId: string;
|
|
279
|
+
from: Maturity;
|
|
280
|
+
to: Maturity;
|
|
281
|
+
}
|
|
282
|
+
/** The promotion/demotion pass. A bullet that lands on `deprecated` is retired. */
|
|
283
|
+
declare function applyMaturity(store: PlaybookStore, now: Date): MaturityChange[];
|
|
284
|
+
/** Live bullets nobody has confirmed or refuted lately: candidates for re-validation, not for removal. */
|
|
285
|
+
declare function staleBullets(store: PlaybookStore, now: Date, options?: StalenessOptions): ScoredBullet[];
|
|
286
|
+
|
|
287
|
+
interface InversionOptions {
|
|
288
|
+
/** Decayed harmful mass a rule needs before it can flip into an anti-pattern. */
|
|
289
|
+
minHarmful?: number;
|
|
290
|
+
/** And by how much harmful must outweigh helpful. */
|
|
291
|
+
harmfulToHelpfulRatio?: number;
|
|
292
|
+
}
|
|
293
|
+
interface CurateOptions {
|
|
294
|
+
/** Stamped onto every bullet this batch creates. Never taken from the deltas themselves. */
|
|
295
|
+
provenance?: Provenance;
|
|
296
|
+
now?: () => Date;
|
|
297
|
+
/** Jaccard similarity at which an `add` folds into a `helpful` on the existing bullet. */
|
|
298
|
+
nearDupThreshold?: number;
|
|
299
|
+
/** Jaccard similarity at which two rules are flagged as possibly contradicting. */
|
|
300
|
+
conflictThreshold?: number;
|
|
301
|
+
inversion?: InversionOptions;
|
|
302
|
+
}
|
|
303
|
+
interface Conflict {
|
|
304
|
+
bulletId: string;
|
|
305
|
+
otherId: string;
|
|
306
|
+
reason: string;
|
|
307
|
+
}
|
|
308
|
+
interface CurationReport {
|
|
309
|
+
added: string[];
|
|
310
|
+
reinforced: string[];
|
|
311
|
+
penalized: string[];
|
|
312
|
+
deprecated: string[];
|
|
313
|
+
inverted: {
|
|
314
|
+
from: string;
|
|
315
|
+
to: string;
|
|
316
|
+
}[];
|
|
317
|
+
blocked: string[];
|
|
318
|
+
skipped: {
|
|
319
|
+
delta: ParsedDelta;
|
|
320
|
+
reason: string;
|
|
321
|
+
}[];
|
|
322
|
+
conflicts: Conflict[];
|
|
323
|
+
maturityChanges: MaturityChange[];
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* cass-memory's curator: zero LLM, fully deterministic. Dedup by content hash
|
|
327
|
+
* and near-dup Jaccard, apply the batch, invert rules that keep hurting into
|
|
328
|
+
* `AVOID:` anti-patterns, then run the maturity pass.
|
|
329
|
+
*/
|
|
330
|
+
declare function curate(store: PlaybookStore, deltas: readonly PlaybookDelta[], options?: CurateOptions): CurationReport;
|
|
331
|
+
|
|
332
|
+
interface SemanticOptions {
|
|
333
|
+
embedder: Embedder;
|
|
334
|
+
index: VectorIndex;
|
|
335
|
+
/** Share of relevance taken from vector similarity; the rest is keyword score. */
|
|
336
|
+
weight?: number;
|
|
337
|
+
timeoutMs?: number;
|
|
338
|
+
}
|
|
339
|
+
interface RecallOptions {
|
|
340
|
+
limit?: number;
|
|
341
|
+
/** Extra tags to match on top of the query's own tokens. */
|
|
342
|
+
tags?: string[];
|
|
343
|
+
/** Keep bullets scoped `global` or to this scope. */
|
|
344
|
+
scope?: BulletScope;
|
|
345
|
+
now?: Date;
|
|
346
|
+
/** Filter on relevance, not final score, so a low-confidence but topical bullet still surfaces. */
|
|
347
|
+
minRelevance?: number;
|
|
348
|
+
semantic?: SemanticOptions;
|
|
349
|
+
}
|
|
350
|
+
interface RecalledBullet extends ScoredBullet {
|
|
351
|
+
keywordScore: number;
|
|
352
|
+
semanticScore: number | null;
|
|
353
|
+
relevanceScore: number;
|
|
354
|
+
finalScore: number;
|
|
355
|
+
}
|
|
356
|
+
interface RecallResult {
|
|
357
|
+
bullets: RecalledBullet[];
|
|
358
|
+
antiPatterns: RecalledBullet[];
|
|
359
|
+
deprecatedWarnings: RecalledBullet[];
|
|
360
|
+
/** Why semantic scoring fell back to keywords, if it did. Never silent. */
|
|
361
|
+
degraded: Degradation[];
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* cass-memory's `cm context`: keyword score blended with optional vector
|
|
365
|
+
* similarity, multiplied by the bullet's floored confidence, so what comes back
|
|
366
|
+
* is topical first and trusted second.
|
|
367
|
+
*/
|
|
368
|
+
declare function recall(store: PlaybookStore, query: string, options?: RecallOptions): Promise<RecallResult>;
|
|
369
|
+
declare function keywordScore(content: string, tags: readonly string[], query: string, extraTags?: readonly string[]): number;
|
|
370
|
+
|
|
371
|
+
interface MemoryVectorsOptions {
|
|
372
|
+
tables?: MemoryTables;
|
|
373
|
+
namespace?: string;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Bullet embeddings in cache_blob, keyed by normalized content and the
|
|
377
|
+
* embedder's model, so a re-worded bullet re-embeds and everything else is a
|
|
378
|
+
* cache hit. The index is rebuilt from the cache; nothing is stored twice.
|
|
379
|
+
*/
|
|
380
|
+
declare class MemoryVectors {
|
|
381
|
+
private readonly embedder;
|
|
382
|
+
private readonly cache;
|
|
383
|
+
private readonly namespace;
|
|
384
|
+
constructor(db: Db, embedder: Embedder, options?: MemoryVectorsOptions);
|
|
385
|
+
/** Embed every bullet the cache does not have, in one call. Returns how many were computed. */
|
|
386
|
+
ensure(bullets: readonly Bullet[]): Promise<number>;
|
|
387
|
+
/** A searchable index over these bullets, embedding any misses first. */
|
|
388
|
+
index(bullets: readonly Bullet[]): Promise<VectorIndex>;
|
|
389
|
+
private keyFor;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
interface ReflectInput {
|
|
393
|
+
sessionRef: string;
|
|
394
|
+
/** Whatever the caller distilled from the session: a diary, a subgraph summary, raw notes. */
|
|
395
|
+
diary: string;
|
|
396
|
+
iteration: number;
|
|
397
|
+
bullets: Bullet[];
|
|
398
|
+
priorDeltas: ParsedDelta[];
|
|
399
|
+
}
|
|
400
|
+
/** The one stage that may call a model. Returns anything; the deltas are validated here, not trusted. */
|
|
401
|
+
type Reflector = (input: ReflectInput) => Promise<unknown>;
|
|
402
|
+
interface ReflectSessionInput {
|
|
403
|
+
sessionRef: string;
|
|
404
|
+
diary: string;
|
|
405
|
+
byteOffset?: number;
|
|
406
|
+
}
|
|
407
|
+
interface ReflectOptions extends Omit<CurateOptions, "provenance"> {
|
|
408
|
+
maxIterations?: number;
|
|
409
|
+
maxDeltas?: number;
|
|
410
|
+
}
|
|
411
|
+
interface RejectedDelta {
|
|
412
|
+
iteration: number;
|
|
413
|
+
index: number;
|
|
414
|
+
reason: string;
|
|
415
|
+
}
|
|
416
|
+
interface ReflectionResult {
|
|
417
|
+
deltas: ParsedDelta[];
|
|
418
|
+
rejected: RejectedDelta[];
|
|
419
|
+
iterations: number;
|
|
420
|
+
report: CurationReport;
|
|
421
|
+
}
|
|
422
|
+
interface ReflectDeps {
|
|
423
|
+
store: PlaybookStore;
|
|
424
|
+
/** When present, the session's offset is recorded so incremental runs know where they stopped. */
|
|
425
|
+
watermark?: WatermarkTable;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* cass-memory's multi-iteration reflect loop: feed the growing playbook and the
|
|
429
|
+
* deltas so far back in, stop early on nothing new or at the cap, then curate
|
|
430
|
+
* with provenance stamped from the input, never from the model.
|
|
431
|
+
*/
|
|
432
|
+
declare function reflectSession(deps: ReflectDeps, input: ReflectSessionInput, reflector: Reflector, options?: ReflectOptions): Promise<ReflectionResult>;
|
|
433
|
+
declare function parseDeltas(raw: unknown, seen: Set<string>, iteration: number, rejected: RejectedDelta[]): ParsedDelta[];
|
|
434
|
+
|
|
435
|
+
export { type AddDelta, BULLET_KINDS, BULLET_SCOPES, BULLET_SOURCES, BULLET_STATES, BULLET_TYPES, type BlockedPattern, type Bullet, type BulletKind, BulletNotFound, type BulletScope, type BulletSource, type BulletState, type BulletType, type Conflict, type CurateOptions, type CurationReport, DEFAULT_HALF_LIFE_DAYS, DEFAULT_MEMORY_TABLES, type DecayedCounts, type FeedbackEvent, type FeedbackInput, type FeedbackType, HARD_DEPRECATE_SCORE, HARMFUL_WEIGHT, type InversionOptions, MATURITIES, MATURITY_MULTIPLIER, type Maturity, type MaturityChange, type MemoryTables, MemoryVectors, type MemoryVectorsOptions, type NewBullet, type ParsedDelta, type PlaybookDelta, PlaybookDeltaSchema, PlaybookStore, type PlaybookStoreOptions, type Provenance, type RecallOptions, type RecallResult, type RecalledBullet, type ReflectDeps, type ReflectInput, type ReflectOptions, type ReflectSessionInput, type ReflectionResult, type Reflector, type RejectedDelta, SUPERSEDES, type ScoreOptions, type ScoredBullet, type SemanticOptions, type StalenessOptions, applyMaturity, bulletRef, contentKey, curate, decayedCounts, decayedValue, effectiveScore, feedbackTableDdl, isStale, jaccard, keywordScore, maturityFor, memoryDdl, memoryMigration, newBulletId, nextMaturity, normalizeContent, parseDeltas, recall, reflectSession, scoreBullet, scorePlaybook, staleBullets, tokenize };
|