@jmtrin/opencode-kevin 0.2.0 → 0.4.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/README.md +108 -35
- package/dist/migrations/004_v03_knowledge.sql +138 -0
- package/dist/migrations/005_v04_signal.sql +57 -0
- package/dist/plugin/CausalChain.d.ts +22 -0
- package/dist/plugin/CausalChain.js +179 -0
- package/dist/plugin/CausalChain.js.map +1 -0
- package/dist/plugin/ContextInjector.d.ts +89 -1
- package/dist/plugin/ContextInjector.js +276 -71
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/InjectionLedger.d.ts +85 -0
- package/dist/plugin/InjectionLedger.js +189 -0
- package/dist/plugin/InjectionLedger.js.map +1 -0
- package/dist/plugin/LessonFixer.d.ts +44 -0
- package/dist/plugin/LessonFixer.js +46 -0
- package/dist/plugin/LessonFixer.js.map +1 -0
- package/dist/plugin/MemoryService.d.ts +123 -2
- package/dist/plugin/MemoryService.js +439 -31
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.js +22 -0
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/QualityGate.d.ts +71 -0
- package/dist/plugin/QualityGate.js +78 -0
- package/dist/plugin/QualityGate.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +33 -0
- package/dist/plugin/Reflector.js +126 -32
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.js +10 -1
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +1 -0
- package/dist/plugin/ToolCallObserver.js +16 -9
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/confidence.d.ts +6 -0
- package/dist/plugin/confidence.js +23 -0
- package/dist/plugin/confidence.js.map +1 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +336 -44
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/kevin_why.d.ts +23 -0
- package/dist/plugin/kevin_why.js +108 -0
- package/dist/plugin/kevin_why.js.map +1 -0
- package/dist/plugin/memory-format.d.ts +12 -0
- package/dist/plugin/memory-format.js +45 -5
- package/dist/plugin/memory-format.js.map +1 -1
- package/dist/plugin/metrics.d.ts +7 -1
- package/dist/plugin/metrics.js +19 -0
- package/dist/plugin/metrics.js.map +1 -1
- package/dist/plugin/okf-export.d.ts +3 -0
- package/dist/plugin/okf-export.js +127 -0
- package/dist/plugin/okf-export.js.map +1 -0
- package/dist/plugin/okf-import.d.ts +76 -0
- package/dist/plugin/okf-import.js +272 -0
- package/dist/plugin/okf-import.js.map +1 -0
- package/dist/plugin/query-tokenizer.d.ts +13 -0
- package/dist/plugin/query-tokenizer.js +86 -0
- package/dist/plugin/query-tokenizer.js.map +1 -0
- package/migrations/004_v03_knowledge.sql +138 -0
- package/migrations/005_v04_signal.sql +57 -0
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { InjectionLedger } from "./InjectionLedger.js";
|
|
1
2
|
import type { MemoryService } from "./MemoryService.js";
|
|
2
3
|
import { type Metrics } from "./metrics.js";
|
|
4
|
+
export declare const QUALITY_GATE_SETTING = "quality_gate_enabled";
|
|
5
|
+
export declare const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
|
|
3
6
|
export interface ChatMessage {
|
|
4
7
|
role: string;
|
|
5
8
|
content: string;
|
|
@@ -21,7 +24,45 @@ export interface CompactingOutput {
|
|
|
21
24
|
export declare class ContextInjector {
|
|
22
25
|
private memoryService;
|
|
23
26
|
private metrics;
|
|
24
|
-
|
|
27
|
+
private ledger;
|
|
28
|
+
private lastRecurrenceCount;
|
|
29
|
+
/** v0.4.0 (K4-016) — session that produced the last recurrence set. */
|
|
30
|
+
private lastRecurredSession;
|
|
31
|
+
/** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
|
|
32
|
+
private readonly seenBySession;
|
|
33
|
+
constructor(memoryService: MemoryService, metrics?: Metrics | null, ledger?: InjectionLedger | null);
|
|
34
|
+
/**
|
|
35
|
+
* v0.4.0 (K4-017) — reset the per-session seen-set when a session is
|
|
36
|
+
* created (plan §5.1 rule 3). Wired from the `session.created` event.
|
|
37
|
+
*/
|
|
38
|
+
onSessionCreated(sessionId: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* v0.3.0 (K3-020) — notify the injector that the negative feedback half
|
|
41
|
+
* fired N times in the last session.idle. The next system.transform or
|
|
42
|
+
* compacting hook will prepend a HITL suggestion block.
|
|
43
|
+
* v0.4.0 (K4-016) — the session id enables the concrete suggestion:
|
|
44
|
+
* the most-recurred fingerprint's pattern + its fix_args.
|
|
45
|
+
*/
|
|
46
|
+
setRecurrences(count: number, sessionId?: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
|
|
49
|
+
* recurrences occurred (plan §5.5): names the most-recurred pattern,
|
|
50
|
+
* its exact recurrence count, observed fix_args and confidence. The
|
|
51
|
+
* AGENTS.md draft line derives from the lesson's `Suggestion:` text —
|
|
52
|
+
* never a canned string, and never the anonymous "the same error
|
|
53
|
+
* pattern" without naming the pattern and count.
|
|
54
|
+
*
|
|
55
|
+
* BUG-012 — emits AT MOST ONCE per session: calling it resets the
|
|
56
|
+
* pending recurrence signal, so whichever hook (system.transform or
|
|
57
|
+
* compacting) runs first in a session consumes the block. Documented
|
|
58
|
+
* behavior; wire index.ts to match.
|
|
59
|
+
*/
|
|
60
|
+
generateSuggestion(): string;
|
|
61
|
+
/**
|
|
62
|
+
* v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
|
|
63
|
+
* `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
|
|
64
|
+
*/
|
|
65
|
+
private agentsDraftLine;
|
|
25
66
|
/**
|
|
26
67
|
* v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
|
|
27
68
|
* to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
|
|
@@ -30,8 +71,55 @@ export declare class ContextInjector {
|
|
|
30
71
|
* callers. Plan §B6.5: "apply the same multiplier as
|
|
31
72
|
* MemoryService.recall so reflector lessons outrank agent-saved notes
|
|
32
73
|
* at injection time" — satisfied transitively via the getRelevant call.
|
|
74
|
+
*
|
|
75
|
+
* v0.4.0 (K4-017): after retrieval, every memory is filtered through
|
|
76
|
+
* `QualityGate.canInject` (session seen-set + recurrence + strength),
|
|
77
|
+
* and each admitted memory is recorded in the `InjectionLedger`
|
|
78
|
+
* (plan §5.2 — one row per injected memory).
|
|
33
79
|
*/
|
|
34
80
|
private inject;
|
|
81
|
+
/**
|
|
82
|
+
* v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
|
|
83
|
+
* memories that may be injected this session, updating the session
|
|
84
|
+
* seen-set.
|
|
85
|
+
*
|
|
86
|
+
* BUG-005 — strength/actionability now go through the REAL
|
|
87
|
+
* `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
|
|
88
|
+
* class had only re-derived from `metadata.dispatch`:
|
|
89
|
+
* - dispatched code → strong + actionable (rescued errorType);
|
|
90
|
+
* - generic fallback suggestion + no code → weak, NOT actionable
|
|
91
|
+
* (the generic-suggestion ban is now enforced even for legacy
|
|
92
|
+
* lessons without dispatch metadata, as long as the `Suggestion:`
|
|
93
|
+
* text is available);
|
|
94
|
+
* - specific suggestion without code → strong + actionable;
|
|
95
|
+
* - no `Suggestion:` text at all (agent-saved note) → strong +
|
|
96
|
+
* actionable — the agent explicitly asked to remember it;
|
|
97
|
+
* - causal patterns (type='pattern') → always strong + actionable:
|
|
98
|
+
* they are the FIXED form of a fingerprint (K4-025).
|
|
99
|
+
*/
|
|
100
|
+
private admit;
|
|
101
|
+
/**
|
|
102
|
+
* BUG-005 — the single source of truth for a memory's lesson quality,
|
|
103
|
+
* shared by `admit` (the gate) and `format` (the K4-023 `(low
|
|
104
|
+
* confidence)` marker). Routes lessons through `QualityGate.evaluate`
|
|
105
|
+
* — the production call site that was previously missing.
|
|
106
|
+
*/
|
|
107
|
+
private lessonQuality;
|
|
108
|
+
/**
|
|
109
|
+
* v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
|
|
110
|
+
* Token attribution uses the memory's share of the final block.
|
|
111
|
+
*/
|
|
112
|
+
private recordInjections;
|
|
113
|
+
/**
|
|
114
|
+
* v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
|
|
115
|
+
* D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
|
|
116
|
+
* instead of the full body. Gated by the `lesson_snippet_injection`
|
|
117
|
+
* setting (default `'1'`); when `'0'`, full content is restored.
|
|
118
|
+
* `escapeInjectedText` is applied to snippet content by the formatter.
|
|
119
|
+
* v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
|
|
120
|
+
* the formatter renders the `(low confidence)` marker.
|
|
121
|
+
*/
|
|
122
|
+
private format;
|
|
35
123
|
deriveQuery(messages: ChatMessage[]): string;
|
|
36
124
|
onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
|
|
37
125
|
onCompacting(input: CompactingInput, output: CompactingOutput): void;
|
|
@@ -1,79 +1,133 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QualityGate } from "./QualityGate.js";
|
|
2
|
+
import { formatMemories, formatMemorySnippets } from "./memory-format.js";
|
|
2
3
|
import { estimateTokens } from "./metrics.js";
|
|
4
|
+
import { STOP_WORDS } from "./query-tokenizer.js";
|
|
5
|
+
export const QUALITY_GATE_SETTING = "quality_gate_enabled";
|
|
6
|
+
export const SNIPPET_INJECTION_SETTING = "lesson_snippet_injection";
|
|
3
7
|
const SYSTEM_TRANSFORM_TOKENS = 1500;
|
|
4
8
|
const COMPACTING_TOKENS = 2000;
|
|
5
|
-
const STOP_WORDS = new Set([
|
|
6
|
-
"a",
|
|
7
|
-
"an",
|
|
8
|
-
"and",
|
|
9
|
-
"are",
|
|
10
|
-
"at",
|
|
11
|
-
"be",
|
|
12
|
-
"been",
|
|
13
|
-
"but",
|
|
14
|
-
"by",
|
|
15
|
-
"did",
|
|
16
|
-
"do",
|
|
17
|
-
"does",
|
|
18
|
-
"el",
|
|
19
|
-
"eso",
|
|
20
|
-
"for",
|
|
21
|
-
"how",
|
|
22
|
-
"i",
|
|
23
|
-
"if",
|
|
24
|
-
"in",
|
|
25
|
-
"is",
|
|
26
|
-
"it",
|
|
27
|
-
"la",
|
|
28
|
-
"las",
|
|
29
|
-
"los",
|
|
30
|
-
"mi",
|
|
31
|
-
"my",
|
|
32
|
-
"o",
|
|
33
|
-
"of",
|
|
34
|
-
"on",
|
|
35
|
-
"or",
|
|
36
|
-
"para",
|
|
37
|
-
"por",
|
|
38
|
-
"que",
|
|
39
|
-
"she",
|
|
40
|
-
"su",
|
|
41
|
-
"that",
|
|
42
|
-
"the",
|
|
43
|
-
"this",
|
|
44
|
-
"to",
|
|
45
|
-
"tu",
|
|
46
|
-
"un",
|
|
47
|
-
"una",
|
|
48
|
-
"we",
|
|
49
|
-
"were",
|
|
50
|
-
"what",
|
|
51
|
-
"when",
|
|
52
|
-
"where",
|
|
53
|
-
"which",
|
|
54
|
-
"who",
|
|
55
|
-
"why",
|
|
56
|
-
"with",
|
|
57
|
-
"y",
|
|
58
|
-
"you",
|
|
59
|
-
"como",
|
|
60
|
-
"con",
|
|
61
|
-
"de",
|
|
62
|
-
"en",
|
|
63
|
-
"he",
|
|
64
|
-
"they",
|
|
65
|
-
"was",
|
|
66
|
-
"sin",
|
|
67
|
-
]);
|
|
68
9
|
function isWordChar(ch) {
|
|
69
10
|
return /[a-z0-9áéíóúüñ]/i.test(ch);
|
|
70
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* BUG-005 — extract the lesson's `Suggestion:` text (first line after the
|
|
14
|
+
* marker) when present. Returns null for agent-saved notes that carry no
|
|
15
|
+
* suggestion payload. Also matches the truncated snippet-style lesson
|
|
16
|
+
* bodies used by older tests.
|
|
17
|
+
*/
|
|
18
|
+
function extractSuggestionText(content) {
|
|
19
|
+
const m = content.match(/\nSuggestion:\s*([^\n]+)/);
|
|
20
|
+
return m ? m[1].trim() : null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* BUG-005 — extract the `fails with X` slot of a lesson. For lessons
|
|
24
|
+
* without a dispatched code this equals the coarse errorType that
|
|
25
|
+
* `QualityGate.evaluate` needs for the strength classification.
|
|
26
|
+
*/
|
|
27
|
+
function extractFailsWithErrorType(content) {
|
|
28
|
+
const m = content.match(/\bfails with ([^:]+):/i);
|
|
29
|
+
return m ? m[1].trim() : null;
|
|
30
|
+
}
|
|
71
31
|
export class ContextInjector {
|
|
72
32
|
memoryService;
|
|
73
33
|
metrics;
|
|
74
|
-
|
|
34
|
+
ledger;
|
|
35
|
+
lastRecurrenceCount = 0;
|
|
36
|
+
/** v0.4.0 (K4-016) — session that produced the last recurrence set. */
|
|
37
|
+
lastRecurredSession = null;
|
|
38
|
+
/** v0.4.0 (K4-017) — per-session seen-set (plan §5.1 rule 3). */
|
|
39
|
+
seenBySession = new Map();
|
|
40
|
+
constructor(memoryService, metrics = null, ledger = null) {
|
|
75
41
|
this.memoryService = memoryService;
|
|
76
42
|
this.metrics = metrics;
|
|
43
|
+
this.ledger = ledger;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* v0.4.0 (K4-017) — reset the per-session seen-set when a session is
|
|
47
|
+
* created (plan §5.1 rule 3). Wired from the `session.created` event.
|
|
48
|
+
*/
|
|
49
|
+
onSessionCreated(sessionId) {
|
|
50
|
+
this.seenBySession.delete(sessionId);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* v0.3.0 (K3-020) — notify the injector that the negative feedback half
|
|
54
|
+
* fired N times in the last session.idle. The next system.transform or
|
|
55
|
+
* compacting hook will prepend a HITL suggestion block.
|
|
56
|
+
* v0.4.0 (K4-016) — the session id enables the concrete suggestion:
|
|
57
|
+
* the most-recurred fingerprint's pattern + its fix_args.
|
|
58
|
+
*/
|
|
59
|
+
setRecurrences(count, sessionId) {
|
|
60
|
+
this.lastRecurrenceCount = count;
|
|
61
|
+
this.lastRecurredSession = sessionId ?? null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* v0.4.0 (K4-016) — generate a CONCRETE HITL suggestion block when
|
|
65
|
+
* recurrences occurred (plan §5.5): names the most-recurred pattern,
|
|
66
|
+
* its exact recurrence count, observed fix_args and confidence. The
|
|
67
|
+
* AGENTS.md draft line derives from the lesson's `Suggestion:` text —
|
|
68
|
+
* never a canned string, and never the anonymous "the same error
|
|
69
|
+
* pattern" without naming the pattern and count.
|
|
70
|
+
*
|
|
71
|
+
* BUG-012 — emits AT MOST ONCE per session: calling it resets the
|
|
72
|
+
* pending recurrence signal, so whichever hook (system.transform or
|
|
73
|
+
* compacting) runs first in a session consumes the block. Documented
|
|
74
|
+
* behavior; wire index.ts to match.
|
|
75
|
+
*/
|
|
76
|
+
generateSuggestion() {
|
|
77
|
+
const count = this.lastRecurrenceCount;
|
|
78
|
+
const sessionId = this.lastRecurredSession;
|
|
79
|
+
this.lastRecurrenceCount = 0;
|
|
80
|
+
this.lastRecurredSession = null;
|
|
81
|
+
if (count === 0)
|
|
82
|
+
return "";
|
|
83
|
+
const recurrences = this.ledger?.recurrencesFor(sessionId ?? "") ?? new Map();
|
|
84
|
+
let topFp = null;
|
|
85
|
+
let topCount = 0;
|
|
86
|
+
for (const [fp, n] of recurrences) {
|
|
87
|
+
if (n > topCount) {
|
|
88
|
+
topFp = fp;
|
|
89
|
+
topCount = n;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const pattern = topFp
|
|
93
|
+
? this.memoryService.getByFingerprint(topFp, "pattern")
|
|
94
|
+
: null;
|
|
95
|
+
// No pattern memory for the recurred fingerprint (or no ledger): a
|
|
96
|
+
// short fallback that still names the count.
|
|
97
|
+
if (!pattern) {
|
|
98
|
+
return `<kevin-suggestion>
|
|
99
|
+
An error pattern recurred ${count} time(s) this session.
|
|
100
|
+
Consider adding a convention to AGENTS.md.
|
|
101
|
+
</kevin-suggestion>`;
|
|
102
|
+
}
|
|
103
|
+
const summary = pattern.content
|
|
104
|
+
.split("\n")[0]
|
|
105
|
+
.replace(/^Causal pattern:\s*/, "")
|
|
106
|
+
.trim();
|
|
107
|
+
const pct = pattern.confidence != null ? Math.round(pattern.confidence * 100) : 0;
|
|
108
|
+
const evidence = pattern.evidenceCount ?? 0;
|
|
109
|
+
const fixLine = pattern.fixArgs
|
|
110
|
+
? `Observed fix: ${pattern.fixArgs} (${evidence} confirmed fix${evidence === 1 ? "" : "es"}, confidence ${pct}%).`
|
|
111
|
+
: "";
|
|
112
|
+
return `<kevin-suggestion>
|
|
113
|
+
The error pattern "${summary}" recurred ${topCount} time(s) this session.
|
|
114
|
+
${fixLine}
|
|
115
|
+
Consider adding this convention to AGENTS.md:
|
|
116
|
+
- ${this.agentsDraftLine(pattern)}
|
|
117
|
+
</kevin-suggestion>`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* v0.4.0 (K4-016) — the AGENTS.md draft line derives from the lesson's
|
|
121
|
+
* `Suggestion:` text (kept verbatim in the pattern's `Original:` block).
|
|
122
|
+
*/
|
|
123
|
+
agentsDraftLine(pattern) {
|
|
124
|
+
const m = pattern.content.match(/\nSuggestion: ([^\n]+)/);
|
|
125
|
+
if (m)
|
|
126
|
+
return m[1].trim();
|
|
127
|
+
return `${pattern.content
|
|
128
|
+
.split("\n")[0]
|
|
129
|
+
.replace(/^Causal pattern:\s*/, "")
|
|
130
|
+
.trim()} recurred — document the fix in AGENTS.md`;
|
|
77
131
|
}
|
|
78
132
|
/**
|
|
79
133
|
* v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
|
|
@@ -83,17 +137,33 @@ export class ContextInjector {
|
|
|
83
137
|
* callers. Plan §B6.5: "apply the same multiplier as
|
|
84
138
|
* MemoryService.recall so reflector lessons outrank agent-saved notes
|
|
85
139
|
* at injection time" — satisfied transitively via the getRelevant call.
|
|
140
|
+
*
|
|
141
|
+
* v0.4.0 (K4-017): after retrieval, every memory is filtered through
|
|
142
|
+
* `QualityGate.canInject` (session seen-set + recurrence + strength),
|
|
143
|
+
* and each admitted memory is recorded in the `InjectionLedger`
|
|
144
|
+
* (plan §5.2 — one row per injected memory).
|
|
86
145
|
*/
|
|
87
|
-
inject(query, tag, cap, metricKey) {
|
|
88
|
-
|
|
146
|
+
inject(query, tag, cap, metricKey, sessionId) {
|
|
147
|
+
// BUG-016 — probe WITHOUT bumping so the overflow decision and any
|
|
148
|
+
// retry both see the ORIGINAL ranking. The single relevance bump
|
|
149
|
+
// (K2-023) is applied exactly once, to the slice that actually
|
|
150
|
+
// produces the injected block — see below.
|
|
151
|
+
let memories = this.memoryService.getRelevant({
|
|
152
|
+
query,
|
|
153
|
+
maxTokens: cap,
|
|
154
|
+
bump: false,
|
|
155
|
+
});
|
|
89
156
|
if (memories.length === 0)
|
|
90
157
|
return "";
|
|
91
|
-
const firstBlock =
|
|
158
|
+
const firstBlock = this.format(memories, tag);
|
|
92
159
|
const aggregateTokens = estimateTokens(firstBlock);
|
|
93
160
|
const firstRowProtect = memories[0]
|
|
94
161
|
?.protect;
|
|
95
162
|
const noProtectAboveTheFold = firstRowProtect === false;
|
|
96
163
|
if (aggregateTokens > 0.8 * cap && noProtectAboveTheFold) {
|
|
164
|
+
// Retry = a single fetch with the adjusted budget, ranked by
|
|
165
|
+
// the original scores (the probe never mutated them) — the
|
|
166
|
+
// equivalent of one getRelevant call with maxTokens=lowerCap.
|
|
97
167
|
const lowerCap = Math.max(1, Math.round(0.8 * cap));
|
|
98
168
|
memories = this.memoryService.getRelevant({
|
|
99
169
|
query,
|
|
@@ -102,10 +172,145 @@ export class ContextInjector {
|
|
|
102
172
|
if (memories.length === 0)
|
|
103
173
|
return "";
|
|
104
174
|
}
|
|
105
|
-
|
|
175
|
+
else {
|
|
176
|
+
// No retry: the probe slice IS the injected slice — bump it
|
|
177
|
+
// exactly once here.
|
|
178
|
+
this.memoryService.bumpRelevance(memories.map((m) => m.id));
|
|
179
|
+
}
|
|
180
|
+
const admitted = this.admit(memories, sessionId);
|
|
181
|
+
if (admitted.length === 0)
|
|
182
|
+
return "";
|
|
183
|
+
const block = this.format(admitted, tag);
|
|
184
|
+
this.recordInjections(admitted, sessionId, tag, block);
|
|
106
185
|
this.metrics?.incr(metricKey, estimateTokens(block));
|
|
107
186
|
return block;
|
|
108
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* v0.4.0 (K4-017) — QualityGate admission: filters the ranked slice to
|
|
190
|
+
* memories that may be injected this session, updating the session
|
|
191
|
+
* seen-set.
|
|
192
|
+
*
|
|
193
|
+
* BUG-005 — strength/actionability now go through the REAL
|
|
194
|
+
* `QualityGate.evaluate` semantics (plan §5.1 rules 1-2), which this
|
|
195
|
+
* class had only re-derived from `metadata.dispatch`:
|
|
196
|
+
* - dispatched code → strong + actionable (rescued errorType);
|
|
197
|
+
* - generic fallback suggestion + no code → weak, NOT actionable
|
|
198
|
+
* (the generic-suggestion ban is now enforced even for legacy
|
|
199
|
+
* lessons without dispatch metadata, as long as the `Suggestion:`
|
|
200
|
+
* text is available);
|
|
201
|
+
* - specific suggestion without code → strong + actionable;
|
|
202
|
+
* - no `Suggestion:` text at all (agent-saved note) → strong +
|
|
203
|
+
* actionable — the agent explicitly asked to remember it;
|
|
204
|
+
* - causal patterns (type='pattern') → always strong + actionable:
|
|
205
|
+
* they are the FIXED form of a fingerprint (K4-025).
|
|
206
|
+
*/
|
|
207
|
+
admit(memories, sessionId) {
|
|
208
|
+
const qualityGateEnabled = this.memoryService.getSetting(QUALITY_GATE_SETTING, "1") === "1";
|
|
209
|
+
const seen = this.seenBySession.get(sessionId) ?? new Set();
|
|
210
|
+
const recurrences = this.ledger?.postInjectionRecurrencesFor(sessionId) ??
|
|
211
|
+
new Map();
|
|
212
|
+
const admitted = [];
|
|
213
|
+
for (const m of memories) {
|
|
214
|
+
const q = this.lessonQuality(m);
|
|
215
|
+
if (QualityGate.canInject({
|
|
216
|
+
id: m.id,
|
|
217
|
+
status: m.status ?? undefined,
|
|
218
|
+
strength: q.strength,
|
|
219
|
+
isActionable: q.isActionable,
|
|
220
|
+
}, {
|
|
221
|
+
seenThisSession: seen,
|
|
222
|
+
// v0.4.0 (K4-025) — plan §5.1 rule 4: a causal pattern
|
|
223
|
+
// re-admits a lesson that the stale error row cannot.
|
|
224
|
+
// The recurrence ban (QualityGate rule 3) is scoped to
|
|
225
|
+
// error lessons — a pattern is the FIXED form of the
|
|
226
|
+
// fingerprint and is exactly what D4-06 wants back in
|
|
227
|
+
// the prompt.
|
|
228
|
+
recurrenceCount: m.type === "pattern"
|
|
229
|
+
? 0
|
|
230
|
+
: m.fingerprint
|
|
231
|
+
? (recurrences.get(m.fingerprint) ?? 0)
|
|
232
|
+
: 0,
|
|
233
|
+
}, qualityGateEnabled)) {
|
|
234
|
+
admitted.push(m);
|
|
235
|
+
seen.add(m.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
this.seenBySession.set(sessionId, seen);
|
|
239
|
+
return admitted;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* BUG-005 — the single source of truth for a memory's lesson quality,
|
|
243
|
+
* shared by `admit` (the gate) and `format` (the K4-023 `(low
|
|
244
|
+
* confidence)` marker). Routes lessons through `QualityGate.evaluate`
|
|
245
|
+
* — the production call site that was previously missing.
|
|
246
|
+
*/
|
|
247
|
+
lessonQuality(m) {
|
|
248
|
+
// K4-025: a causal pattern is the fixed form of the fingerprint —
|
|
249
|
+
// never gated by suggestion text.
|
|
250
|
+
if (m.type === "pattern") {
|
|
251
|
+
return { strength: "strong", isActionable: true, weak: false };
|
|
252
|
+
}
|
|
253
|
+
const meta = (m.metadata ?? null);
|
|
254
|
+
const dispatch = meta?.dispatch ?? null;
|
|
255
|
+
const suggestion = extractSuggestionText(m.content);
|
|
256
|
+
if (suggestion === null) {
|
|
257
|
+
// Agent-saved note without a `Suggestion:` line: the agent
|
|
258
|
+
// explicitly asked to remember it → strong + actionable.
|
|
259
|
+
return { strength: "strong", isActionable: true, weak: false };
|
|
260
|
+
}
|
|
261
|
+
// The lesson's `fails with X` slot holds the displayed errorType;
|
|
262
|
+
// when no code was dispatched it equals the coarse errorType that
|
|
263
|
+
// `evaluate` needs for the strength classification.
|
|
264
|
+
const errorType = extractFailsWithErrorType(m.content);
|
|
265
|
+
const q = QualityGate.evaluate({ errorType: errorType ?? "unknown", suggestion }, dispatch, errorType ?? "unknown");
|
|
266
|
+
return {
|
|
267
|
+
strength: q.strength,
|
|
268
|
+
isActionable: q.isActionable,
|
|
269
|
+
weak: q.strength === "weak",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* v0.4.0 (K4-017) — one ledger row per admitted memory (plan §5.2).
|
|
274
|
+
* Token attribution uses the memory's share of the final block.
|
|
275
|
+
*/
|
|
276
|
+
recordInjections(admitted, sessionId, tag, block) {
|
|
277
|
+
if (this.ledger === null)
|
|
278
|
+
return;
|
|
279
|
+
const blockTokens = estimateTokens(block);
|
|
280
|
+
const measurable = admitted.filter((m) => m.fingerprint);
|
|
281
|
+
const perMemory = Math.max(1, Math.round(blockTokens / admitted.length));
|
|
282
|
+
const hook = tag === "context" ? "pre_prompt" : "compacting";
|
|
283
|
+
for (const m of measurable) {
|
|
284
|
+
this.ledger.record({
|
|
285
|
+
memoryId: m.id,
|
|
286
|
+
fingerprint: m.fingerprint,
|
|
287
|
+
sessionId,
|
|
288
|
+
hook,
|
|
289
|
+
tokens: perMemory,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* v0.4.0 (K4-012) — snippet injection payload (plan §5.1 rule 5,
|
|
295
|
+
* D4-05): rows show `id:` + first 2 non-empty lines + `<protect>`
|
|
296
|
+
* instead of the full body. Gated by the `lesson_snippet_injection`
|
|
297
|
+
* setting (default `'1'`); when `'0'`, full content is restored.
|
|
298
|
+
* `escapeInjectedText` is applied to snippet content by the formatter.
|
|
299
|
+
* v0.4.0 (K4-023) — weak lessons admitted in debug mode are flagged so
|
|
300
|
+
* the formatter renders the `(low confidence)` marker.
|
|
301
|
+
*/
|
|
302
|
+
format(memories, tag) {
|
|
303
|
+
const snippetsOn = this.memoryService.getSetting(SNIPPET_INJECTION_SETTING, "1") === "1";
|
|
304
|
+
const items = memories.map((m) => ({
|
|
305
|
+
...m,
|
|
306
|
+
// BUG-005 — the weak marker must mirror the admission decision
|
|
307
|
+
// (K4-023 debug mode), not re-derive from dispatch alone.
|
|
308
|
+
weak: this.lessonQuality(m).weak,
|
|
309
|
+
}));
|
|
310
|
+
return snippetsOn
|
|
311
|
+
? formatMemorySnippets(items, tag)
|
|
312
|
+
: formatMemories(items, tag);
|
|
313
|
+
}
|
|
109
314
|
deriveQuery(messages) {
|
|
110
315
|
let lastUserContent = "";
|
|
111
316
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -134,7 +339,7 @@ export class ContextInjector {
|
|
|
134
339
|
const query = this.deriveQuery(input.messages);
|
|
135
340
|
if (!query)
|
|
136
341
|
return;
|
|
137
|
-
const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt");
|
|
342
|
+
const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt", input.sessionID ?? "");
|
|
138
343
|
if (block)
|
|
139
344
|
output.system.push(block);
|
|
140
345
|
}
|
|
@@ -142,7 +347,7 @@ export class ContextInjector {
|
|
|
142
347
|
const query = this.deriveQuery(input.messages);
|
|
143
348
|
if (!query)
|
|
144
349
|
return;
|
|
145
|
-
const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting");
|
|
350
|
+
const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting", input.sessionID);
|
|
146
351
|
if (block)
|
|
147
352
|
output.context.push(block);
|
|
148
353
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Store } from "./Store.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
3
|
+
/**
|
|
4
|
+
* v0.4.0 InjectionLedger (K4-006/K4-007, plan §5.2, D4-04).
|
|
5
|
+
*
|
|
6
|
+
* The measurement half of the closed feedback loop: every lesson injected
|
|
7
|
+
* into a prompt is recorded in `kevin_injections`, and at `session.idle` the
|
|
8
|
+
* ledger settles each unmeasured row — if the same fingerprint failed again
|
|
9
|
+
* after the injection, the lesson is `ineffective`; otherwise `effective`.
|
|
10
|
+
* `precision_rate = effective / total` is the honest, measured value of the
|
|
11
|
+
* injection system (replacing Kevin_Token_Impact-style estimates).
|
|
12
|
+
*
|
|
13
|
+
* Schema (migration 005_v04_signal.sql):
|
|
14
|
+
* kevin_injections(id PK, memory_id, fingerprint, session_id, hook,
|
|
15
|
+
* tokens, injected_at, outcome)
|
|
16
|
+
*
|
|
17
|
+
* BUG-015 — a ledger row is only meaningful when its fingerprint can be
|
|
18
|
+
* matched against failing tool_calls (`COALESCE(error_fingerprint,
|
|
19
|
+
* fingerprint)`). Memories WITHOUT a fingerprint (agent-saved notes) can
|
|
20
|
+
* never match, so they are NOT recorded: settle() would otherwise mark
|
|
21
|
+
* them `effective` forever and inflate `precision_rate`. The caller
|
|
22
|
+
* (ContextInjector.recordInjections) skips them.
|
|
23
|
+
*/
|
|
24
|
+
export type InjectionHook = "pre_prompt" | "compacting";
|
|
25
|
+
export type InjectionOutcome = "unmeasured" | "effective" | "ineffective";
|
|
26
|
+
export interface InjectionRecordInput {
|
|
27
|
+
memoryId: string;
|
|
28
|
+
fingerprint: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
hook: InjectionHook;
|
|
31
|
+
tokens: number;
|
|
32
|
+
}
|
|
33
|
+
interface InjectionRow {
|
|
34
|
+
id: string;
|
|
35
|
+
memory_id: string;
|
|
36
|
+
fingerprint: string;
|
|
37
|
+
session_id: string;
|
|
38
|
+
hook: InjectionHook;
|
|
39
|
+
tokens: number;
|
|
40
|
+
injected_at: string;
|
|
41
|
+
outcome: InjectionOutcome;
|
|
42
|
+
}
|
|
43
|
+
export declare class InjectionLedger {
|
|
44
|
+
private readonly store;
|
|
45
|
+
private readonly metrics;
|
|
46
|
+
constructor(store: Store, metrics?: Metrics | null);
|
|
47
|
+
/**
|
|
48
|
+
* Records one injected memory. Idempotent at the row level (UUID PK);
|
|
49
|
+
* duplicates are expected only via the caller's per-session seen-set.
|
|
50
|
+
*/
|
|
51
|
+
record(input: InjectionRecordInput): void;
|
|
52
|
+
/**
|
|
53
|
+
* Settles every unmeasured injection of the session: a fingerprint that
|
|
54
|
+
* failed again (as a failing tool_call) after the injection is
|
|
55
|
+
* `ineffective`, otherwise `effective`. Idempotent — only
|
|
56
|
+
* `outcome = 'unmeasured'` rows are flipped, and the recurrence charge
|
|
57
|
+
* is `MAX(recurrence_count, n)` where n = all failing calls of the
|
|
58
|
+
* fingerprint after `injected_at` (a later idle re-computes n and
|
|
59
|
+
* catches up — plan §5.1 rule 4: 3 recurrences → stale).
|
|
60
|
+
*
|
|
61
|
+
* An ineffective injection also bumps the target memory's
|
|
62
|
+
* `recurrence_count` (negative evidence, plan §5.3) and stamps
|
|
63
|
+
* `last_injected_at`.
|
|
64
|
+
*/
|
|
65
|
+
settle(sessionId: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Per-fingerprint failing tool-call counts for the session. Feeds
|
|
68
|
+
* QualityGate.canInject and the HITL suggestion block.
|
|
69
|
+
*/
|
|
70
|
+
recurrencesFor(sessionId: string): Map<string, number>;
|
|
71
|
+
/**
|
|
72
|
+
* v0.4.0 (K4-017) — recurrence counts for the QualityGate at
|
|
73
|
+
* injection time: only failing calls that happened AFTER the
|
|
74
|
+
* fingerprint was already injected this session count. The failure
|
|
75
|
+
* that *created* a lesson is not a recurrence — it precedes any
|
|
76
|
+
* injection (plan §5.1 rule 4, same `ts >= injected_at` semantics
|
|
77
|
+
* `settle` uses).
|
|
78
|
+
*/
|
|
79
|
+
postInjectionRecurrencesFor(sessionId: string): Map<string, number>;
|
|
80
|
+
/** Number of rows for the session not yet settled (drives tests and settle). */
|
|
81
|
+
unsettledForSession(sessionId: string): number;
|
|
82
|
+
/** Latest ledger rows for a session, newest first (used by tests/tools). */
|
|
83
|
+
rowsForSession(sessionId: string): InjectionRow[];
|
|
84
|
+
}
|
|
85
|
+
export {};
|