hippo-memory 1.55.0 → 1.57.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 +11 -0
- package/dist/api.d.ts +19 -9
- package/dist/api.js +112 -35
- package/dist/card-detail.d.ts +1 -1
- package/dist/card-detail.js +1 -1
- package/dist/cli/shared.d.ts +137 -0
- package/dist/cli/shared.js +830 -0
- package/dist/cli/sleep.d.ts +10 -0
- package/dist/cli/sleep.js +171 -0
- package/dist/cli.d.ts +0 -7
- package/dist/cli.js +313 -1806
- package/dist/config.d.ts +5 -0
- package/dist/config.js +21 -0
- package/dist/connectors/github/webhook.d.ts +19 -0
- package/dist/connectors/github/webhook.js +313 -0
- package/dist/connectors/slack/webhook.d.ts +22 -0
- package/dist/connectors/slack/webhook.js +203 -0
- package/dist/consolidate.js +3 -2
- package/dist/context-auto.d.ts +3 -0
- package/dist/context-auto.js +34 -0
- package/dist/customer-notes.js +2 -1
- package/dist/dashboard.js +2 -1
- package/dist/db.js +67 -1
- package/dist/decisions.js +2 -1
- package/dist/delivery-recorder.d.ts +127 -0
- package/dist/delivery-recorder.js +218 -0
- package/dist/eval-stats.d.ts +58 -0
- package/dist/eval-stats.js +111 -0
- package/dist/goals.d.ts +49 -25
- package/dist/goals.js +39 -22
- package/dist/graph-extract.js +1 -1
- package/dist/graph-recall.d.ts +1 -1
- package/dist/graph-recall.js +1 -1
- package/dist/graph.js +1 -1
- package/dist/hooks.d.ts +1 -3
- package/dist/hooks.js +2 -4
- package/dist/http-util.d.ts +31 -0
- package/dist/http-util.js +46 -0
- package/dist/incidents.js +2 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/mcp/server.js +173 -285
- package/dist/memory.d.ts +19 -0
- package/dist/memory.js +38 -0
- package/dist/policies.js +2 -1
- package/dist/predictions.js +2 -1
- package/dist/processes.js +2 -1
- package/dist/project-briefs.js +3 -1
- package/dist/prompt-recall.js +1 -1
- package/dist/recall-history.d.ts +5 -0
- package/dist/recall-history.js +9 -0
- package/dist/recall-pipeline.d.ts +101 -0
- package/dist/recall-pipeline.js +313 -0
- package/dist/recall-scope.d.ts +22 -0
- package/dist/recall-scope.js +27 -1
- package/dist/recall-trace.d.ts +69 -0
- package/dist/recall-trace.js +136 -0
- package/dist/search.d.ts +0 -20
- package/dist/search.js +2 -49
- package/dist/server.js +1901 -2384
- package/dist/skills.js +2 -1
- package/dist/store-cards.d.ts +53 -0
- package/dist/store-cards.js +512 -0
- package/dist/store.d.ts +2 -89
- package/dist/store.js +6 -562
- package/dist/tenant.d.ts +22 -0
- package/dist/tenant.js +26 -0
- package/dist/token-ledger.d.ts +2 -0
- package/dist/token-ledger.js +5 -0
- package/dist/tokenize.d.ts +2 -0
- package/dist/tokenize.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
package/dist/recall-trace.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { type DatabaseSyncLike } from './db.js';
|
|
19
19
|
import type { RerankStep } from './search.js';
|
|
20
|
+
import { type DeliveryEventInput } from './delivery-recorder.js';
|
|
20
21
|
/** One ranked result to persist alongside its trace row. */
|
|
21
22
|
export interface RecallTraceResultInput {
|
|
22
23
|
memoryId: string;
|
|
@@ -114,4 +115,72 @@ export interface RecordTraceOutcomeInput {
|
|
|
114
115
|
* Fail-soft: never throws.
|
|
115
116
|
*/
|
|
116
117
|
export declare function recordTraceOutcome(db: DatabaseSyncLike, input: RecordTraceOutcomeInput): void;
|
|
118
|
+
/** Pruned on write, counted back from the event's ts capped at the real clock, so a far-future fake time spares real rows. */
|
|
119
|
+
export declare const DELIVERY_LEDGER_RETENTION_DAYS = 90;
|
|
120
|
+
/** Lock wait for the ledger's own connection: a busy store drops the row rather than slow the hook. */
|
|
121
|
+
export declare const DELIVERY_LEDGER_WAIT_MS = 50;
|
|
122
|
+
/** Two prompt-identical events without a host turn id this close together are one turn fired twice. */
|
|
123
|
+
export declare const DELIVERY_DUPLICATE_WINDOW_MS = 2000;
|
|
124
|
+
/** One stored `delivery_candidates` row. */
|
|
125
|
+
export interface DeliveryCandidateRow {
|
|
126
|
+
event_id: number;
|
|
127
|
+
tenant_id: string;
|
|
128
|
+
memory_id: string;
|
|
129
|
+
source_store: string;
|
|
130
|
+
pool: string;
|
|
131
|
+
stage: string;
|
|
132
|
+
outcome: string;
|
|
133
|
+
reason: string | null;
|
|
134
|
+
cand_rank: number | null;
|
|
135
|
+
score: number | null;
|
|
136
|
+
tokens: number | null;
|
|
137
|
+
}
|
|
138
|
+
/** One stored `delivery_events` row with its candidate rows. */
|
|
139
|
+
export interface DeliveryEventRow {
|
|
140
|
+
id: number;
|
|
141
|
+
ts: string;
|
|
142
|
+
ledger_version: number;
|
|
143
|
+
tenant_id: string;
|
|
144
|
+
runtime: string;
|
|
145
|
+
event_type: string;
|
|
146
|
+
surface: string;
|
|
147
|
+
store_hash: string;
|
|
148
|
+
write_store: string;
|
|
149
|
+
project_hash: string | null;
|
|
150
|
+
session_id: string | null;
|
|
151
|
+
session_state: string;
|
|
152
|
+
host_turn_id: string | null;
|
|
153
|
+
turn_seq: number | null;
|
|
154
|
+
duplicate_of: number | null;
|
|
155
|
+
prompt_hash: string | null;
|
|
156
|
+
prompt_length: number;
|
|
157
|
+
query_hash: string | null;
|
|
158
|
+
recall_trace_id: number | null;
|
|
159
|
+
block_state: string;
|
|
160
|
+
prompt_recall: number;
|
|
161
|
+
considered_count: number;
|
|
162
|
+
filtered_count: number;
|
|
163
|
+
selected_count: number;
|
|
164
|
+
emitted_count: number;
|
|
165
|
+
rejected_count: number;
|
|
166
|
+
rejected_unlisted: number;
|
|
167
|
+
sections_shown: number;
|
|
168
|
+
sections_dropped: number;
|
|
169
|
+
budget_tokens: number;
|
|
170
|
+
selected_tokens: number;
|
|
171
|
+
injected_tokens: number;
|
|
172
|
+
static_hash: string | null;
|
|
173
|
+
recall_hash: string | null;
|
|
174
|
+
emitted_hash: string | null;
|
|
175
|
+
elapsed_ms: number;
|
|
176
|
+
candidates: DeliveryCandidateRow[];
|
|
177
|
+
}
|
|
178
|
+
/** One event plus its candidates in one write transaction, then prune; fail-soft. The caller must not hold a transaction on `db`. */
|
|
179
|
+
export declare function writeDeliveryEvent(db: DatabaseSyncLike, input: DeliveryEventInput): number | null;
|
|
180
|
+
/** Write on a short-lived connection that waits at most {@link DELIVERY_LEDGER_WAIT_MS} for the lock. Fail-soft. */
|
|
181
|
+
export declare function writeDeliveryEventAtRoot(root: string, input: DeliveryEventInput): number | null;
|
|
182
|
+
/** On a caller's open handle, which saves a second open and close per turn; the handle's own lock wait comes back after. */
|
|
183
|
+
export declare function writeDeliveryEventOnHandle(db: DatabaseSyncLike, input: DeliveryEventInput): number | null;
|
|
184
|
+
/** A session's delivery events in write order, each with its candidate rows; `sessionId` null reads session-less events. */
|
|
185
|
+
export declare function readDeliveryEvents(db: DatabaseSyncLike, tenantId: string, sessionId: string | null): DeliveryEventRow[];
|
|
117
186
|
//# sourceMappingURL=recall-trace.d.ts.map
|
package/dist/recall-trace.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { createHash } from 'node:crypto';
|
|
19
19
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
20
|
+
import { DELIVERY_LEDGER_VERSION } from './delivery-recorder.js';
|
|
20
21
|
/**
|
|
21
22
|
* Strip a RerankStep down to {stage, multiplier, scoreBefore, scoreAfter}
|
|
22
23
|
* before persisting (F3 privacy fix, codex cross-model finding). `note` is
|
|
@@ -183,4 +184,139 @@ export function recordTraceOutcome(db, input) {
|
|
|
183
184
|
console.error(`[hippo] recall trace outcome write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
184
185
|
}
|
|
185
186
|
}
|
|
187
|
+
/** Pruned on write, counted back from the event's ts capped at the real clock, so a far-future fake time spares real rows. */
|
|
188
|
+
export const DELIVERY_LEDGER_RETENTION_DAYS = 90;
|
|
189
|
+
/** Lock wait for the ledger's own connection: a busy store drops the row rather than slow the hook. */
|
|
190
|
+
export const DELIVERY_LEDGER_WAIT_MS = 50;
|
|
191
|
+
/** Two prompt-identical events without a host turn id this close together are one turn fired twice. */
|
|
192
|
+
export const DELIVERY_DUPLICATE_WINDOW_MS = 2000;
|
|
193
|
+
const DELIVERY_EVENT_COLUMNS = [
|
|
194
|
+
'ts', 'ledger_version', 'tenant_id', 'runtime', 'event_type', 'surface', 'store_hash', 'write_store', 'project_hash',
|
|
195
|
+
'session_id', 'session_state', 'host_turn_id', 'turn_seq', 'duplicate_of', 'prompt_hash', 'prompt_length', 'query_hash',
|
|
196
|
+
'recall_trace_id', 'block_state', 'prompt_recall', 'considered_count', 'filtered_count', 'selected_count', 'emitted_count',
|
|
197
|
+
'rejected_count', 'rejected_unlisted', 'sections_shown', 'sections_dropped', 'budget_tokens', 'selected_tokens',
|
|
198
|
+
'injected_tokens', 'static_hash', 'recall_hash', 'emitted_hash', 'elapsed_ms',
|
|
199
|
+
];
|
|
200
|
+
function findDuplicateTurn(db, input) {
|
|
201
|
+
if (input.hostTurnId !== null) {
|
|
202
|
+
// SAFETY: a single `id` column, undefined when no row matches.
|
|
203
|
+
const row = db.prepare(`
|
|
204
|
+
SELECT id FROM delivery_events
|
|
205
|
+
WHERE tenant_id = ? AND session_id = ? AND event_type = ? AND host_turn_id = ? AND turn_seq IS NOT NULL
|
|
206
|
+
ORDER BY id LIMIT 1
|
|
207
|
+
`).get(input.tenantId, input.sessionId, input.eventType, input.hostTurnId);
|
|
208
|
+
return row?.id ?? null;
|
|
209
|
+
}
|
|
210
|
+
if (input.promptHash === null)
|
|
211
|
+
return null;
|
|
212
|
+
// SAFETY: rows carry exactly the `id` and `ts` columns selected.
|
|
213
|
+
const rows = db.prepare(`
|
|
214
|
+
SELECT id, ts FROM delivery_events
|
|
215
|
+
WHERE tenant_id = ? AND session_id = ? AND event_type = ? AND prompt_hash = ? AND host_turn_id IS NULL AND turn_seq IS NOT NULL
|
|
216
|
+
ORDER BY id
|
|
217
|
+
`).all(input.tenantId, input.sessionId, input.eventType, input.promptHash);
|
|
218
|
+
const at = Date.parse(input.ts);
|
|
219
|
+
// Absolute difference: two hook processes can commit out of ts order.
|
|
220
|
+
return rows.find((r) => Math.abs(Date.parse(r.ts) - at) <= DELIVERY_DUPLICATE_WINDOW_MS)?.id ?? null;
|
|
221
|
+
}
|
|
222
|
+
function nextTurnSeq(db, input) {
|
|
223
|
+
// SAFETY: a single MAX aggregate aliased `m`, NULL when the session has no turns yet.
|
|
224
|
+
const row = db.prepare(`
|
|
225
|
+
SELECT MAX(turn_seq) AS m FROM delivery_events
|
|
226
|
+
WHERE tenant_id = ? AND session_id = ? AND event_type = ? AND turn_seq IS NOT NULL
|
|
227
|
+
`).get(input.tenantId, input.sessionId, input.eventType);
|
|
228
|
+
return (row.m ?? 0) + 1;
|
|
229
|
+
}
|
|
230
|
+
/** One event plus its candidates in one write transaction, then prune; fail-soft. The caller must not hold a transaction on `db`. */
|
|
231
|
+
export function writeDeliveryEvent(db, input) {
|
|
232
|
+
try {
|
|
233
|
+
db.exec('BEGIN IMMEDIATE');
|
|
234
|
+
try {
|
|
235
|
+
// Missing-session and sub-agent events are not turns of a session, so they get no number and no duplicate check.
|
|
236
|
+
const isTurn = input.sessionId !== null && (input.sessionState === 'payload' || input.sessionState === 'env');
|
|
237
|
+
const duplicateOf = isTurn ? findDuplicateTurn(db, input) : null;
|
|
238
|
+
const turnSeq = isTurn && duplicateOf === null ? nextTurnSeq(db, input) : null;
|
|
239
|
+
const values = [
|
|
240
|
+
input.ts, DELIVERY_LEDGER_VERSION, input.tenantId, input.runtime, input.eventType, input.surface, input.storeHash,
|
|
241
|
+
input.writeStore, input.projectHash, input.sessionId, input.sessionState, input.hostTurnId, turnSeq, duplicateOf,
|
|
242
|
+
input.promptHash, input.promptLength, input.queryHash, input.recallTraceId, input.blockState, input.promptRecall ? 1 : 0,
|
|
243
|
+
input.consideredCount, input.filteredCount, input.selectedCount, input.emittedCount, input.rejectedCount,
|
|
244
|
+
input.rejectedUnlisted, input.sectionsShown, input.sectionsDropped, input.budgetTokens, input.selectedTokens,
|
|
245
|
+
input.injectedTokens, input.staticHash, input.recallHash, input.emittedHash, Math.round(input.elapsedMs),
|
|
246
|
+
];
|
|
247
|
+
const eventId = Number(db.prepare(`
|
|
248
|
+
INSERT INTO delivery_events (${DELIVERY_EVENT_COLUMNS.join(', ')})
|
|
249
|
+
VALUES (${DELIVERY_EVENT_COLUMNS.map(() => '?').join(', ')})
|
|
250
|
+
`).run(...values).lastInsertRowid);
|
|
251
|
+
const insertCandidate = db.prepare(`
|
|
252
|
+
INSERT INTO delivery_candidates (event_id, tenant_id, memory_id, source_store, pool, stage, outcome, reason, cand_rank, score, tokens)
|
|
253
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
254
|
+
`);
|
|
255
|
+
for (const c of input.candidates) {
|
|
256
|
+
insertCandidate.run(eventId, input.tenantId, c.memoryId, c.sourceStore, c.pool, c.stage, c.outcome, c.reason, c.rank, c.score, c.tokens);
|
|
257
|
+
}
|
|
258
|
+
const pruneFrom = Math.min(Date.parse(input.ts), Date.now());
|
|
259
|
+
const cutoff = new Date(pruneFrom - DELIVERY_LEDGER_RETENTION_DAYS * 86_400_000).toISOString();
|
|
260
|
+
db.prepare(`DELETE FROM delivery_events WHERE ts < ?`).run(cutoff);
|
|
261
|
+
db.exec('COMMIT');
|
|
262
|
+
return eventId;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
try {
|
|
266
|
+
db.exec('ROLLBACK');
|
|
267
|
+
}
|
|
268
|
+
catch { /* SQLite may already have rolled back (SQLITE_FULL, IOERR); keep the original error */ }
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
// eslint-disable-next-line no-console
|
|
274
|
+
console.error(`[hippo] delivery ledger write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Write on a short-lived connection that waits at most {@link DELIVERY_LEDGER_WAIT_MS} for the lock. Fail-soft. */
|
|
279
|
+
export function writeDeliveryEventAtRoot(root, input) {
|
|
280
|
+
let db;
|
|
281
|
+
try {
|
|
282
|
+
db = openHippoDb(root, { busyWaitMs: DELIVERY_LEDGER_WAIT_MS });
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
// eslint-disable-next-line no-console
|
|
286
|
+
console.error(`[hippo] delivery ledger write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
return writeDeliveryEvent(db, input);
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
closeHippoDb(db);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/** On a caller's open handle, which saves a second open and close per turn; the handle's own lock wait comes back after. */
|
|
297
|
+
export function writeDeliveryEventOnHandle(db, input) {
|
|
298
|
+
const prior = Math.trunc(Number(db.prepare('PRAGMA busy_timeout').get().timeout));
|
|
299
|
+
db.exec(`PRAGMA busy_timeout = ${DELIVERY_LEDGER_WAIT_MS}`);
|
|
300
|
+
try {
|
|
301
|
+
return writeDeliveryEvent(db, input);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
db.exec(`PRAGMA busy_timeout = ${prior}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** A session's delivery events in write order, each with its candidate rows; `sessionId` null reads session-less events. */
|
|
308
|
+
export function readDeliveryEvents(db, tenantId, sessionId) {
|
|
309
|
+
// SAFETY: SELECT * over delivery_events returns exactly the columns DeliveryEventRow names, less `candidates`.
|
|
310
|
+
const events = db.prepare(`SELECT * FROM delivery_events WHERE tenant_id = ? AND session_id IS ? ORDER BY id`)
|
|
311
|
+
.all(tenantId, sessionId);
|
|
312
|
+
const candidates = db.prepare(`
|
|
313
|
+
SELECT * FROM delivery_candidates WHERE event_id = ?
|
|
314
|
+
ORDER BY outcome = 'rejected', cand_rank, memory_id
|
|
315
|
+
`);
|
|
316
|
+
return events.map((e) => ({
|
|
317
|
+
...e,
|
|
318
|
+
// SAFETY: SELECT * over delivery_candidates returns exactly the columns DeliveryCandidateRow names.
|
|
319
|
+
candidates: candidates.all(e.id),
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
186
322
|
//# sourceMappingURL=recall-trace.js.map
|
package/dist/search.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { MemoryEntry } from './memory.js';
|
|
|
7
7
|
import type { PhysicsConfig } from './physics-config.js';
|
|
8
8
|
export declare const CHURN_STALE_RANK_MULTIPLIER = 0.5;
|
|
9
9
|
export declare function churnStaleFactor(entry: MemoryEntry): number;
|
|
10
|
-
export declare function tokenize(text: string): string[];
|
|
11
10
|
/**
|
|
12
11
|
* Tokenized BM25 corpus. Callers can pre-build this with `buildCorpus` once
|
|
13
12
|
* and reuse across many `hybridSearch` calls on the same entry set — the
|
|
@@ -287,25 +286,6 @@ export declare function search(query: string, entries: MemoryEntry[], options?:
|
|
|
287
286
|
includeSuperseded?: boolean;
|
|
288
287
|
asOf?: string;
|
|
289
288
|
}): SearchResult[];
|
|
290
|
-
/**
|
|
291
|
-
* Update retrieval metadata on entries that were returned by a search.
|
|
292
|
-
* Returns the mutated copies (caller must persist to disk).
|
|
293
|
-
*
|
|
294
|
-
* EVAL-ONLY ablation (see ablation.ts): with HIPPO_ABLATE_RECALL_BOOST set,
|
|
295
|
-
* this returns the entries UNMUTATED - neutralizing all three strengthening
|
|
296
|
-
* sub-effects (clock reset, retrieval_count, half-life increment) at the
|
|
297
|
-
* single shared write site. The entries (not an empty array) must be
|
|
298
|
-
* returned because callers derive `last_retrieval_ids` from the return
|
|
299
|
-
* value, and a later `hippo outcome --good/--bad` targets those ids - an
|
|
300
|
-
* empty return would silently co-ablate the outcome channel in the
|
|
301
|
-
* strengthen-off arm (codex round-7 P2). PERSISTENCE is gated separately at
|
|
302
|
-
* each persisting caller (CLI recall, api context, MCP recall/context,
|
|
303
|
-
* consolidation replay): writeEntry on identical rows still refreshes
|
|
304
|
-
* updated_at, rewrites mirrors, and marks DAG parents dirty (codex round-6
|
|
305
|
-
* P2), so those write loops skip under the flag.
|
|
306
|
-
* The default `now` honors HIPPO_FAKE_NOW (simulated-time protocols).
|
|
307
|
-
*/
|
|
308
|
-
export declare function markRetrieved(entries: MemoryEntry[], now?: Date): MemoryEntry[];
|
|
309
289
|
export interface MatchExplanation {
|
|
310
290
|
/** Human-readable reason string */
|
|
311
291
|
reason: string;
|
package/dist/search.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Zero external dependencies when embeddings are not available.
|
|
4
4
|
*/
|
|
5
5
|
import { estimateTokens } from './token-ledger.js';
|
|
6
|
-
import { calculateStrength,
|
|
6
|
+
import { calculateStrength, CHURN_STALE_TAG } from './memory.js';
|
|
7
|
+
import { tokenize } from './tokenize.js';
|
|
7
8
|
import { isOutcomeFastAblated, isRecallBoostAblated, isRecencyAblated, evalRecencyScaleDays, evalNow } from './ablation.js';
|
|
8
9
|
import { extractPathTags, pathBoostMultiplier } from './path-context.js';
|
|
9
10
|
import { detectScope, scopeMatch } from './scope.js';
|
|
@@ -20,16 +21,6 @@ export const CHURN_STALE_RANK_MULTIPLIER = 0.5; // SHORTCUT: untuned; FE3 measur
|
|
|
20
21
|
export function churnStaleFactor(entry) {
|
|
21
22
|
return entry.tags.includes(CHURN_STALE_TAG) ? CHURN_STALE_RANK_MULTIPLIER : 1.0;
|
|
22
23
|
}
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Tokenizer
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
export function tokenize(text) {
|
|
27
|
-
return text
|
|
28
|
-
.toLowerCase()
|
|
29
|
-
.replace(/[^\w\s]/g, ' ')
|
|
30
|
-
.split(/\s+/)
|
|
31
|
-
.filter((t) => t.length > 1);
|
|
32
|
-
}
|
|
33
24
|
const BM25_K1 = 1.5;
|
|
34
25
|
const BM25_B = 0.75;
|
|
35
26
|
export function buildCorpus(texts) {
|
|
@@ -902,44 +893,6 @@ export function search(query, entries, options = {}) {
|
|
|
902
893
|
}
|
|
903
894
|
return fitBudget(dedupedSync, budget, minResults, options.cost);
|
|
904
895
|
}
|
|
905
|
-
/**
|
|
906
|
-
* Update retrieval metadata on entries that were returned by a search.
|
|
907
|
-
* Returns the mutated copies (caller must persist to disk).
|
|
908
|
-
*
|
|
909
|
-
* EVAL-ONLY ablation (see ablation.ts): with HIPPO_ABLATE_RECALL_BOOST set,
|
|
910
|
-
* this returns the entries UNMUTATED - neutralizing all three strengthening
|
|
911
|
-
* sub-effects (clock reset, retrieval_count, half-life increment) at the
|
|
912
|
-
* single shared write site. The entries (not an empty array) must be
|
|
913
|
-
* returned because callers derive `last_retrieval_ids` from the return
|
|
914
|
-
* value, and a later `hippo outcome --good/--bad` targets those ids - an
|
|
915
|
-
* empty return would silently co-ablate the outcome channel in the
|
|
916
|
-
* strengthen-off arm (codex round-7 P2). PERSISTENCE is gated separately at
|
|
917
|
-
* each persisting caller (CLI recall, api context, MCP recall/context,
|
|
918
|
-
* consolidation replay): writeEntry on identical rows still refreshes
|
|
919
|
-
* updated_at, rewrites mirrors, and marks DAG parents dirty (codex round-6
|
|
920
|
-
* P2), so those write loops skip under the flag.
|
|
921
|
-
* The default `now` honors HIPPO_FAKE_NOW (simulated-time protocols).
|
|
922
|
-
*/
|
|
923
|
-
// Confidence is deliberately absent below: it is an epistemic tier, not a
|
|
924
|
-
// recency signal, and a stored 'stale' is always a deliberate mark (2026-09-07).
|
|
925
|
-
export function markRetrieved(entries, now = evalNow()) {
|
|
926
|
-
if (isRecallBoostAblated())
|
|
927
|
-
return entries;
|
|
928
|
-
return entries.map((e) => {
|
|
929
|
-
if (e.superseded_by)
|
|
930
|
-
return e;
|
|
931
|
-
const wrong = netWrong(e) > 0;
|
|
932
|
-
const updated = {
|
|
933
|
-
...e,
|
|
934
|
-
retrieval_count: e.retrieval_count + 1,
|
|
935
|
-
last_retrieved: wrong ? e.last_retrieved : now.toISOString(),
|
|
936
|
-
// +2 days half-life per retrieval (PLAN.md); a wrong memory keeps both, since last_retrieved is the decay anchor
|
|
937
|
-
half_life_days: wrong ? e.half_life_days : e.half_life_days + 2,
|
|
938
|
-
};
|
|
939
|
-
updated.strength = calculateStrength(updated, now);
|
|
940
|
-
return updated;
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
896
|
/**
|
|
944
897
|
* Explain why a search result matched a query.
|
|
945
898
|
* Computes which query terms overlapped with the document and whether
|