@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.
Files changed (42) hide show
  1. package/README.md +50 -16
  2. package/dist/migrations/003_v02_signal.sql +58 -0
  3. package/dist/plugin/ContextInjector.d.ts +13 -2
  4. package/dist/plugin/ContextInjector.js +42 -23
  5. package/dist/plugin/ContextInjector.js.map +1 -1
  6. package/dist/plugin/MemoryService.d.ts +49 -2
  7. package/dist/plugin/MemoryService.js +182 -15
  8. package/dist/plugin/MemoryService.js.map +1 -1
  9. package/dist/plugin/Migrate.d.ts +4 -1
  10. package/dist/plugin/Migrate.js +28 -1
  11. package/dist/plugin/Migrate.js.map +1 -1
  12. package/dist/plugin/PatternMiner.d.ts +49 -0
  13. package/dist/plugin/PatternMiner.js +133 -0
  14. package/dist/plugin/PatternMiner.js.map +1 -0
  15. package/dist/plugin/Reflector.d.ts +29 -2
  16. package/dist/plugin/Reflector.js +90 -7
  17. package/dist/plugin/Reflector.js.map +1 -1
  18. package/dist/plugin/Retrospective.d.ts +4 -1
  19. package/dist/plugin/Retrospective.js +72 -5
  20. package/dist/plugin/Retrospective.js.map +1 -1
  21. package/dist/plugin/Store.d.ts +15 -0
  22. package/dist/plugin/Store.js +15 -0
  23. package/dist/plugin/Store.js.map +1 -1
  24. package/dist/plugin/ToolCallObserver.d.ts +4 -1
  25. package/dist/plugin/ToolCallObserver.js +39 -7
  26. package/dist/plugin/ToolCallObserver.js.map +1 -1
  27. package/dist/plugin/fingerprint.d.ts +27 -0
  28. package/dist/plugin/fingerprint.js +74 -0
  29. package/dist/plugin/fingerprint.js.map +1 -0
  30. package/dist/plugin/index.js +95 -24
  31. package/dist/plugin/index.js.map +1 -1
  32. package/dist/plugin/memory-format.d.ts +8 -0
  33. package/dist/plugin/memory-format.js +20 -0
  34. package/dist/plugin/memory-format.js.map +1 -0
  35. package/dist/plugin/metrics.d.ts +60 -0
  36. package/dist/plugin/metrics.js +159 -0
  37. package/dist/plugin/metrics.js.map +1 -0
  38. package/dist/plugin/redact.d.ts +1 -0
  39. package/dist/plugin/redact.js +7 -0
  40. package/dist/plugin/redact.js.map +1 -1
  41. package/migrations/003_v02_signal.sql +58 -0
  42. 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 five tools: `kevin_save`, `kevin_query`, `kevin_recall`, `kevin_status`, `kevin_retrospective`.
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 │ redacts paths/secrets, throttled 1/min,
99
- └────────┬────────┘ persists type:error memory
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
- └─────────────────┘ with summary of failures and lessons
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 5 tools callable by the agent:
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, "tool_calls": 318, "retrospectives": 7 }
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 (configurable via `throttleMs`).
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 + truncation
239
- ContextInjector.ts # deriveQuery + pre-prompt/compacting injection
240
- Retrospective.ts # Generates retrospective.md + table insert
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
- constructor(memoryService: MemoryService);
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
- constructor(memoryService) {
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 memories = this.memoryService.getRelevant({
103
- query,
104
- maxTokens: SYSTEM_TRANSFORM_TOKENS,
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 memories = this.memoryService.getRelevant({
115
- query,
116
- maxTokens: COMPACTING_TOKENS,
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":"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"}
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
- query(input: QueryInput): Memory[];
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
  }