@loreai/core 0.20.1 → 0.21.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/bun/config.d.ts +1 -0
- package/dist/bun/config.d.ts.map +1 -1
- package/dist/bun/curator.d.ts +2 -0
- package/dist/bun/curator.d.ts.map +1 -1
- package/dist/bun/db.d.ts +10 -0
- package/dist/bun/db.d.ts.map +1 -1
- package/dist/bun/distillation.d.ts +4 -0
- package/dist/bun/distillation.d.ts.map +1 -1
- package/dist/bun/gradient.d.ts +26 -4
- package/dist/bun/gradient.d.ts.map +1 -1
- package/dist/bun/index.d.ts +2 -2
- package/dist/bun/index.d.ts.map +1 -1
- package/dist/bun/index.js +150 -17
- package/dist/bun/index.js.map +3 -3
- package/dist/bun/ltm.d.ts +10 -0
- package/dist/bun/ltm.d.ts.map +1 -1
- package/dist/bun/prompt.d.ts +1 -1
- package/dist/bun/prompt.d.ts.map +1 -1
- package/dist/node/config.d.ts +1 -0
- package/dist/node/config.d.ts.map +1 -1
- package/dist/node/curator.d.ts +2 -0
- package/dist/node/curator.d.ts.map +1 -1
- package/dist/node/db.d.ts +10 -0
- package/dist/node/db.d.ts.map +1 -1
- package/dist/node/distillation.d.ts +4 -0
- package/dist/node/distillation.d.ts.map +1 -1
- package/dist/node/gradient.d.ts +26 -4
- package/dist/node/gradient.d.ts.map +1 -1
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +150 -17
- package/dist/node/index.js.map +3 -3
- package/dist/node/ltm.d.ts +10 -0
- package/dist/node/ltm.d.ts.map +1 -1
- package/dist/node/prompt.d.ts +1 -1
- package/dist/node/prompt.d.ts.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/curator.d.ts +2 -0
- package/dist/types/curator.d.ts.map +1 -1
- package/dist/types/db.d.ts +10 -0
- package/dist/types/db.d.ts.map +1 -1
- package/dist/types/distillation.d.ts +4 -0
- package/dist/types/distillation.d.ts.map +1 -1
- package/dist/types/gradient.d.ts +26 -4
- package/dist/types/gradient.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ltm.d.ts +10 -0
- package/dist/types/ltm.d.ts.map +1 -1
- package/dist/types/prompt.d.ts +1 -1
- package/dist/types/prompt.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +4 -2
- package/src/curator.ts +3 -0
- package/src/db.ts +52 -1
- package/src/distillation.ts +17 -1
- package/src/gradient.ts +65 -13
- package/src/index.ts +5 -0
- package/src/ltm.ts +78 -4
- package/src/prompt.ts +18 -2
package/src/distillation.ts
CHANGED
|
@@ -147,6 +147,11 @@ function splitSegments(
|
|
|
147
147
|
const totalTokens = messages.reduce((s, m) => s + m.tokens, 0);
|
|
148
148
|
if (totalTokens <= maxTokens) return [messages];
|
|
149
149
|
|
|
150
|
+
// Cannot subdivide a single message — yield as-is (oversized but indivisible).
|
|
151
|
+
// Prevents infinite recursion when one message exceeds maxTokens (e.g., a 50KB+
|
|
152
|
+
// tool output where Math.ceil(content.length / 3) > 16384).
|
|
153
|
+
if (messages.length <= 1) return [messages];
|
|
154
|
+
|
|
150
155
|
// Find the split point: prefer the largest time gap if it's significant
|
|
151
156
|
const splitIdx = findSplitIndex(messages, maxTokens);
|
|
152
157
|
|
|
@@ -623,6 +628,10 @@ export async function run(input: {
|
|
|
623
628
|
skipMeta?: boolean;
|
|
624
629
|
urgent?: boolean;
|
|
625
630
|
callType?: "batch" | "direct";
|
|
631
|
+
/** Override the meta-distillation gen-0 threshold. When set, meta-distillation
|
|
632
|
+
* triggers at this count instead of `cfg.distillation.metaThreshold`.
|
|
633
|
+
* Used by the urgent-distillation path to consolidate earlier under bust pressure. */
|
|
634
|
+
metaThresholdOverride?: number;
|
|
626
635
|
}): Promise<{ rounds: number; distilled: number }> {
|
|
627
636
|
return distillLimiter.get(input.sessionID)(() => runInner(input));
|
|
628
637
|
}
|
|
@@ -648,6 +657,8 @@ async function runInner(input: {
|
|
|
648
657
|
/** Whether the LLM call will use batch or direct pricing. Recorded on the
|
|
649
658
|
* distillation row for accurate historical cost estimates. */
|
|
650
659
|
callType?: "batch" | "direct";
|
|
660
|
+
/** Override the meta-distillation gen-0 threshold (see run()). */
|
|
661
|
+
metaThresholdOverride?: number;
|
|
651
662
|
}): Promise<{ rounds: number; distilled: number }> {
|
|
652
663
|
// Reset orphaned messages (marked distilled by a deleted/migrated distillation)
|
|
653
664
|
const orphans = resetOrphans(input.projectPath, input.sessionID);
|
|
@@ -708,10 +719,15 @@ async function runInner(input: {
|
|
|
708
719
|
// Check if meta-distillation is needed (skip when cache is warm to avoid
|
|
709
720
|
// prefix cache invalidation — row IDs change after meta-distill, busting
|
|
710
721
|
// the prompt cache on the next turn).
|
|
722
|
+
// Clamp override to min 2 — meta-distillation with < 2 gen-0 segments is pointless.
|
|
723
|
+
const effectiveMetaThreshold = Math.max(
|
|
724
|
+
2,
|
|
725
|
+
input.metaThresholdOverride ?? cfg.distillation.metaThreshold,
|
|
726
|
+
);
|
|
711
727
|
if (
|
|
712
728
|
!input.skipMeta &&
|
|
713
729
|
gen0Count(input.projectPath, input.sessionID) >=
|
|
714
|
-
|
|
730
|
+
effectiveMetaThreshold
|
|
715
731
|
) {
|
|
716
732
|
// Call inner directly — we're already under the per-session limiter.
|
|
717
733
|
await metaDistillInner({
|
package/src/gradient.ts
CHANGED
|
@@ -135,10 +135,11 @@ export function getTier(tokens: number): number {
|
|
|
135
135
|
*
|
|
136
136
|
* A "bust" is when cache_write > 50% of total input tokens.
|
|
137
137
|
*
|
|
138
|
-
* @param cacheWrite
|
|
139
|
-
* @param cacheRead
|
|
140
|
-
* @param inputTokens -
|
|
141
|
-
*
|
|
138
|
+
* @param cacheWrite - cache_creation_input_tokens from the API response
|
|
139
|
+
* @param cacheRead - cache_read_input_tokens from the API response
|
|
140
|
+
* @param inputTokens - input_tokens from the API response (uncached portion only —
|
|
141
|
+
* Anthropic's input_tokens excludes both cache reads and writes)
|
|
142
|
+
* @param sessionID - session that produced this response
|
|
142
143
|
*/
|
|
143
144
|
export function recordCacheUsage(
|
|
144
145
|
cacheWrite: number,
|
|
@@ -149,16 +150,25 @@ export function recordCacheUsage(
|
|
|
149
150
|
if (!sessionID) return;
|
|
150
151
|
const state = getSessionState(sessionID);
|
|
151
152
|
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
const total =
|
|
153
|
+
// Total = cacheWrite + cacheRead + uncached input. Anthropic's input_tokens
|
|
154
|
+
// field is only the uncached portion, NOT the total — using it alone as the
|
|
155
|
+
// denominator makes every cached turn look like a bust (e.g. 1000/3 >> 0.5).
|
|
156
|
+
const total = cacheWrite + cacheRead + inputTokens;
|
|
156
157
|
if (total > 0) {
|
|
157
|
-
|
|
158
|
+
const bustRatio = cacheWrite / total;
|
|
159
|
+
const prev = state.consecutiveBusts;
|
|
160
|
+
if (bustRatio > 0.5) {
|
|
158
161
|
state.consecutiveBusts++;
|
|
159
162
|
} else {
|
|
160
163
|
state.consecutiveBusts = 0;
|
|
161
164
|
}
|
|
165
|
+
if (state.consecutiveBusts !== prev) {
|
|
166
|
+
log.info(
|
|
167
|
+
`bust-tracker: session=${sessionID.slice(0, 16)} ratio=${bustRatio.toFixed(3)}` +
|
|
168
|
+
` (write=${cacheWrite} read=${cacheRead} uncached=${inputTokens})` +
|
|
169
|
+
` busts=${prev}→${state.consecutiveBusts}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
162
172
|
}
|
|
163
173
|
}
|
|
164
174
|
|
|
@@ -316,9 +326,11 @@ function getSessionState(sessionID: string): SessionState {
|
|
|
316
326
|
state.lastLayer = persisted.lastLayer as SafetyLayer;
|
|
317
327
|
state.lastKnownInput = persisted.lastKnownInput;
|
|
318
328
|
state.lastTurnAt = persisted.lastTurnAt;
|
|
319
|
-
// consecutiveBusts
|
|
320
|
-
//
|
|
321
|
-
state
|
|
329
|
+
// Don't restore consecutiveBusts from DB — it's a short-term rolling
|
|
330
|
+
// signal that must rebuild from live API responses in the current process.
|
|
331
|
+
// Stale values from a previous process (different cache state after restart)
|
|
332
|
+
// cause false unsustainable warnings. The dynamicContextCap column is still
|
|
333
|
+
// written for diagnostics but not consumed on restore.
|
|
322
334
|
}
|
|
323
335
|
|
|
324
336
|
sessionStates.set(sessionID, state);
|
|
@@ -475,6 +487,9 @@ export function getLtmBudget(ltmFraction: number): number {
|
|
|
475
487
|
return Math.floor(usable * ltmFraction);
|
|
476
488
|
}
|
|
477
489
|
|
|
490
|
+
/** Returns the token budget for stable LTM (preferences). Independent of context-bound LTM budget. */
|
|
491
|
+
export const getPreferenceLtmBudget = getLtmBudget;
|
|
492
|
+
|
|
478
493
|
// Called after each assistant message completes with real token usage data.
|
|
479
494
|
// actualInput = tokens.input + tokens.cache.read + tokens.cache.write
|
|
480
495
|
// sessionID = session that produced this response (for exact-tracking validity)
|
|
@@ -603,6 +618,34 @@ export function inspectSessionState(sessionID: string): {
|
|
|
603
618
|
};
|
|
604
619
|
}
|
|
605
620
|
|
|
621
|
+
/**
|
|
622
|
+
* Return the consecutive-bust counter for a session (read-only).
|
|
623
|
+
* Returns 0 if the session has no in-memory state — callers treat this
|
|
624
|
+
* as "no bust pressure" which is the safe default.
|
|
625
|
+
*
|
|
626
|
+
* Uses Map.get() instead of getSessionState() to avoid creating phantom
|
|
627
|
+
* SessionState entries with zeroed calibration fields, which would cause
|
|
628
|
+
* the next transform() call to treat the session as uncalibrated.
|
|
629
|
+
*/
|
|
630
|
+
export function getConsecutiveBusts(sessionID: string): number {
|
|
631
|
+
return sessionStates.get(sessionID)?.consecutiveBusts ?? 0;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Bust-pressure threshold for meta-distillation: consecutive busts ≥ this
|
|
635
|
+
* value trigger earlier consolidation of gen-0 segments. */
|
|
636
|
+
export const BUST_PRESSURE_THRESHOLD = 3;
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Compute the effective meta-distillation threshold under bust pressure.
|
|
640
|
+
* When busts ≥ BUST_PRESSURE_THRESHOLD, lowers the threshold to 1/4 of the
|
|
641
|
+
* configured value (min 3) to consolidate the distilled prefix earlier.
|
|
642
|
+
*/
|
|
643
|
+
export function effectiveMetaThreshold(busts: number, configThreshold: number): number {
|
|
644
|
+
return busts >= BUST_PRESSURE_THRESHOLD
|
|
645
|
+
? Math.max(3, Math.floor(configThreshold / 4))
|
|
646
|
+
: configThreshold;
|
|
647
|
+
}
|
|
648
|
+
|
|
606
649
|
/**
|
|
607
650
|
* For testing only — set the session's lastTurnAt field. Used to simulate
|
|
608
651
|
* idle gaps without sleeping. Creates the session state if not present so
|
|
@@ -1703,6 +1746,7 @@ function transformInner(input: {
|
|
|
1703
1746
|
distilledBudget,
|
|
1704
1747
|
rawBudget,
|
|
1705
1748
|
refreshLtm: false,
|
|
1749
|
+
unsustainable: sid ? getSessionState(sid).consecutiveBusts >= 5 : false,
|
|
1706
1750
|
};
|
|
1707
1751
|
}
|
|
1708
1752
|
|
|
@@ -1830,7 +1874,15 @@ function transformInner(input: {
|
|
|
1830
1874
|
if (sid && (s > 0 || cached.tokens === 0)) {
|
|
1831
1875
|
urgentDistillationMap.set(sid, true);
|
|
1832
1876
|
}
|
|
1833
|
-
return {
|
|
1877
|
+
return {
|
|
1878
|
+
...result!,
|
|
1879
|
+
layer: stageLayer,
|
|
1880
|
+
usable,
|
|
1881
|
+
distilledBudget,
|
|
1882
|
+
rawBudget,
|
|
1883
|
+
refreshLtm: false,
|
|
1884
|
+
unsustainable: sid ? getSessionState(sid).consecutiveBusts >= 5 : false,
|
|
1885
|
+
};
|
|
1834
1886
|
}
|
|
1835
1887
|
}
|
|
1836
1888
|
|
package/src/index.ts
CHANGED
|
@@ -78,6 +78,7 @@ export {
|
|
|
78
78
|
saveSessionTracking,
|
|
79
79
|
loadSessionTracking,
|
|
80
80
|
loadHeaderSessionIndex,
|
|
81
|
+
loadParentChildMap,
|
|
81
82
|
type SessionTrackingState,
|
|
82
83
|
type LoadedSessionTracking,
|
|
83
84
|
getKV,
|
|
@@ -104,6 +105,7 @@ export {
|
|
|
104
105
|
setLtmTokens,
|
|
105
106
|
getLtmTokens,
|
|
106
107
|
getLtmBudget,
|
|
108
|
+
getPreferenceLtmBudget,
|
|
107
109
|
setForceMinLayer,
|
|
108
110
|
getLastTransformedCount,
|
|
109
111
|
getLastTransformEstimate,
|
|
@@ -117,6 +119,9 @@ export {
|
|
|
117
119
|
// gaps without sleeping. Not part of the public API.
|
|
118
120
|
setLastTurnAtForTest,
|
|
119
121
|
inspectSessionState,
|
|
122
|
+
getConsecutiveBusts,
|
|
123
|
+
BUST_PRESSURE_THRESHOLD,
|
|
124
|
+
effectiveMetaThreshold,
|
|
120
125
|
} from "./gradient";
|
|
121
126
|
export {
|
|
122
127
|
formatKnowledge,
|
package/src/ltm.ts
CHANGED
|
@@ -44,6 +44,8 @@ export function create(input: {
|
|
|
44
44
|
crossProject?: boolean;
|
|
45
45
|
/** Explicit ID to use — for cross-machine import via agents-file. Defaults to a new UUIDv7. */
|
|
46
46
|
id?: string;
|
|
47
|
+
/** Initial confidence (0.0–1.0). Default 1.0. Controls injection priority for preferences. */
|
|
48
|
+
confidence?: number;
|
|
47
49
|
}): string {
|
|
48
50
|
const pid =
|
|
49
51
|
input.scope === "project" && input.projectPath
|
|
@@ -77,8 +79,15 @@ export function create(input: {
|
|
|
77
79
|
.get(input.title)
|
|
78
80
|
) as { id: string } | null;
|
|
79
81
|
|
|
82
|
+
// Build the update payload — forward confidence when the caller provided one
|
|
83
|
+
// so the curator's scoring intent isn't silently dropped on dedup.
|
|
84
|
+
const dedupUpdate = {
|
|
85
|
+
content: input.content,
|
|
86
|
+
...(input.confidence != null ? { confidence: input.confidence } : {}),
|
|
87
|
+
};
|
|
88
|
+
|
|
80
89
|
if (existing) {
|
|
81
|
-
update(existing.id,
|
|
90
|
+
update(existing.id, dedupUpdate);
|
|
82
91
|
return existing.id;
|
|
83
92
|
}
|
|
84
93
|
|
|
@@ -91,7 +100,7 @@ export function create(input: {
|
|
|
91
100
|
.get(input.title) as { id: string } | null;
|
|
92
101
|
|
|
93
102
|
if (crossExisting) {
|
|
94
|
-
update(crossExisting.id,
|
|
103
|
+
update(crossExisting.id, dedupUpdate);
|
|
95
104
|
return crossExisting.id;
|
|
96
105
|
}
|
|
97
106
|
|
|
@@ -101,17 +110,20 @@ export function create(input: {
|
|
|
101
110
|
// lock re-entry bug"). Placed after exact checks (cheaper checks first).
|
|
102
111
|
const fuzzyMatch = findFuzzyDuplicate({ title: input.title, projectId: pid });
|
|
103
112
|
if (fuzzyMatch) {
|
|
104
|
-
update(fuzzyMatch.id,
|
|
113
|
+
update(fuzzyMatch.id, dedupUpdate);
|
|
105
114
|
return fuzzyMatch.id;
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const id = input.id ?? uuidv7();
|
|
110
119
|
const now = Date.now();
|
|
120
|
+
const confidence = input.confidence != null
|
|
121
|
+
? Math.max(0, Math.min(1, input.confidence))
|
|
122
|
+
: 1.0;
|
|
111
123
|
db()
|
|
112
124
|
.query(
|
|
113
125
|
`INSERT INTO knowledge (id, project_id, category, title, content, source_session, cross_project, confidence, created_at, updated_at)
|
|
114
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
126
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
115
127
|
)
|
|
116
128
|
.run(
|
|
117
129
|
id,
|
|
@@ -121,6 +133,7 @@ export function create(input: {
|
|
|
121
133
|
input.content,
|
|
122
134
|
input.session ?? null,
|
|
123
135
|
crossProject ? 1 : 0,
|
|
136
|
+
confidence,
|
|
124
137
|
now,
|
|
125
138
|
now,
|
|
126
139
|
);
|
|
@@ -440,6 +453,31 @@ export async function forSession(
|
|
|
440
453
|
|
|
441
454
|
if (!crossEntries.length && !projectEntries.length) return [];
|
|
442
455
|
|
|
456
|
+
// --- Preference-only fast path ---
|
|
457
|
+
// Preferences are unconditional user directives — relevance scoring harms them.
|
|
458
|
+
// Skip scoring; rank purely by confidence (set by curator or `lore data rerank`)
|
|
459
|
+
// then recency. Confidence carries real meaning now: 1.0 = unconditional
|
|
460
|
+
// directive, 0.9 = strong preference, 0.8 = moderate, 0.6 = mild.
|
|
461
|
+
const isPreferenceOnly = categoryFilter?.length === 1 && categoryFilter[0] === "preference";
|
|
462
|
+
if (isPreferenceOnly) {
|
|
463
|
+
const allPrefs = [...projectEntries, ...crossEntries];
|
|
464
|
+
allPrefs.sort((a, b) =>
|
|
465
|
+
a.confidence !== b.confidence ? b.confidence - a.confidence : b.updated_at - a.updated_at
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const HEADER_OVERHEAD_TOKENS = 15;
|
|
469
|
+
let used = HEADER_OVERHEAD_TOKENS;
|
|
470
|
+
const result: KnowledgeEntry[] = [];
|
|
471
|
+
for (const entry of allPrefs) {
|
|
472
|
+
if (used >= maxTokens) break;
|
|
473
|
+
const cost = estimateTokens(entry.title + entry.content) + 10;
|
|
474
|
+
if (used + cost > maxTokens) continue;
|
|
475
|
+
result.push(entry);
|
|
476
|
+
used += cost;
|
|
477
|
+
}
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
|
|
443
481
|
// --- 3. Build session context for relevance scoring ---
|
|
444
482
|
let sessionContext = "";
|
|
445
483
|
if (sessionID) {
|
|
@@ -651,6 +689,42 @@ export function crossProject(): KnowledgeEntry[] {
|
|
|
651
689
|
.all() as KnowledgeEntry[];
|
|
652
690
|
}
|
|
653
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Re-score confidence on preference entries using directive-detection patterns.
|
|
694
|
+
* Only touches entries with confidence = 1.0 (legacy/unscored). Entries already
|
|
695
|
+
* scored by the curator (confidence < 1.0) are left untouched.
|
|
696
|
+
*
|
|
697
|
+
* @returns Count of entries updated.
|
|
698
|
+
*/
|
|
699
|
+
export function rerankPreferences(): number {
|
|
700
|
+
const prefs = db()
|
|
701
|
+
.query(`SELECT ${KNOWLEDGE_COLS} FROM knowledge WHERE category = 'preference' AND confidence = 1.0`)
|
|
702
|
+
.all() as KnowledgeEntry[];
|
|
703
|
+
|
|
704
|
+
// Strong unconditional directives
|
|
705
|
+
const STRONG_DIRECTIVE_RE = /\b(never|always|must not|must)\b/i;
|
|
706
|
+
// Explicit preference language
|
|
707
|
+
const EXPLICIT_PREF_RE = /\b(I (?:want|need|prefer|expect)|make sure to|don'?t forget)\b/i;
|
|
708
|
+
|
|
709
|
+
let updated = 0;
|
|
710
|
+
for (const entry of prefs) {
|
|
711
|
+
const text = entry.title + " " + entry.content;
|
|
712
|
+
let newConfidence: number;
|
|
713
|
+
if (STRONG_DIRECTIVE_RE.test(text)) {
|
|
714
|
+
newConfidence = 1.0; // Keep at max — unconditional directive
|
|
715
|
+
} else if (EXPLICIT_PREF_RE.test(text)) {
|
|
716
|
+
newConfidence = 0.9; // Strong but not absolute
|
|
717
|
+
} else {
|
|
718
|
+
newConfidence = 0.8; // No directive language detected — moderate
|
|
719
|
+
}
|
|
720
|
+
if (newConfidence !== entry.confidence) {
|
|
721
|
+
update(entry.id, { confidence: newConfidence });
|
|
722
|
+
updated++;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return updated;
|
|
726
|
+
}
|
|
727
|
+
|
|
654
728
|
// LIKE-based fallback for when FTS5 fails unexpectedly.
|
|
655
729
|
function searchLike(input: {
|
|
656
730
|
query: string;
|
package/src/prompt.ts
CHANGED
|
@@ -266,6 +266,20 @@ crossProject flag:
|
|
|
266
266
|
- Default is true — most useful knowledge is worth sharing across projects
|
|
267
267
|
- Set crossProject to false for things that are meaningless outside this specific repo (e.g. a config path, a project-local naming convention that conflicts with your usual style)
|
|
268
268
|
|
|
269
|
+
Confidence values (0.0–1.0) — determines injection priority when budget is tight:
|
|
270
|
+
- 1.0: Unconditional directive — user used "NEVER", "ALWAYS", "from now on", or similarly
|
|
271
|
+
absolute language. These must always be respected regardless of context.
|
|
272
|
+
- 0.9: Strong preference — explicit user preference ("I prefer", "I want", "make sure to",
|
|
273
|
+
"don't forget to"). Clear intent but not absolute.
|
|
274
|
+
- 0.8: Moderate preference — inferred from repeated user behavior or gentle correction across
|
|
275
|
+
sessions. Not explicitly stated as a rule.
|
|
276
|
+
- 0.6: Mild/contextual preference — may not apply universally. Observed once or context-dependent.
|
|
277
|
+
- For non-preference categories (gotcha, pattern, architecture, decision), confidence reflects
|
|
278
|
+
how well-established the knowledge is: 1.0 = verified/confirmed, 0.8 = high confidence,
|
|
279
|
+
0.6 = probable but unverified.
|
|
280
|
+
- Default to 1.0 for preferences with strong directive language, 0.8 for other preferences.
|
|
281
|
+
- Always set confidence on create ops — it determines injection priority.
|
|
282
|
+
|
|
269
283
|
Produce a JSON array of operations:
|
|
270
284
|
[
|
|
271
285
|
{
|
|
@@ -274,7 +288,8 @@ Produce a JSON array of operations:
|
|
|
274
288
|
"title": "Short descriptive title",
|
|
275
289
|
"content": "Concise knowledge entry — under 150 words",
|
|
276
290
|
"scope": "project" | "global",
|
|
277
|
-
"crossProject": true
|
|
291
|
+
"crossProject": true,
|
|
292
|
+
"confidence": 1.0
|
|
278
293
|
},
|
|
279
294
|
{
|
|
280
295
|
"op": "update",
|
|
@@ -322,7 +337,8 @@ IMPORTANT:
|
|
|
322
337
|
4. Only create a new entry for genuinely distinct knowledge with no existing home.
|
|
323
338
|
5. Keep all entries under 150 words. If an existing entry is too long, use an update op to trim it.
|
|
324
339
|
6. Pay special attention to user instructions ("always do X", "never do Y", "make sure to X").
|
|
325
|
-
These are strong signals for "preference" entries with high confidence
|
|
340
|
+
These are strong signals for "preference" entries with high confidence (1.0 for absolute
|
|
341
|
+
directives like "never"/"always", 0.9 for explicit preferences like "I prefer").`;
|
|
326
342
|
}
|
|
327
343
|
|
|
328
344
|
/**
|