@jmtrin/opencode-kevin 0.1.4 → 0.2.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 +50 -16
- package/dist/migrations/003_v02_signal.sql +58 -0
- package/dist/plugin/ContextInjector.d.ts +13 -2
- package/dist/plugin/ContextInjector.js +42 -23
- package/dist/plugin/ContextInjector.js.map +1 -1
- package/dist/plugin/MemoryService.d.ts +49 -2
- package/dist/plugin/MemoryService.js +182 -15
- package/dist/plugin/MemoryService.js.map +1 -1
- package/dist/plugin/Migrate.d.ts +4 -1
- package/dist/plugin/Migrate.js +28 -1
- package/dist/plugin/Migrate.js.map +1 -1
- package/dist/plugin/PatternMiner.d.ts +49 -0
- package/dist/plugin/PatternMiner.js +133 -0
- package/dist/plugin/PatternMiner.js.map +1 -0
- package/dist/plugin/Reflector.d.ts +29 -2
- package/dist/plugin/Reflector.js +90 -7
- package/dist/plugin/Reflector.js.map +1 -1
- package/dist/plugin/Retrospective.d.ts +4 -1
- package/dist/plugin/Retrospective.js +72 -5
- package/dist/plugin/Retrospective.js.map +1 -1
- package/dist/plugin/Store.d.ts +15 -0
- package/dist/plugin/Store.js +15 -0
- package/dist/plugin/Store.js.map +1 -1
- package/dist/plugin/ToolCallObserver.d.ts +4 -1
- package/dist/plugin/ToolCallObserver.js +39 -7
- package/dist/plugin/ToolCallObserver.js.map +1 -1
- package/dist/plugin/fingerprint.d.ts +27 -0
- package/dist/plugin/fingerprint.js +74 -0
- package/dist/plugin/fingerprint.js.map +1 -0
- package/dist/plugin/index.js +95 -24
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/memory-format.d.ts +8 -0
- package/dist/plugin/memory-format.js +20 -0
- package/dist/plugin/memory-format.js.map +1 -0
- package/dist/plugin/metrics.d.ts +60 -0
- package/dist/plugin/metrics.js +159 -0
- package/dist/plugin/metrics.js.map +1 -0
- package/dist/plugin/redact.d.ts +1 -0
- package/dist/plugin/redact.js +7 -0
- package/dist/plugin/redact.js.map +1 -1
- package/migrations/003_v02_signal.sql +58 -0
- package/package.json +48 -48
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ For a **single project**, put the same `plugin` array in `./opencode.json` or `.
|
|
|
30
30
|
|
|
31
31
|
### 2. Restart OpenCode
|
|
32
32
|
|
|
33
|
-
Config is loaded once at startup and is **not hot-reloaded** — quit and reopen OpenCode after editing. On start, OpenCode resolves the npm spec, caches the plugin in `~/.cache/opencode/packages/@jmtrin/opencode-kevin/`, and exposes
|
|
33
|
+
Config is loaded once at startup and is **not hot-reloaded** — quit and reopen OpenCode after editing. On start, OpenCode resolves the npm spec, caches the plugin in `~/.cache/opencode/packages/@jmtrin/opencode-kevin/`, and exposes six tools: `kevin_save`, `kevin_query`, `kevin_get`, `kevin_recall`, `kevin_status`, `kevin_retrospective`.
|
|
34
34
|
|
|
35
35
|
### 3. Where data lives
|
|
36
36
|
|
|
@@ -90,31 +90,40 @@ Use `:memory:` for `dbPath` in tests.
|
|
|
90
90
|
┌─────────────────┐
|
|
91
91
|
│ OBSERVE │ ToolCallObserver records every call
|
|
92
92
|
│ ToolCallObserver│ (tool, args redacted, success, duration, error_type)
|
|
93
|
+
│ │ + stripPrivate blocks + opt-in dedup (v0.2.0)
|
|
93
94
|
└────────┬────────┘
|
|
94
95
|
│ on failure
|
|
95
96
|
▼
|
|
96
97
|
┌─────────────────┐
|
|
97
98
|
│ LEARN │ Reflector generates a heuristic lesson
|
|
98
|
-
│ Reflector │
|
|
99
|
-
|
|
99
|
+
│ Reflector │ per-error-code code table (v0.2.0 lesson v2),
|
|
100
|
+
│ │ per-fingerprint throttle, origin='reflector',
|
|
101
|
+
│ │ fingerprint=FNV-1a 64-bit
|
|
102
|
+
└────────┬────────┘
|
|
100
103
|
│
|
|
101
104
|
▼
|
|
102
105
|
┌─────────────────┐
|
|
103
106
|
│ SHARE │ ContextInjector injects relevant lessons
|
|
104
107
|
│ ContextInjector │ pre-prompt (1500 tokens) and on compacting (2000 tokens)
|
|
108
|
+
│ │ + <protect> wrapper + id: line (v0.2.0)
|
|
109
|
+
│ │ + origin-aware rank (v0.2.0)
|
|
110
|
+
│ │ + conditional budget (v0.2.0)
|
|
105
111
|
└────────┬────────┘
|
|
106
112
|
│ session.idle
|
|
107
113
|
▼
|
|
108
114
|
┌─────────────────┐
|
|
109
115
|
│ RETROSPECTIVE │ Retrospective generates ~/.opencode-kevin/retrospectives/<session>.md
|
|
110
|
-
|
|
116
|
+
│ │ with origin labels, false-positive recap, metrics snapshot (v0.2.0)
|
|
117
|
+
│ │ + boostPositiveReflectors (v0.2.0)
|
|
118
|
+
│ │ + PatternMiner.mine (opt-in, v0.2.0)
|
|
119
|
+
└─────────────────┘
|
|
111
120
|
```
|
|
112
121
|
|
|
113
122
|
---
|
|
114
123
|
|
|
115
124
|
## Tools
|
|
116
125
|
|
|
117
|
-
Kevin exposes
|
|
126
|
+
Kevin exposes 6 tools callable by the agent:
|
|
118
127
|
|
|
119
128
|
### `kevin_save`
|
|
120
129
|
|
|
@@ -129,13 +138,27 @@ kevin_save({ type: "decision", content: "We use vitest for tests", scope: "proje
|
|
|
129
138
|
|
|
130
139
|
### `kevin_query`
|
|
131
140
|
|
|
132
|
-
Searches memories by text (FTS5 + bm25).
|
|
141
|
+
Searches memories by text (FTS5 + bm25). Returns a **slim** payload by default (v0.2.0). Pass `full: true` for the v0.1.x full content body.
|
|
133
142
|
|
|
134
143
|
```
|
|
135
144
|
kevin_query({ query: "typecheck", type: "error", limit: 5 })
|
|
145
|
+
// → [{ "id": "...", "type": "error", "scope": "project", "score": -0.87, "snippet": "When bash fails with typecheck:..." }, ...]
|
|
146
|
+
|
|
147
|
+
kevin_query({ query: "typecheck", type: "error", limit: 5, full: true })
|
|
136
148
|
// → [{ "id": "...", "type": "error", "content": "...", "scope": "project" }, ...]
|
|
137
149
|
```
|
|
138
150
|
|
|
151
|
+
### `kevin_get`
|
|
152
|
+
|
|
153
|
+
Fetches a **single full memory** by id (v0.2.0 — progressive disclosure). Use when `kevin_query` returns a slim snippet and you need the complete content.
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
kevin_get({ id: "0195a3b2-..." })
|
|
157
|
+
// → { "id": "...", "type": "error", "content": "...", "scope": "project",
|
|
158
|
+
// "relevanceScore": 0.55, "origin": "reflector", "fingerprint": "cbf29ce484222325",
|
|
159
|
+
// "projectId": null, "metadata": null }
|
|
160
|
+
```
|
|
161
|
+
|
|
139
162
|
### `kevin_recall`
|
|
140
163
|
|
|
141
164
|
Retrieves relevant memories (greedy fill by relevance). Without `query`, returns all memories in scope.
|
|
@@ -147,11 +170,15 @@ kevin_recall({ query: "auth", limit: 3 })
|
|
|
147
170
|
|
|
148
171
|
### `kevin_status`
|
|
149
172
|
|
|
150
|
-
Global counts.
|
|
173
|
+
Global counts and metrics (v0.2.0 adds `memories_reflector`, `memories_agent`, `memories_pattern`, and a `metrics` object with 6 seeded counters).
|
|
151
174
|
|
|
152
175
|
```
|
|
153
176
|
kevin_status({})
|
|
154
|
-
// → { "memories": 42, "
|
|
177
|
+
// → { "memories": 42, "memories_reflector": 12, "memories_agent": 30, "memories_pattern": 0,
|
|
178
|
+
// "tool_calls": 318, "retrospectives": 7,
|
|
179
|
+
// "metrics": { "tokens_injected_pre_prompt": 51, "tokens_injected_compacting": 0,
|
|
180
|
+
// "reflections_throttled": 3, "duplicate_suppressions": 2,
|
|
181
|
+
// "tool_calls_deduped": 0, "patterns_mined": 0 } }
|
|
155
182
|
```
|
|
156
183
|
|
|
157
184
|
### `kevin_retrospective`
|
|
@@ -179,9 +206,9 @@ Kevin subscribes to 6 OpenCode hooks:
|
|
|
179
206
|
| `event` (`session.created`) | Captures current `sessionID` |
|
|
180
207
|
| `event` (`session.idle`) | Generates retrospective.md for the session |
|
|
181
208
|
|
|
182
|
-
**Redaction**: absolute paths (`C:\Users\...`, `/home/...`) → `<path>` and secrets (`API_KEY=`, `Bearer`, `token`) → `<redacted>` before persisting anything.
|
|
209
|
+
**Redaction**: absolute paths (`C:\Users\...`, `/home/...`) → `<path>` and secrets (`API_KEY=`, `Bearer`, `token`) → `<redacted>` before persisting anything. v0.2.0 adds `<private>…</private>` block redaction: sweeps tool call args and output before persistence, replaces with `<private: redacted N chars>`.
|
|
183
210
|
|
|
184
|
-
**Throttle**: Reflector generates at most 1 lesson per minute (
|
|
211
|
+
**Throttle**: Reflector generates at most 1 lesson per minute per unique fingerprint (v0.2.0: per-fingerprint, not global). Configurable via `throttleMs`.
|
|
185
212
|
|
|
186
213
|
**Truncation**: content > 4KB keeps the lesson searchable; only the additional context is truncated (`metadata.truncated = true`).
|
|
187
214
|
|
|
@@ -232,15 +259,22 @@ npm publish --access public
|
|
|
232
259
|
plugin/
|
|
233
260
|
index.ts # Entry point: KevinPlugin
|
|
234
261
|
Store.ts # Wrapper SQLite (node:sqlite / bun:sqlite / better-sqlite3 fallback)
|
|
235
|
-
Migrate.ts # Idempotent migrations
|
|
236
|
-
MemoryService.ts # save/query/getRelevant (FTS5 + bm25)
|
|
237
|
-
ToolCallObserver.ts # onBefore/onAfter + redact + inferErrorType
|
|
238
|
-
Reflector.ts # Heuristic lessons + throttle +
|
|
239
|
-
ContextInjector.ts # deriveQuery + pre-prompt/compacting injection
|
|
240
|
-
Retrospective.ts # Generates retrospective.md +
|
|
262
|
+
Migrate.ts # Idempotent migrations + post-apply hooks
|
|
263
|
+
MemoryService.ts # save/query/getRelevant (FTS5 + bm25 + origin-aware rank)
|
|
264
|
+
ToolCallObserver.ts # onBefore/onAfter + redact + inferErrorType + dedup (opt-in)
|
|
265
|
+
Reflector.ts # Heuristic lessons + per-fingerprint throttle + lesson v2
|
|
266
|
+
ContextInjector.ts # deriveQuery + pre-prompt/compacting injection + conditional budget
|
|
267
|
+
Retrospective.ts # Generates retrospective.md + FP recap + metrics snapshot
|
|
268
|
+
fingerprint.ts # FNV-1a 64-bit (in-house, no node:crypto)
|
|
269
|
+
metrics.ts # In-memory counters + debounced flush to kevin_metrics
|
|
270
|
+
PatternMiner.ts # Opt-in deterministic 2-gram/3-gram miner
|
|
271
|
+
memory-format.ts # escapeInjectedText, formatMemories, <protect> + id: line wrappers
|
|
272
|
+
redact.ts # redactPaths + stripPrivate
|
|
273
|
+
uuid.ts # UUIDv7
|
|
241
274
|
migrations/
|
|
242
275
|
001_initial.sql # schema: memories, tool_calls, retrospectives
|
|
243
276
|
002_indexes.sql # FTS5 + indexes
|
|
277
|
+
003_v02_signal.sql # v0.2.0 Signal Quality: fingerprint, origin, metrics, dedup indexes
|
|
244
278
|
tests/{unit,integration,e2e}/
|
|
245
279
|
scripts/
|
|
246
280
|
copy-migrations.mjs # build step: copies *.sql to dist/migrations
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- Kevin 0.2.0 — Migration 003: Signal Quality (additive)
|
|
3
|
+
-- ============================================================
|
|
4
|
+
-- Backward-compatible, additive only. All new columns are
|
|
5
|
+
-- nullable or carry a NOT NULL DEFAULT so legacy rows keep
|
|
6
|
+
-- working without a destructive rebuild. The partial UNIQUE
|
|
7
|
+
-- index excludes NULL fingerprints so legacy error memories
|
|
8
|
+
-- do not collide. The schema_version table (created by 001)
|
|
9
|
+
-- guarantees this file runs at most once per DB.
|
|
10
|
+
-- ============================================================
|
|
11
|
+
|
|
12
|
+
-- 1. memories: Signal Quality columns.
|
|
13
|
+
-- project_id — first-class scoping dimension (D2-11).
|
|
14
|
+
-- fingerprint — stable hash of normalized error content, salted by project_id (D2-14).
|
|
15
|
+
-- origin — distinguishes reflector-sourced vs agent-sourced memories (D2-06),
|
|
16
|
+
-- with a CHECK enum and DEFAULT 'agent' so legacy rows backfill cleanly.
|
|
17
|
+
ALTER TABLE memories ADD COLUMN project_id TEXT;
|
|
18
|
+
ALTER TABLE memories ADD COLUMN fingerprint TEXT;
|
|
19
|
+
ALTER TABLE memories ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'
|
|
20
|
+
CHECK(origin IN ('reflector', 'agent', 'pattern', 'retrospective'));
|
|
21
|
+
|
|
22
|
+
-- 2. tool_calls: same scoping/fingerprint columns for dedup and PatternMiner.
|
|
23
|
+
ALTER TABLE tool_calls ADD COLUMN project_id TEXT;
|
|
24
|
+
ALTER TABLE tool_calls ADD COLUMN fingerprint TEXT;
|
|
25
|
+
|
|
26
|
+
-- 3. Partial UNIQUE: one reflector-sourced error memory per (project_id, fingerprint).
|
|
27
|
+
-- NULL fingerprints are excluded (no dedup for non-error memories or legacy rows).
|
|
28
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_memories_error_fp
|
|
29
|
+
ON memories (project_id, fingerprint)
|
|
30
|
+
WHERE type = 'error' AND fingerprint IS NOT NULL AND origin = 'reflector';
|
|
31
|
+
|
|
32
|
+
-- 4. kevin_metrics: seeded counters surfaced by kevin_status (D2-07 / K2-004).
|
|
33
|
+
CREATE TABLE IF NOT EXISTS kevin_metrics (
|
|
34
|
+
key TEXT PRIMARY KEY,
|
|
35
|
+
value INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
INSERT OR IGNORE INTO kevin_metrics (key, value) VALUES
|
|
40
|
+
('tokens_injected_pre_prompt', 0),
|
|
41
|
+
('tokens_injected_compacting', 0),
|
|
42
|
+
('reflections_throttled', 0),
|
|
43
|
+
('duplicate_suppressions', 0),
|
|
44
|
+
('tool_calls_deduped', 0),
|
|
45
|
+
('patterns_mined', 0);
|
|
46
|
+
|
|
47
|
+
-- 5. kevin_settings: opt-in feature flags (PatternMiner off, tool_calls dedup off).
|
|
48
|
+
CREATE TABLE IF NOT EXISTS kevin_settings (
|
|
49
|
+
key TEXT PRIMARY KEY,
|
|
50
|
+
value TEXT NOT NULL
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
INSERT OR IGNORE INTO kevin_settings (key, value) VALUES
|
|
54
|
+
('patternminer_enabled', '0'),
|
|
55
|
+
('tool_calls_dedup_enabled', '0');
|
|
56
|
+
|
|
57
|
+
-- 6. Seed version 003.
|
|
58
|
+
INSERT OR IGNORE INTO schema_version (version) VALUES ('003');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
+
import { type Metrics } from "./metrics.js";
|
|
2
3
|
export interface ChatMessage {
|
|
3
4
|
role: string;
|
|
4
5
|
content: string;
|
|
@@ -19,9 +20,19 @@ export interface CompactingOutput {
|
|
|
19
20
|
}
|
|
20
21
|
export declare class ContextInjector {
|
|
21
22
|
private memoryService;
|
|
22
|
-
|
|
23
|
+
private metrics;
|
|
24
|
+
constructor(memoryService: MemoryService, metrics?: Metrics | null);
|
|
25
|
+
/**
|
|
26
|
+
* v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
|
|
27
|
+
* to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
|
|
28
|
+
* NOT re-rank on its own — it consumes the already-ranked slice and
|
|
29
|
+
* applies the conditional-budget guard for opt-out (`protect: false`)
|
|
30
|
+
* callers. Plan §B6.5: "apply the same multiplier as
|
|
31
|
+
* MemoryService.recall so reflector lessons outrank agent-saved notes
|
|
32
|
+
* at injection time" — satisfied transitively via the getRelevant call.
|
|
33
|
+
*/
|
|
34
|
+
private inject;
|
|
23
35
|
deriveQuery(messages: ChatMessage[]): string;
|
|
24
36
|
onSystemTransform(input: SystemTransformInput, output: SystemTransformOutput): void;
|
|
25
37
|
onCompacting(input: CompactingInput, output: CompactingOutput): void;
|
|
26
|
-
private formatMemories;
|
|
27
38
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatMemories } from "./memory-format.js";
|
|
2
|
+
import { estimateTokens } from "./metrics.js";
|
|
1
3
|
const SYSTEM_TRANSFORM_TOKENS = 1500;
|
|
2
4
|
const COMPACTING_TOKENS = 2000;
|
|
3
5
|
const STOP_WORDS = new Set([
|
|
@@ -68,8 +70,41 @@ function isWordChar(ch) {
|
|
|
68
70
|
}
|
|
69
71
|
export class ContextInjector {
|
|
70
72
|
memoryService;
|
|
71
|
-
|
|
73
|
+
metrics;
|
|
74
|
+
constructor(memoryService, metrics = null) {
|
|
72
75
|
this.memoryService = memoryService;
|
|
76
|
+
this.metrics = metrics;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* v0.2.0 (K2-024): origin-aware ranking at injection time is delegated
|
|
80
|
+
* to `MemoryService.getRelevant()` (K2-023, D2-13). The injector does
|
|
81
|
+
* NOT re-rank on its own — it consumes the already-ranked slice and
|
|
82
|
+
* applies the conditional-budget guard for opt-out (`protect: false`)
|
|
83
|
+
* callers. Plan §B6.5: "apply the same multiplier as
|
|
84
|
+
* MemoryService.recall so reflector lessons outrank agent-saved notes
|
|
85
|
+
* at injection time" — satisfied transitively via the getRelevant call.
|
|
86
|
+
*/
|
|
87
|
+
inject(query, tag, cap, metricKey) {
|
|
88
|
+
let memories = this.memoryService.getRelevant({ query, maxTokens: cap });
|
|
89
|
+
if (memories.length === 0)
|
|
90
|
+
return "";
|
|
91
|
+
const firstBlock = formatMemories(memories, tag);
|
|
92
|
+
const aggregateTokens = estimateTokens(firstBlock);
|
|
93
|
+
const firstRowProtect = memories[0]
|
|
94
|
+
?.protect;
|
|
95
|
+
const noProtectAboveTheFold = firstRowProtect === false;
|
|
96
|
+
if (aggregateTokens > 0.8 * cap && noProtectAboveTheFold) {
|
|
97
|
+
const lowerCap = Math.max(1, Math.round(0.8 * cap));
|
|
98
|
+
memories = this.memoryService.getRelevant({
|
|
99
|
+
query,
|
|
100
|
+
maxTokens: lowerCap,
|
|
101
|
+
});
|
|
102
|
+
if (memories.length === 0)
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
const block = formatMemories(memories, tag);
|
|
106
|
+
this.metrics?.incr(metricKey, estimateTokens(block));
|
|
107
|
+
return block;
|
|
73
108
|
}
|
|
74
109
|
deriveQuery(messages) {
|
|
75
110
|
let lastUserContent = "";
|
|
@@ -99,33 +134,17 @@ export class ContextInjector {
|
|
|
99
134
|
const query = this.deriveQuery(input.messages);
|
|
100
135
|
if (!query)
|
|
101
136
|
return;
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
if (memories.length === 0)
|
|
107
|
-
return;
|
|
108
|
-
output.system.push(this.formatMemories(memories, "context"));
|
|
137
|
+
const block = this.inject(query, "context", SYSTEM_TRANSFORM_TOKENS, "tokens_injected_pre_prompt");
|
|
138
|
+
if (block)
|
|
139
|
+
output.system.push(block);
|
|
109
140
|
}
|
|
110
141
|
onCompacting(input, output) {
|
|
111
142
|
const query = this.deriveQuery(input.messages);
|
|
112
143
|
if (!query)
|
|
113
144
|
return;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
if (memories.length === 0)
|
|
119
|
-
return;
|
|
120
|
-
output.context.push(this.formatMemories(memories, "memory"));
|
|
121
|
-
}
|
|
122
|
-
formatMemories(memories, format) {
|
|
123
|
-
const lines = memories.map((m) => `[${m.type}] ${m.content}`);
|
|
124
|
-
const body = lines.join("\n");
|
|
125
|
-
if (format === "context") {
|
|
126
|
-
return `<kevin-context>Lecciones relevantes:\n${body}\n</kevin-context>`;
|
|
127
|
-
}
|
|
128
|
-
return `<kevin-memory>\n${body}\n</kevin-memory>`;
|
|
145
|
+
const block = this.inject(query, "memory", COMPACTING_TOKENS, "tokens_injected_compacting");
|
|
146
|
+
if (block)
|
|
147
|
+
output.context.push(block);
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
//# sourceMappingURL=ContextInjector.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ContextInjector.js","sourceRoot":"","sources":["../../plugin/ContextInjector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Store } from "./Store.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
2
3
|
export type MemoryType = "error" | "pattern" | "decision" | "context";
|
|
3
4
|
export type MemoryScope = "project" | "session";
|
|
5
|
+
export type MemoryOrigin = "reflector" | "agent" | "pattern" | "retrospective";
|
|
4
6
|
export interface Memory {
|
|
5
7
|
id: string;
|
|
6
8
|
type: MemoryType;
|
|
@@ -13,6 +15,12 @@ export interface Memory {
|
|
|
13
15
|
createdAt: string;
|
|
14
16
|
updatedAt: string;
|
|
15
17
|
expiresAt?: string | null;
|
|
18
|
+
/** v0.2.0 — project the memory belongs to (nullable for legacy rows). */
|
|
19
|
+
projectId?: string | null;
|
|
20
|
+
/** v0.2.0 — content fingerprint for dedup (only set for `type='error'`). */
|
|
21
|
+
fingerprint?: string | null;
|
|
22
|
+
/** v0.2.0 — origin of the memory. */
|
|
23
|
+
origin?: MemoryOrigin | null;
|
|
16
24
|
}
|
|
17
25
|
export interface SaveInput {
|
|
18
26
|
type: MemoryType;
|
|
@@ -23,12 +31,33 @@ export interface SaveInput {
|
|
|
23
31
|
sourceSession?: string;
|
|
24
32
|
metadata?: Record<string, unknown>;
|
|
25
33
|
expiresAt?: string;
|
|
34
|
+
/** v0.2.0 — project id. When absent, the memory is cross-project (NULL project_id). */
|
|
35
|
+
projectId?: string;
|
|
36
|
+
/** v0.2.0 — origin. Defaults to `'agent'` when omitted. */
|
|
37
|
+
origin?: MemoryOrigin;
|
|
38
|
+
/** v0.2.0 — explicit fingerprint. Auto-derived for `type='error'` if absent. */
|
|
39
|
+
fingerprint?: string;
|
|
26
40
|
}
|
|
27
41
|
export interface QueryInput {
|
|
28
42
|
text: string;
|
|
29
43
|
type?: string;
|
|
30
44
|
scope?: MemoryScope | "all";
|
|
31
45
|
limit?: number;
|
|
46
|
+
/** v0.2.0 — when true, returns full `Memory` rows (v0.1.x behavior).
|
|
47
|
+
* When `false` or absent, returns `SlimMemory` rows (default v0.2.0
|
|
48
|
+
* behavior, per plan §B6.3 / K2-010).
|
|
49
|
+
*/
|
|
50
|
+
full?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** v0.2.0 — slim query payload (K2-010). Snippet is a short content prefix;
|
|
53
|
+
* `score` is the FTS5 BM25 score when available, falling back to
|
|
54
|
+
* `relevanceScore` for non-FTS callers. */
|
|
55
|
+
export interface SlimMemory {
|
|
56
|
+
id: string;
|
|
57
|
+
type: MemoryType;
|
|
58
|
+
scope: MemoryScope;
|
|
59
|
+
score: number;
|
|
60
|
+
snippet: string;
|
|
32
61
|
}
|
|
33
62
|
export interface GetRelevantInput {
|
|
34
63
|
query?: string;
|
|
@@ -36,14 +65,32 @@ export interface GetRelevantInput {
|
|
|
36
65
|
scope?: MemoryScope | "all";
|
|
37
66
|
}
|
|
38
67
|
export declare class MemoryService {
|
|
68
|
+
private readonly metrics;
|
|
69
|
+
constructor(store: Store, metrics?: Metrics | null);
|
|
39
70
|
private store;
|
|
40
|
-
constructor(store: Store);
|
|
41
71
|
save(input: SaveInput): string;
|
|
42
72
|
getById(id: string): Memory | null;
|
|
43
73
|
update(id: string, fields: Partial<Memory>): void;
|
|
44
74
|
delete(id: string): void;
|
|
45
|
-
|
|
75
|
+
/** v0.1.x behavior — returns full `Memory` rows. */
|
|
76
|
+
query(input: QueryInput & {
|
|
77
|
+
full: true;
|
|
78
|
+
}): Memory[];
|
|
79
|
+
/** v0.2.0 default — returns `SlimMemory` rows. */
|
|
80
|
+
query(input: QueryInput): SlimMemory[];
|
|
46
81
|
private loadAll;
|
|
47
82
|
private queryRelevant;
|
|
48
83
|
getRelevant(input: GetRelevantInput): Memory[];
|
|
84
|
+
/**
|
|
85
|
+
* v0.2.0 (K2-026) — Feedback loop positive half (plan §B6.10 / D2-10).
|
|
86
|
+
*
|
|
87
|
+
* For each reflector-sourced error memory emitted during the given session
|
|
88
|
+
* whose fingerprint did NOT recur as a failing `tool_call` within the same
|
|
89
|
+
* project, bump `relevance_score` by `RELEVANCE_BUMP` (capped at
|
|
90
|
+
* `RELEVANCE_MAX`). Agent-saved memories are NEVER auto-boosted by this
|
|
91
|
+
* loop (anti-gaming guarantee, D2-06).
|
|
92
|
+
*
|
|
93
|
+
* Returns the number of memories that received a positive boost.
|
|
94
|
+
*/
|
|
95
|
+
boostPositiveReflectors(sessionId: string): number;
|
|
49
96
|
}
|