hippo-memory 1.64.0 → 1.65.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-memories/sync.js +3 -2
  3. package/dist/api/recall-record.d.ts +46 -0
  4. package/dist/api/recall-record.js +98 -0
  5. package/dist/api/recall-request.d.ts +32 -0
  6. package/dist/api/recall-request.js +121 -0
  7. package/dist/api/recall-types.d.ts +7 -1
  8. package/dist/api/recall.d.ts +3 -2
  9. package/dist/api/recall.js +174 -181
  10. package/dist/api/tokens.d.ts +1 -1
  11. package/dist/api/tokens.js +13 -17
  12. package/dist/cli/maintenance.d.ts +2 -1
  13. package/dist/cli/maintenance.js +17 -11
  14. package/dist/cli/recall.d.ts +1 -1
  15. package/dist/cli/recall.js +18 -49
  16. package/dist/cli/shared.js +2 -2
  17. package/dist/cli.d.ts +16 -0
  18. package/dist/cli.js +18 -1
  19. package/dist/config.d.ts +2 -0
  20. package/dist/config.js +4 -0
  21. package/dist/db/connect.d.ts +5 -0
  22. package/dist/db/connect.js +52 -0
  23. package/dist/db/open.d.ts +9 -11
  24. package/dist/db/open.js +23 -116
  25. package/dist/db/request-stores.d.ts +30 -0
  26. package/dist/db/request-stores.js +100 -0
  27. package/dist/db/sqlite-blocked.d.ts +6 -0
  28. package/dist/db/sqlite-blocked.js +11 -0
  29. package/dist/db.d.ts +3 -1
  30. package/dist/db.js +3 -1
  31. package/dist/embedding-provider.js +4 -3
  32. package/dist/embeddings.js +2 -1
  33. package/dist/goals.d.ts +18 -11
  34. package/dist/goals.js +38 -36
  35. package/dist/http-util.d.ts +2 -0
  36. package/dist/http-util.js +5 -0
  37. package/dist/local-embedding.d.ts +2 -0
  38. package/dist/local-embedding.js +20 -4
  39. package/dist/mcp/memory-tools.js +4 -1
  40. package/dist/mcp/recall-tools.js +28 -133
  41. package/dist/mcp/request.d.ts +7 -0
  42. package/dist/mcp/request.js +63 -41
  43. package/dist/mcp/session-state.d.ts +1 -3
  44. package/dist/mcp/session-state.js +3 -5
  45. package/dist/predictions/planning-fallacy.d.ts +52 -0
  46. package/dist/predictions/planning-fallacy.js +70 -55
  47. package/dist/recall-trace.d.ts +1 -1
  48. package/dist/recall-trace.js +5 -4
  49. package/dist/search/physics-search.js +4 -1
  50. package/dist/search/vector.js +2 -0
  51. package/dist/server/routes/recall.d.ts +1 -1
  52. package/dist/server/routes/recall.js +30 -218
  53. package/dist/server.d.ts +12 -1
  54. package/dist/server.js +33 -22
  55. package/dist/store/entry-writes.d.ts +2 -0
  56. package/dist/store/entry-writes.js +24 -16
  57. package/dist/store-port.d.ts +70 -1
  58. package/dist/store-port.js +87 -9
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  62. package/extensions/openclaw-plugin/package.json +1 -1
  63. package/openclaw.plugin.json +1 -1
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -994,7 +994,7 @@ Three benchmarks testing three different things. Full details in [`benchmarks/`]
994
994
 
995
995
  [LongMemEval](https://arxiv.org/abs/2410.10813) (ICLR 2025) is the industry-standard benchmark: 500 questions across 5 memory abilities, embedded in 115k+ token chat histories.
996
996
 
997
- **`hippo recall` (measured 2026-09-28 on hippo-memory 1.52.5).** On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 `_s` questions (95% CI 82.4 to 88.6), and 87.6% with the optional MiniLM embedder (84.6 to 90.4). Most of the gap to the scripts below is the default 4,000-token budget. Each memory in this test is a whole session, about 2,600 tokens at the median, so recall returns a median of 2 sessions. With the budget lifted, the same rankings score 96.8% (95.2 to 98.2) and 97.4% (96.0 to 98.6). A lifted call returns every candidate, a median of 47 sessions and 123,491 tokens per question, so those two figures measure the ranking, not an amount of text an agent could take in. Result: [`docs/evals/2026-09-28-recall-cli-longmemeval-result.md`](docs/evals/2026-09-28-recall-cli-longmemeval-result.md).
997
+ **`hippo recall` (measured 2026-09-28 on hippo-memory 1.52.5, re-run 2026-10-06 on 1.64.0).** On a default install, `hippo recall` puts an answer session in its top 5 for 85.6% of the 500 `_s` questions (95% CI 82.4 to 88.6), and 87.8% with the optional MiniLM embedder (84.8 to 90.6; 87.6% on 1.52.5). Most of the gap to the scripts below is the default 4,000-token budget. Each memory in this test is a whole session, about 2,600 tokens at the median, so recall returns a median of 2 sessions. With the budget lifted, the same rankings score 96.8% (95.2 to 98.2) and 97.4% (96.0 to 98.6). A lifted call returns every candidate, a median of 47 sessions and 123,491 tokens per question, so those two figures measure the ranking, not an amount of text an agent could take in. Result: [`docs/evals/2026-09-28-recall-cli-longmemeval-result.md`](docs/evals/2026-09-28-recall-cli-longmemeval-result.md); re-run: [`docs/evals/2026-10-06-recall-cli-longmemeval-rerun-1.64.0.md`](docs/evals/2026-10-06-recall-cli-longmemeval-rerun-1.64.0.md).
998
998
 
999
999
  **The benchmark scripts (`_s` split; MiniLM re-measured 2026-09-23, voyage measured 2026-06-09).** Each question is scored against its own ~48-session haystack, the same way gbrain and other published systems report. The v1.23.0 pluggable embedding provider lets you choose the embedder:
1000
1000
 
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { errorMessage } from '../log.js';
7
7
  import { loadConfig } from '../config.js';
8
- import { closeHippoDb, isSqliteBusy, openHippoDb } from '../db.js';
8
+ import { closeHippoDb, isSqliteBusy, openHippoDb, outsideRequestStores } from '../db.js';
9
9
  import { namesFoldedInto } from '../project-merge.js';
10
10
  import { isGlobalStoreRoot, originInSql, projectNames, resolveGlobalRootDir, resolveProjectIdentity } from '../project-identity.js';
11
11
  import { duplicateKey, heldTextKeys } from '../same-text.js';
@@ -169,7 +169,8 @@ function openTarget(target, hasItems, opts) {
169
169
  }
170
170
  function emptyStandIn(global) {
171
171
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hippo-agent-memories-'));
172
- const db = openHippoDb(root);
172
+ // Its folder is deleted at close, so a request scope must not keep the file open past that.
173
+ const db = outsideRequestStores(() => openHippoDb(root));
173
174
  return {
174
175
  db, root, global,
175
176
  close: () => {
@@ -0,0 +1,46 @@
1
+ import { type AppendAuditOpts, type AuditOp } from '../audit.js';
2
+ import type { AvailabilityHint } from '../availability.js';
3
+ import { RingBuffer, type AnchoringHint, type RecallHistorySnapshot } from '../recall-history.js';
4
+ export type RecallSurface = 'cli' | 'mcp' | 'http';
5
+ /** The session's ring, created on first use; null when anchoring is off or the call has no session. */
6
+ export declare function sessionRing(surface: RecallSurface, tenantId: string, sessionId: string | undefined): RingBuffer | null;
7
+ /** The session's history, read without creating or touching its ring, so a request that then fails evicts no live session. */
8
+ export declare function peekSessionRing(surface: RecallSurface, tenantId: string, sessionId: string): RecallHistorySnapshot;
9
+ /** Records this recall's top row and hint, which the next recall's cooldown reads. */
10
+ export declare function noteRecall(ring: RingBuffer, query: string, topId: string | null, anchoredOn: string | undefined): void;
11
+ export declare function resetSessionRings(surface: RecallSurface): void;
12
+ export interface RecallAuditor {
13
+ readonly hippoRoot: string;
14
+ readonly tenantId: string;
15
+ readonly actor: string;
16
+ /** Log and count a failed write instead of throwing, for a CLI command that has already done its work. */
17
+ readonly bestEffort?: boolean;
18
+ }
19
+ /** Whom a recall audit row names; a surface that writes through the store port needs no root. */
20
+ export type RecallAuditCaller = Pick<RecallAuditor, 'tenantId' | 'actor'>;
21
+ type RecallAuditMetadata = Readonly<Record<string, string | number | null>>;
22
+ export declare function recallAuditRow(who: RecallAuditCaller, op: AuditOp, targetId?: string, metadata?: RecallAuditMetadata): AppendAuditOpts;
23
+ /** One audit row on its own short-lived handle. */
24
+ export declare function appendRecallAudit(who: RecallAuditor, op: AuditOp, targetId?: string, metadata?: RecallAuditMetadata): void;
25
+ export declare function recallAuditMetadata(query: string, results: number): {
26
+ results: number;
27
+ query_hash: string;
28
+ query_length: number;
29
+ };
30
+ /** No session means no ring; the row hashes with SHA-256/16, since hashQueryText is FNV-1a and easy to reverse on short queries. */
31
+ export declare function anchorSkippedRows(who: RecallAuditCaller, query: string): AppendAuditOpts[];
32
+ export declare function anchoringRows(who: RecallAuditCaller, hint: AnchoringHint | null): AppendAuditOpts[];
33
+ export declare function availabilityRows(who: RecallAuditCaller, hint: AvailabilityHint | null): AppendAuditOpts[];
34
+ export interface ShownRecall {
35
+ readonly query: string;
36
+ readonly ring: RingBuffer | null;
37
+ readonly topId: string | null;
38
+ readonly anchoring: AnchoringHint | null;
39
+ readonly availability: AvailabilityHint | null;
40
+ }
41
+ /** The hint rows for a list a surface showed, so a surface on the store port can hand them to the recall's one write. */
42
+ export declare function shownRecallRows(who: RecallAuditCaller, shown: ShownRecall): AppendAuditOpts[];
43
+ /** For a surface that computes its hints over the list it shows: feeds the ring after the final detect, then audits the hints. */
44
+ export declare function recordShownRecall(who: RecallAuditor, shown: ShownRecall): void;
45
+ export {};
46
+ //# sourceMappingURL=recall-record.d.ts.map
@@ -0,0 +1,98 @@
1
+ // The recall session rings and the recall audit rows, in one place for the CLI, MCP and HTTP surfaces.
2
+ import { appendAuditEvent, auditQueryFields, reportAuditWriteFailure } from '../audit.js';
3
+ import { closeHippoDb, openHippoDb } from '../db.js';
4
+ import { appendRecall, biasHintEnabled, buildSessionKey, getOrCreateRing, hashQueryText, snapshotRing, } from '../recall-history.js';
5
+ // One map per surface, each with its own LRU cap, so a repeat on one surface is a first recall on another.
6
+ const rings = {
7
+ cli: new Map(),
8
+ mcp: new Map(),
9
+ http: new Map(),
10
+ };
11
+ /** The session's ring, created on first use; null when anchoring is off or the call has no session. */
12
+ export function sessionRing(surface, tenantId, sessionId) {
13
+ if (!biasHintEnabled('anchoring') || !sessionId)
14
+ return null;
15
+ return getOrCreateRing(rings[surface], buildSessionKey(tenantId, sessionId));
16
+ }
17
+ /** The session's history, read without creating or touching its ring, so a request that then fails evicts no live session. */
18
+ export function peekSessionRing(surface, tenantId, sessionId) {
19
+ const ring = rings[surface].get(buildSessionKey(tenantId, sessionId));
20
+ return ring ? snapshotRing(ring) : [];
21
+ }
22
+ /** Records this recall's top row and hint, which the next recall's cooldown reads. */
23
+ export function noteRecall(ring, query, topId, anchoredOn) {
24
+ appendRecall(ring, hashQueryText(query), topId, anchoredOn);
25
+ }
26
+ export function resetSessionRings(surface) {
27
+ rings[surface].clear();
28
+ }
29
+ export function recallAuditRow(who, op, targetId, metadata) {
30
+ return { tenantId: who.tenantId, actor: who.actor, op, targetId, metadata };
31
+ }
32
+ function writeRecallAudit(who, row) {
33
+ try {
34
+ const db = openHippoDb(who.hippoRoot);
35
+ try {
36
+ appendAuditEvent(db, row);
37
+ }
38
+ finally {
39
+ closeHippoDb(db);
40
+ }
41
+ }
42
+ catch (err) {
43
+ if (!who.bestEffort)
44
+ throw err;
45
+ reportAuditWriteFailure(row.op, String(err), row.targetId);
46
+ }
47
+ }
48
+ /** One audit row on its own short-lived handle. */
49
+ export function appendRecallAudit(who, op, targetId, metadata) {
50
+ writeRecallAudit(who, recallAuditRow(who, op, targetId, metadata));
51
+ }
52
+ // The row stores a hash of the query, never its text, so an archived memory's words cannot persist there.
53
+ export function recallAuditMetadata(query, results) {
54
+ return { ...auditQueryFields(query), results };
55
+ }
56
+ /** No session means no ring; the row hashes with SHA-256/16, since hashQueryText is FNV-1a and easy to reverse on short queries. */
57
+ export function anchorSkippedRows(who, query) {
58
+ if (!biasHintEnabled('anchoring'))
59
+ return [];
60
+ return [recallAuditRow(who, 'recall_anchor_skipped_no_session', undefined, auditQueryFields(query))];
61
+ }
62
+ export function anchoringRows(who, hint) {
63
+ if (hint?.reason === 'memory_dominance') {
64
+ return [recallAuditRow(who, 'recall_anchor_detected_memory_dominance', hint.memoryId, {
65
+ memory_id: hint.memoryId,
66
+ query_count: hint.queryCount ?? null,
67
+ })];
68
+ }
69
+ if (hint?.reason === 'query_repeat') {
70
+ return [recallAuditRow(who, 'recall_anchor_detected_query_repeat', hint.memoryId, { memory_id: hint.memoryId })];
71
+ }
72
+ return [];
73
+ }
74
+ export function availabilityRows(who, hint) {
75
+ if (!hint)
76
+ return [];
77
+ return [recallAuditRow(who, 'recall_availability_detected', undefined, {
78
+ recent_fraction: hint.recentFraction,
79
+ older_passed_over: hint.olderCandidatesPassedOver,
80
+ returned_count: hint.returnedCount,
81
+ })];
82
+ }
83
+ /** The hint rows for a list a surface showed, so a surface on the store port can hand them to the recall's one write. */
84
+ export function shownRecallRows(who, shown) {
85
+ return [
86
+ ...(shown.ring ? [] : anchorSkippedRows(who, shown.query)),
87
+ ...anchoringRows(who, shown.anchoring),
88
+ ...availabilityRows(who, shown.availability),
89
+ ];
90
+ }
91
+ /** For a surface that computes its hints over the list it shows: feeds the ring after the final detect, then audits the hints. */
92
+ export function recordShownRecall(who, shown) {
93
+ if (shown.ring)
94
+ noteRecall(shown.ring, shown.query, shown.topId, shown.anchoring?.memoryId);
95
+ for (const row of shownRecallRows(who, shown))
96
+ writeRecallAudit(who, row);
97
+ }
98
+ //# sourceMappingURL=recall-record.js.map
@@ -0,0 +1,32 @@
1
+ import { BadRequestError } from '../api-errors.js';
2
+ import { type JsonValue } from '../json.js';
3
+ import type { ContextOpts } from './context-types.js';
4
+ import type { RecallOpts } from './recall-types.js';
5
+ /** A recall or context input the caller must fix; MCP answers it with an isError result, HTTP with a 400. */
6
+ export declare class RecallRequestError extends BadRequestError {
7
+ constructor(message: string);
8
+ }
9
+ type ParamValue = JsonValue | undefined;
10
+ /** A request's named inputs; `queryName` is the surface's name for the query text. */
11
+ export interface RequestParams {
12
+ readonly get: (name: string) => ParamValue;
13
+ readonly queryName: 'q' | 'query';
14
+ }
15
+ export declare function httpParams(query: URLSearchParams): RequestParams;
16
+ export declare function toolParams(args: Readonly<Record<string, JsonValue>>): RequestParams;
17
+ /** The recall options every surface passes through to retrieve(). */
18
+ export type SharedRecallOpts = Pick<RecallOpts, 'query' | 'scope' | 'freshTailCount' | 'freshTailSessionId' | 'summarizeOverflow' | 'scorerWindow' | 'sessionId'> & {
19
+ includeContinuity: boolean;
20
+ };
21
+ export interface ParsedRecallRequest {
22
+ readonly opts: SharedRecallOpts;
23
+ readonly limit: number | undefined;
24
+ readonly mode: RecallOpts['mode'];
25
+ readonly explain: boolean;
26
+ }
27
+ export type ParsedContextRequest = Pick<ContextOpts, 'q' | 'budget' | 'limit' | 'pinnedOnly' | 'scope' | 'includeRecent' | 'crossProject'>;
28
+ /** Checks in a fixed order, so the first bad input is the one reported. */
29
+ export declare function parseRecallRequest(params: RequestParams): ParsedRecallRequest;
30
+ export declare function parseContextRequest(params: RequestParams): ParsedContextRequest;
31
+ export {};
32
+ //# sourceMappingURL=recall-request.d.ts.map
@@ -0,0 +1,121 @@
1
+ // The recall and context input rules in one copy, so MCP and HTTP reject the same inputs with the same messages.
2
+ import { BadRequestError } from '../api-errors.js';
3
+ import { MAX_ID_LEN } from '../http-util.js';
4
+ import { isJsonString } from '../json.js';
5
+ const MAX_RECALL_LIMIT = 1000;
6
+ // recall() owns the shape rule for scorer_window (invalid_scorer_window); this cap bounds remote cost.
7
+ const MAX_SCORER_WINDOW = 1000;
8
+ // Bounds BM25 tokenisation cost while covering pasted error messages.
9
+ const MAX_CONTEXT_QUERY_LEN = 1024;
10
+ const RECALL_MODES = ['bm25', 'hybrid', 'physics'];
11
+ /** A recall or context input the caller must fix; MCP answers it with an isError result, HTTP with a 400. */
12
+ export class RecallRequestError extends BadRequestError {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'RecallRequestError';
16
+ }
17
+ }
18
+ export function httpParams(query) {
19
+ return { get: (name) => query.get(name) ?? undefined, queryName: 'q' };
20
+ }
21
+ export function toolParams(args) {
22
+ return { get: (name) => args[name], queryName: 'query' };
23
+ }
24
+ function numberParam(params, name) {
25
+ const value = params.get(name);
26
+ return value === undefined || value === null ? undefined : Number(value);
27
+ }
28
+ function nonNegativeParam(params, name) {
29
+ const n = numberParam(params, name);
30
+ if (n !== undefined && (!Number.isFinite(n) || n < 0))
31
+ throw new RecallRequestError(`${name} must be a non-negative number`);
32
+ return n;
33
+ }
34
+ // Only `true`, "1" and "true" turn a flag on, so `?summarize_overflow=banana` or an empty value stays off.
35
+ function flagParam(params, name) {
36
+ const value = params.get(name);
37
+ return value === true || value === '1' || value === 'true';
38
+ }
39
+ function optionalFlagParam(params, name) {
40
+ const value = params.get(name);
41
+ return value === undefined || value === null ? undefined : flagParam(params, name);
42
+ }
43
+ function stringParam(params, name) {
44
+ const value = params.get(name);
45
+ return isJsonString(value) && value.length > 0 ? value : undefined;
46
+ }
47
+ /** An id-shaped string capped at MAX_ID_LEN; empty reads as absent. */
48
+ function idParam(params, name) {
49
+ const value = params.get(name);
50
+ if (isJsonString(value) && value.length > MAX_ID_LEN)
51
+ throw new RecallRequestError(`${name} exceeds ${MAX_ID_LEN}-character cap`);
52
+ return stringParam(params, name);
53
+ }
54
+ function limitParam(params) {
55
+ const limit = numberParam(params, 'limit');
56
+ if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0 || limit > MAX_RECALL_LIMIT)) {
57
+ throw new RecallRequestError(`limit must be a positive integer <= ${MAX_RECALL_LIMIT}`);
58
+ }
59
+ return limit;
60
+ }
61
+ function modeParam(params) {
62
+ const value = params.get('mode');
63
+ if (value === undefined || value === null)
64
+ return undefined;
65
+ const mode = RECALL_MODES.find((m) => m === value);
66
+ if (!mode)
67
+ throw new RecallRequestError("mode must be 'bm25', 'hybrid', or 'physics'");
68
+ return mode;
69
+ }
70
+ function scorerWindowParam(params) {
71
+ const scorerWindow = numberParam(params, 'scorer_window');
72
+ if (scorerWindow !== undefined && scorerWindow > MAX_SCORER_WINDOW) {
73
+ throw new RecallRequestError(`scorer_window must be <= ${MAX_SCORER_WINDOW}`);
74
+ }
75
+ return scorerWindow;
76
+ }
77
+ /** Trimmed, and absent when blank, so a whitespace id never keys a goal stack or a session ring. */
78
+ function sessionIdParam(params) {
79
+ const value = idParam(params, 'session_id');
80
+ const trimmed = value?.trim() ?? '';
81
+ return trimmed.length > 0 ? trimmed : undefined;
82
+ }
83
+ /** Checks in a fixed order, so the first bad input is the one reported. */
84
+ export function parseRecallRequest(params) {
85
+ const query = params.get(params.queryName);
86
+ if (!isJsonString(query) || query.length === 0)
87
+ throw new RecallRequestError(`${params.queryName} is required`);
88
+ const limit = limitParam(params);
89
+ const mode = modeParam(params);
90
+ const scope = stringParam(params, 'scope');
91
+ const includeContinuity = flagParam(params, 'include_continuity');
92
+ const freshTailCount = nonNegativeParam(params, 'fresh_tail_count');
93
+ const freshTailSessionId = idParam(params, 'fresh_tail_session_id');
94
+ const summarizeOverflow = optionalFlagParam(params, 'summarize_overflow');
95
+ const scorerWindow = scorerWindowParam(params);
96
+ const sessionId = sessionIdParam(params);
97
+ const explain = flagParam(params, 'explain');
98
+ return {
99
+ opts: { query, scope, includeContinuity, freshTailCount, freshTailSessionId, summarizeOverflow, scorerWindow, sessionId },
100
+ limit,
101
+ mode,
102
+ explain,
103
+ };
104
+ }
105
+ export function parseContextRequest(params) {
106
+ const qValue = params.get(params.queryName);
107
+ const q = isJsonString(qValue) ? qValue : undefined;
108
+ if (q !== undefined && q.length > MAX_CONTEXT_QUERY_LEN) {
109
+ throw new RecallRequestError(`${params.queryName} exceeds ${MAX_CONTEXT_QUERY_LEN}-character cap`);
110
+ }
111
+ const budget = nonNegativeParam(params, 'budget');
112
+ const limit = numberParam(params, 'limit');
113
+ if (limit !== undefined && (!Number.isFinite(limit) || limit <= 0))
114
+ throw new RecallRequestError('limit must be a positive number');
115
+ const pinnedOnly = flagParam(params, 'pinned_only');
116
+ const scope = idParam(params, 'scope');
117
+ const includeRecent = nonNegativeParam(params, 'include_recent');
118
+ const crossProject = flagParam(params, 'cross_project');
119
+ return { q, budget, limit, pinnedOnly, scope, includeRecent, crossProject };
120
+ }
121
+ //# sourceMappingURL=recall-request.js.map
@@ -5,6 +5,7 @@ import type { RerankStep, SearchResult } from '../search/types.js';
5
5
  import type { PlanningFallacyHint, PlanningFallacyWatching } from '../predictions/planning-fallacy.js';
6
6
  import type { AnchoringHint, RecallHistorySnapshot } from '../recall-history.js';
7
7
  import type { AvailabilityHint } from '../availability.js';
8
+ import type { AppendAuditOpts } from '../audit.js';
8
9
  import type { CallerProject } from '../prompt-hook.js';
9
10
  export interface RecallOpts {
10
11
  query: string;
@@ -152,7 +153,12 @@ export interface RecallOpts {
152
153
  * then keeps a memory that a merged row in the same result holds word for word. Other callers leave it unset. */
153
154
  keepHeldCopies?: boolean;
154
155
  /** MCP recall only: `retrieve` ranks the whole scoped store and strengthens and traces (pipeline 'mcp') just the ids this returns; `results` stays the window band. */
155
- showRanked?: (ranking: StoreRanking, result: RecallResult) => readonly string[];
156
+ showRanked?: (ranking: StoreRanking, result: RecallResult) => ShownRanking;
157
+ }
158
+ /** `ids`: what the caller showed; `audit`: its own rows, written after the recall's and in the same transaction, so all land or none. */
159
+ export interface ShownRanking {
160
+ ids: readonly string[];
161
+ audit: readonly AppendAuditOpts[];
156
162
  }
157
163
  /** `ranked`: every scored row, best first, goal boost applied, entries as loaded; `pool`: the store after the scope filter. */
158
164
  export interface StoreRanking {
@@ -16,7 +16,8 @@ export declare function buildSuppressionSummary(counts: {
16
16
  suppressedByInterference: number;
17
17
  }): RecallSuppressionSummary;
18
18
  /**
19
- * Domain-level recall. Loads BM25-ranked candidates from SQLite scoped to
19
+ * Domain-level recall on hippo.db only, never `ctx.store`, since it is synchronous: under another store its first open throws
20
+ * `SqliteBlockedError`. Loads BM25-ranked candidates from SQLite scoped to
20
21
  * `ctx.tenantId` and keeps that order whatever `mode` says; `retrieve` is the
21
22
  * mode-aware, strengthening variant the HTTP route uses.
22
23
  *
@@ -31,6 +32,6 @@ export declare function buildSuppressionSummary(counts: {
31
32
  * `tests/api-recall-no-side-effects.test.ts`.
32
33
  */
33
34
  export declare function recall(ctx: Context, opts: RecallOpts): RecallResult;
34
- /** Mode-aware recall that strengthens each returned row; never writes last_retrieval_ids (contract lock). */
35
+ /** Mode-aware recall through the served store that strengthens each returned row; never writes last_retrieval_ids (contract lock). */
35
36
  export declare function retrieve(ctx: Context, opts: RecallOpts): Promise<RecallResult>;
36
37
  //# sourceMappingURL=recall.d.ts.map