hippo-memory 1.61.0 → 1.63.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 +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +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 +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// getContext's selection stages: the pinned-only branch, the strongest-first branch and the search branch.
|
|
2
|
+
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
3
|
+
import { recallScopeFilter } from '../store/search-rows.js';
|
|
4
|
+
import { loadIndex } from '../store/index-and-stats.js';
|
|
5
|
+
import { calculateStrength } from '../memory.js';
|
|
6
|
+
import { appendAuditEvent, auditQueryFields, isContentWorthStoring } from '../audit.js';
|
|
7
|
+
import { rankBothStores } from '../shared.js';
|
|
8
|
+
import { evalNow } from '../ablation.js';
|
|
9
|
+
import { hybridSearch } from '../search/hybrid.js';
|
|
10
|
+
import { physicsSearch } from '../search/physics-search.js';
|
|
11
|
+
import { DEFAULT_LOCAL_BUMP } from '../search/types.js';
|
|
12
|
+
import { compareScoredResults } from '../compare.js';
|
|
13
|
+
import { scopeMatch } from '../scope.js';
|
|
14
|
+
import { promptTokens, contentTokens, gatePromptRecall, } from '../prompt-recall.js';
|
|
15
|
+
const GLOBAL_DISCOUNT = 1 / DEFAULT_LOCAL_BUMP;
|
|
16
|
+
// Share and promote copy a memory to the global store under a new id, so equal content is the only link.
|
|
17
|
+
// A pinned copy wins, then the stronger one after the ranking's own global discount; a tie keeps the local copy.
|
|
18
|
+
export function oneCopyPerMemory(local, global, now) {
|
|
19
|
+
const score = (e, isGlobal) => calculateStrength(e, now) * (isGlobal ? GLOBAL_DISCOUNT : 1);
|
|
20
|
+
const best = new Map();
|
|
21
|
+
const offer = (entry, isGlobal) => {
|
|
22
|
+
const held = best.get(entry.content);
|
|
23
|
+
const wins = !held || (held.entry.pinned !== entry.pinned
|
|
24
|
+
? entry.pinned
|
|
25
|
+
: score(entry, isGlobal) > score(held.entry, held.isGlobal));
|
|
26
|
+
if (wins)
|
|
27
|
+
best.set(entry.content, { entry, isGlobal });
|
|
28
|
+
};
|
|
29
|
+
for (const e of local)
|
|
30
|
+
offer(e, false);
|
|
31
|
+
for (const e of global)
|
|
32
|
+
offer(e, true);
|
|
33
|
+
const kept = new Set([...best.values()].map((b) => b.entry));
|
|
34
|
+
return [local.filter((e) => kept.has(e)), global.filter((e) => kept.has(e))];
|
|
35
|
+
}
|
|
36
|
+
export const finiteOr = (v, dflt, min) => Number.isFinite(v) && v >= min ? v : dflt;
|
|
37
|
+
/** Pins plus the prompt-recall or recent-N backfill; null means the block is empty. */
|
|
38
|
+
export function selectPinned(opts, plan, left, pools, admission) {
|
|
39
|
+
const { obs, primaryIsGlobal, config: pinnedCfg } = plan;
|
|
40
|
+
if (!pinnedCfg.pinnedInject.enabled) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Effective budget: explicit opts.budget wins over config, less what the sections took.
|
|
44
|
+
const effBudget = left;
|
|
45
|
+
const nowP = evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
46
|
+
const localEntries = pools.local.entries;
|
|
47
|
+
const globalEntries = pools.global.entries;
|
|
48
|
+
obs?.offer(localEntries, primaryIsGlobal);
|
|
49
|
+
obs?.offer(globalEntries, true);
|
|
50
|
+
const [localPool, globalPool] = oneCopyPerMemory(localEntries, globalEntries, nowP);
|
|
51
|
+
obs?.dropMissing([...localEntries, ...globalEntries], [...localPool, ...globalPool], 'load', 'duplicate');
|
|
52
|
+
const picked = { items: [], ids: new Set(), used: 0 };
|
|
53
|
+
// Pinned entries are explicit user intent, the recent-N list an automatic
|
|
54
|
+
// backfill. Both loops share ONE budget and the recent loop runs first, so
|
|
55
|
+
// pins are ranked here and reserve their share before it can spend.
|
|
56
|
+
const pinnedLocal = localPool.filter((e) => e.pinned);
|
|
57
|
+
const pinnedGlobal = globalPool.filter((e) => e.pinned);
|
|
58
|
+
const rankedPinned = rankPinned(plan, pinnedLocal, pinnedGlobal, nowP);
|
|
59
|
+
const recentBudget = Math.max(0, effBudget - reservePinned(rankedPinned, effBudget));
|
|
60
|
+
// Prompt recall gates the backfill on the prompt instead of recency.
|
|
61
|
+
if (plan.promptRecallPending) {
|
|
62
|
+
const candidates = () => promptRecallCandidates(plan, pools, admission.admit, rankedPinned, nowP);
|
|
63
|
+
backfillFromPrompt(opts, plan, pinnedCfg, candidates, picked, recentBudget);
|
|
64
|
+
}
|
|
65
|
+
else if (plan.includeRecent > 0) {
|
|
66
|
+
backfillRecent(plan, localPool, globalPool, picked, recentBudget, nowP);
|
|
67
|
+
}
|
|
68
|
+
if (pinnedLocal.length === 0 &&
|
|
69
|
+
pinnedGlobal.length === 0 &&
|
|
70
|
+
picked.items.length === 0 &&
|
|
71
|
+
!admission.digestHidden()) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
admitWithinBudget(rankedPinned, picked, effBudget, obs);
|
|
75
|
+
return picked.items;
|
|
76
|
+
}
|
|
77
|
+
function rankPinned(plan, pinnedLocal, pinnedGlobal, nowP) {
|
|
78
|
+
return [
|
|
79
|
+
...pinnedLocal.map((e) => ({ entry: e, isGlobal: plan.primaryIsGlobal })),
|
|
80
|
+
...pinnedGlobal.map((e) => ({ entry: e, isGlobal: true })),
|
|
81
|
+
]
|
|
82
|
+
.map(({ entry, isGlobal }) => {
|
|
83
|
+
const scopeSig = scopeMatch(entry.tags, plan.activeScope);
|
|
84
|
+
const sBst = scopeSig === 1 ? 1.5 : scopeSig === -1 ? 0.5 : 1.0;
|
|
85
|
+
return {
|
|
86
|
+
entry,
|
|
87
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? GLOBAL_DISCOUNT : 1) * sBst,
|
|
88
|
+
tokens: plan.price(entry, isGlobal),
|
|
89
|
+
isGlobal,
|
|
90
|
+
};
|
|
91
|
+
})
|
|
92
|
+
.sort(compareScoredResults);
|
|
93
|
+
}
|
|
94
|
+
// Mirrors the pin loop's continue-not-break so a big pin cannot block smaller ones from reserving, and dedupes by id
|
|
95
|
+
// because a synced pin sits in both stores. A pin also in the recent slice is counted twice: recents under-fill, safely.
|
|
96
|
+
function reservePinned(rankedPinned, effBudget) {
|
|
97
|
+
let pinnedReserve = 0;
|
|
98
|
+
const reservedIds = new Set();
|
|
99
|
+
for (const r of rankedPinned) {
|
|
100
|
+
if (reservedIds.has(r.entry.id))
|
|
101
|
+
continue;
|
|
102
|
+
if (pinnedReserve + r.tokens <= effBudget) {
|
|
103
|
+
pinnedReserve += r.tokens;
|
|
104
|
+
reservedIds.add(r.entry.id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return pinnedReserve;
|
|
108
|
+
}
|
|
109
|
+
/** Skips ids already picked and rows past the budget, so a large row never blocks smaller ones behind it. */
|
|
110
|
+
function admitWithinBudget(rows, picked, budget, obs) {
|
|
111
|
+
for (const r of rows) {
|
|
112
|
+
if (picked.ids.has(r.entry.id))
|
|
113
|
+
continue;
|
|
114
|
+
if (picked.used + r.tokens > budget) {
|
|
115
|
+
obs?.reject(r.entry, 'budget', 'budget', r.score, r.tokens);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
picked.items.push(r);
|
|
119
|
+
picked.ids.add(r.entry.id);
|
|
120
|
+
picked.used += r.tokens;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function backfillFromPrompt(opts, plan, pinnedCfg, candidates, picked, recentBudget) {
|
|
124
|
+
const rawMetric = pinnedCfg.pinnedInject.promptRecallMetric;
|
|
125
|
+
const metric = rawMetric === 'cosine' ? 'cosine' : 'jaccard';
|
|
126
|
+
const gate = {
|
|
127
|
+
metric,
|
|
128
|
+
threshold: finiteOr(pinnedCfg.pinnedInject.promptRecallThreshold, 0.04, 0),
|
|
129
|
+
minShared: finiteOr(pinnedCfg.pinnedInject.promptRecallMinShared, 2, 0),
|
|
130
|
+
maxItems: finiteOr(pinnedCfg.pinnedInject.promptRecallMaxItems, 5, 1),
|
|
131
|
+
};
|
|
132
|
+
const p = promptTokens(opts.prompt ?? '');
|
|
133
|
+
if (p.size === 0)
|
|
134
|
+
return;
|
|
135
|
+
const candidateItems = candidates();
|
|
136
|
+
const gated = gatePromptRecall(p, candidateItems, gate);
|
|
137
|
+
plan.obs?.gated(p, candidateItems, gate, gated);
|
|
138
|
+
for (const g of gated) {
|
|
139
|
+
if (picked.ids.has(g.item.id))
|
|
140
|
+
continue;
|
|
141
|
+
const tokens = plan.price(g.item.entry, g.item.isGlobal, true);
|
|
142
|
+
if (picked.used + tokens > recentBudget) {
|
|
143
|
+
plan.obs?.reject(g.item.entry, 'budget', 'budget', g.score, tokens);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
picked.items.push({ entry: g.item.entry, score: g.score, tokens, isGlobal: g.item.isGlobal, promptRecall: true });
|
|
147
|
+
picked.ids.add(g.item.id);
|
|
148
|
+
picked.used += tokens;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Candidates came off the ambient load's own connection (the recall request), not a fresh open.
|
|
152
|
+
function promptRecallCandidates(plan, pools, admit, rankedPinned, nowP) {
|
|
153
|
+
const { obs, primaryIsGlobal } = plan;
|
|
154
|
+
// A candidate carrying a pin's text would inject that memory a second time.
|
|
155
|
+
const pinnedText = new Set(rankedPinned.map((r) => r.entry.content));
|
|
156
|
+
const ineligibleReason = (e) => !admit(e) ? 'scope'
|
|
157
|
+
: e.pinned ? 'pinned'
|
|
158
|
+
: !isContentWorthStoring(e.content) ? 'quality'
|
|
159
|
+
: pinnedText.has(e.content) ? 'duplicate'
|
|
160
|
+
: null;
|
|
161
|
+
const eligible = (e) => {
|
|
162
|
+
const why = ineligibleReason(e);
|
|
163
|
+
if (why !== null && why !== 'pinned')
|
|
164
|
+
obs?.reject(e, 'eligible', why);
|
|
165
|
+
return why === null;
|
|
166
|
+
};
|
|
167
|
+
obs?.offer(pools.local.recall ?? [], primaryIsGlobal, 'prompt-recall');
|
|
168
|
+
obs?.offer(pools.global.recall ?? [], true, 'prompt-recall');
|
|
169
|
+
const localEligible = (pools.local.recall ?? []).filter(eligible);
|
|
170
|
+
const globalEligible = (pools.global.recall ?? []).filter(eligible);
|
|
171
|
+
const [localCandidates, globalCandidates] = oneCopyPerMemory(localEligible, globalEligible, nowP);
|
|
172
|
+
obs?.dropMissing([...localEligible, ...globalEligible], [...localCandidates, ...globalCandidates], 'eligible', 'duplicate');
|
|
173
|
+
const seenCandidateIds = new Set();
|
|
174
|
+
const candidateItems = [];
|
|
175
|
+
// Local wins the id collision (a global row synced into the local store).
|
|
176
|
+
for (const e of localCandidates) {
|
|
177
|
+
if (seenCandidateIds.has(e.id))
|
|
178
|
+
continue;
|
|
179
|
+
seenCandidateIds.add(e.id);
|
|
180
|
+
candidateItems.push({ id: e.id, tokens: contentTokens(e.content), entry: e, isGlobal: primaryIsGlobal });
|
|
181
|
+
}
|
|
182
|
+
for (const e of globalCandidates) {
|
|
183
|
+
if (seenCandidateIds.has(e.id))
|
|
184
|
+
continue;
|
|
185
|
+
seenCandidateIds.add(e.id);
|
|
186
|
+
candidateItems.push({ id: e.id, tokens: contentTokens(e.content), entry: e, isGlobal: true });
|
|
187
|
+
}
|
|
188
|
+
return candidateItems;
|
|
189
|
+
}
|
|
190
|
+
function backfillRecent(plan, localPool, globalPool, picked, recentBudget, nowP) {
|
|
191
|
+
const recent = [
|
|
192
|
+
...localPool.map((entry) => ({ entry, isGlobal: plan.primaryIsGlobal })),
|
|
193
|
+
...globalPool.map((entry) => ({ entry, isGlobal: true })),
|
|
194
|
+
]
|
|
195
|
+
// Newest first, then id: stable within one store, but same-millisecond rows fall to random ids across ingests.
|
|
196
|
+
.sort((a, b) => {
|
|
197
|
+
const byCreated = Date.parse(b.entry.created) - Date.parse(a.entry.created);
|
|
198
|
+
return byCreated !== 0 ? byCreated : b.entry.id.localeCompare(a.entry.id);
|
|
199
|
+
})
|
|
200
|
+
// Filter before slice so a junk row is backfilled past, not counted against N. Pins bypass the floor: a dropped
|
|
201
|
+
// pin's share of the shared budget would go to a backfilled row, and the pin loop could not win it back.
|
|
202
|
+
.filter(({ entry }) => entry.pinned || isContentWorthStoring(entry.content))
|
|
203
|
+
.slice(0, plan.includeRecent)
|
|
204
|
+
.map(({ entry, isGlobal }) => ({
|
|
205
|
+
entry,
|
|
206
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? GLOBAL_DISCOUNT : 1),
|
|
207
|
+
tokens: plan.price(entry, isGlobal),
|
|
208
|
+
isGlobal,
|
|
209
|
+
}));
|
|
210
|
+
admitWithinBudget(recent, picked, recentBudget, plan.obs);
|
|
211
|
+
}
|
|
212
|
+
/** No query: the strongest memories by strength, up to budget. */
|
|
213
|
+
export function selectStrongest(plan, left, pools) {
|
|
214
|
+
const now = evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
215
|
+
const [localPool, globalPool] = oneCopyPerMemory(pools.local.entries, pools.global.entries, now);
|
|
216
|
+
const localRanked = localPool
|
|
217
|
+
.map((e) => ({
|
|
218
|
+
entry: e,
|
|
219
|
+
score: calculateStrength(e, now),
|
|
220
|
+
tokens: plan.price(e, plan.primaryIsGlobal),
|
|
221
|
+
isGlobal: plan.primaryIsGlobal,
|
|
222
|
+
}))
|
|
223
|
+
.sort(compareScoredResults);
|
|
224
|
+
const globalRanked = globalPool
|
|
225
|
+
.map((e) => ({
|
|
226
|
+
entry: e,
|
|
227
|
+
score: calculateStrength(e, now) * GLOBAL_DISCOUNT,
|
|
228
|
+
tokens: plan.price(e, true),
|
|
229
|
+
isGlobal: true,
|
|
230
|
+
}))
|
|
231
|
+
.sort(compareScoredResults);
|
|
232
|
+
const combined = [...localRanked, ...globalRanked].sort(compareScoredResults);
|
|
233
|
+
const selected = [];
|
|
234
|
+
let used = 0;
|
|
235
|
+
for (const r of combined) {
|
|
236
|
+
if (used + r.tokens > left)
|
|
237
|
+
continue;
|
|
238
|
+
selected.push(r);
|
|
239
|
+
used += r.tokens;
|
|
240
|
+
}
|
|
241
|
+
return selected;
|
|
242
|
+
}
|
|
243
|
+
/** Real query: hybrid search over both stores, or physics/hybrid over the local rows; emits the 'recall' audit row. */
|
|
244
|
+
export async function selectBySearch(ctx, plan, left, pools, admission) {
|
|
245
|
+
const minResults = plan.cost ? 0 : undefined; // a priced block skips an oversize top hit too, so the budget bounds it
|
|
246
|
+
const results = plan.hasGlobal && !plan.primaryIsGlobal
|
|
247
|
+
? await searchBothStores(ctx, plan, left, minResults, pools, admission.bothStoresAdmit)
|
|
248
|
+
: await searchLocalRows(ctx, plan, left, minResults, pools.local.entries, admission.admit);
|
|
249
|
+
auditContextRecall(ctx, plan, results.length);
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
// The pools were admitted at load, before ranking, dedupe and budget: a post-filter would let an excluded row fill the
|
|
253
|
+
// budget or shadow its admitted duplicate.
|
|
254
|
+
async function searchBothStores(ctx, plan, left, minResults, pools, admit) {
|
|
255
|
+
const { cost, price } = plan;
|
|
256
|
+
const localIndex = loadIndex(ctx.hippoRoot);
|
|
257
|
+
const isGlobalHit = (e) => !localIndex.entries[e.id];
|
|
258
|
+
const roots = { local: ctx.hippoRoot, global: plan.globalRoot };
|
|
259
|
+
const merged = await rankBothStores(plan.query, roots, { local: pools.local.entries, global: pools.global.entries }, contextVectorSpec(ctx, plan, admit), {
|
|
260
|
+
budget: left,
|
|
261
|
+
minResults,
|
|
262
|
+
cost: cost && ((r) => price(r.entry, isGlobalHit(r.entry))),
|
|
263
|
+
scope: plan.activeScope,
|
|
264
|
+
});
|
|
265
|
+
return merged.map((r) => ({
|
|
266
|
+
entry: r.entry,
|
|
267
|
+
score: r.score,
|
|
268
|
+
tokens: price(r.entry, isGlobalHit(r.entry)),
|
|
269
|
+
isGlobal: isGlobalHit(r.entry),
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
/** The vector arm under the lexical window's own tenant, scope and current-row rules. */
|
|
273
|
+
function contextVectorSpec(ctx, plan, admit) {
|
|
274
|
+
return { tenantId: ctx.tenantId, scope: recallScopeFilter(plan.exactScope, 'exact'), includeSuperseded: false, admit };
|
|
275
|
+
}
|
|
276
|
+
async function searchLocalRows(ctx, plan, left, minResults, localEntries, admit) {
|
|
277
|
+
const { cost, price, primaryIsGlobal, query, config: ctxConfig } = plan;
|
|
278
|
+
const usePhysicsCtx = ctxConfig.physics?.enabled !== false;
|
|
279
|
+
const localCost = cost && ((r) => price(r.entry, primaryIsGlobal));
|
|
280
|
+
const vectorCandidates = contextVectorSpec(ctx, plan, admit);
|
|
281
|
+
const ctxResults = usePhysicsCtx
|
|
282
|
+
? await physicsSearch(query, localEntries, {
|
|
283
|
+
budget: left,
|
|
284
|
+
minResults,
|
|
285
|
+
cost: localCost,
|
|
286
|
+
hippoRoot: ctx.hippoRoot,
|
|
287
|
+
physicsConfig: ctxConfig.physics,
|
|
288
|
+
scope: plan.activeScope,
|
|
289
|
+
vectorCandidates,
|
|
290
|
+
})
|
|
291
|
+
: await hybridSearch(query, localEntries, {
|
|
292
|
+
budget: left,
|
|
293
|
+
minResults,
|
|
294
|
+
cost: localCost,
|
|
295
|
+
hippoRoot: ctx.hippoRoot,
|
|
296
|
+
scope: plan.activeScope,
|
|
297
|
+
vectorCandidates,
|
|
298
|
+
});
|
|
299
|
+
return ctxResults.map((r) => ({
|
|
300
|
+
entry: r.entry,
|
|
301
|
+
score: r.score,
|
|
302
|
+
tokens: price(r.entry, primaryIsGlobal),
|
|
303
|
+
isGlobal: primaryIsGlobal,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
// Same 'recall' op api.recall emits; the pinned-only and no-query branches never search, so they never emit.
|
|
307
|
+
function auditContextRecall(ctx, plan, resultCount) {
|
|
308
|
+
const ctxRecallMetadata = {
|
|
309
|
+
...auditQueryFields(plan.query),
|
|
310
|
+
results: resultCount,
|
|
311
|
+
mode: 'context',
|
|
312
|
+
};
|
|
313
|
+
if (plan.hasLocal) {
|
|
314
|
+
const localDb = openHippoDb(ctx.hippoRoot);
|
|
315
|
+
try {
|
|
316
|
+
appendAuditEvent(localDb, {
|
|
317
|
+
tenantId: ctx.tenantId,
|
|
318
|
+
actor: ctx.actor.subject,
|
|
319
|
+
op: 'recall',
|
|
320
|
+
metadata: ctxRecallMetadata,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
closeHippoDb(localDb);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (plan.hasGlobal && !plan.primaryIsGlobal) {
|
|
328
|
+
const globalDb = openHippoDb(plan.globalRoot);
|
|
329
|
+
try {
|
|
330
|
+
appendAuditEvent(globalDb, {
|
|
331
|
+
tenantId: ctx.tenantId,
|
|
332
|
+
actor: ctx.actor.subject,
|
|
333
|
+
op: 'recall',
|
|
334
|
+
metadata: ctxRecallMetadata,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
closeHippoDb(globalDb);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=context-select.js.map
|
|
@@ -3,22 +3,17 @@ import type { SessionHandoff } from '../handoff.js';
|
|
|
3
3
|
import type { MemoryEntry } from '../memory.js';
|
|
4
4
|
import type { DeliveryObserver } from '../delivery-recorder.js';
|
|
5
5
|
import type { AmbientState } from '../ambient.js';
|
|
6
|
+
import type { ProjectRef } from '../project-identity.js';
|
|
6
7
|
/**
|
|
7
8
|
* Options for `getContext` — assemble a budget-bounded context bundle
|
|
8
9
|
* (recalled memories + active task snapshot + handoff + recent events).
|
|
9
|
-
* Extracted from `cmdContext` in `cli.ts` in Episode A of the api.ts refactor.
|
|
10
10
|
*
|
|
11
11
|
* Named `getContext` (not `context`) to avoid collision with the `Context`
|
|
12
12
|
* interface above and the ubiquitous `ctx: Context` convention. Follows the
|
|
13
13
|
* existing `getEntry` naming pattern in store.ts.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* helpers (`printContextMarkdown`, `printActiveTaskSnapshot`, `printHandoff`,
|
|
18
|
-
* `printSessionEvents`) are shared with `cmdRecall` / `cmdSnapshot` /
|
|
19
|
-
* `cmdHandoffShow` — moving them into api.ts would expand T5 to also rewire
|
|
20
|
-
* those commands. CLI handles rendering + auto-resolution. Episode B can add
|
|
21
|
-
* `api.renderContext` once a shared rendering need actually materializes.
|
|
15
|
+
* Rendering opts (`format`, `framing`, `rendered`) and host-side opts (`auto`) stay in the CLI,
|
|
16
|
+
* because its print helpers are shared with `cmdRecall` / `cmdSnapshot` / `cmdHandoffShow`.
|
|
22
17
|
*/
|
|
23
18
|
export interface ContextOpts {
|
|
24
19
|
q?: string;
|
|
@@ -30,7 +25,7 @@ export interface ContextOpts {
|
|
|
30
25
|
/** Envelope scope to match exactly, as in `recall`: admits that scope even when private, after the actor's scope check. */
|
|
31
26
|
exactScope?: string;
|
|
32
27
|
/** With `pinnedOnly`, also inject the N most recent writes that pass the
|
|
33
|
-
* quality floor (`isContentWorthStoring
|
|
28
|
+
* quality floor (`isContentWorthStoring`). Filtering happens BEFORE
|
|
34
29
|
* the take-N, so a caller asking for 5 gets 5 qualifying entries rather
|
|
35
30
|
* than 5-minus-junk; pinned entries bypass the floor. Entries are only
|
|
36
31
|
* skipped for this read, never mutated or deleted. Ignored when
|
|
@@ -40,12 +35,12 @@ export interface ContextOpts {
|
|
|
40
35
|
* origin partition excludes by default. They come back tagged
|
|
41
36
|
* `category: 'cross-project'` so renderers can demarcate them. */
|
|
42
37
|
crossProject?: boolean;
|
|
43
|
-
/** The active project
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* should pass it
|
|
47
|
-
currentProject?:
|
|
48
|
-
/**
|
|
38
|
+
/** The active project for the origin partition ('' = not in a project): a
|
|
39
|
+
* name, or an identity whose rows may also carry its legacy folder name.
|
|
40
|
+
* Defaults to `resolveProjectIdentity(process.cwd())`; surfaces whose
|
|
41
|
+
* process cwd is not the caller's project (HTTP server) should pass it. */
|
|
42
|
+
currentProject?: ProjectRef;
|
|
43
|
+
/** The calling
|
|
49
44
|
* session's id. Stamped on this call's recall trace, and the owner-match input to
|
|
50
45
|
* `loadFreshActiveTaskSnapshot` — when it strictly equals the active
|
|
51
46
|
* snapshot's `session_id`, the read is unbounded (same-session
|
|
@@ -54,7 +49,7 @@ export interface ContextOpts {
|
|
|
54
49
|
* it just means every snapshot goes through the age check. Host-resolved
|
|
55
50
|
* (stdin payload, HIPPO_SESSION_ID, else the host's session var) so this stays host-agnostic. */
|
|
56
51
|
currentSessionId?: string | null;
|
|
57
|
-
/**
|
|
52
|
+
/** Raw hook-payload prompt; only the pinned-only branch reads it, gated on `pinnedInject.promptRecall`. */
|
|
58
53
|
prompt?: string;
|
|
59
54
|
/** What the budget pays for, from the caller that renders the block. Absent = the memory text alone. */
|
|
60
55
|
cost?: ContextCost;
|
|
@@ -83,7 +78,7 @@ export interface ContextResultEntry {
|
|
|
83
78
|
tokens: number;
|
|
84
79
|
isGlobal?: boolean;
|
|
85
80
|
isFreshTail?: boolean;
|
|
86
|
-
/**
|
|
81
|
+
/** Admitted by the prompt-recall gate, not the recent-N backfill or a pin. */
|
|
87
82
|
promptRecall?: boolean;
|
|
88
83
|
/** v39: the entry's owning project ('' = user-global, null = legacy row). */
|
|
89
84
|
origin?: string | null;
|
package/dist/api/context.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { type MemoryEntry } from '../memory.js';
|
|
2
|
+
import { type ProjectRef } from '../project-identity.js';
|
|
2
3
|
import type { ContextOpts, ContextResult } from './context-types.js';
|
|
3
4
|
import type { Context } from './types.js';
|
|
5
|
+
export { oneCopyPerMemory } from './context-select.js';
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* The secret half of the ambient policy on its own, for callers
|
|
6
8
|
* that apply their own scope rule. A flagged row is only admitted inside its owning project;
|
|
7
9
|
* flagged rows with no project origin never ambient-inject.
|
|
8
10
|
*/
|
|
9
|
-
export declare function ambientSecretAdmit(e: MemoryEntry,
|
|
11
|
+
export declare function ambientSecretAdmit(e: MemoryEntry, currentProject: ProjectRef): boolean;
|
|
10
12
|
/** Most rows per store a no-query context reads; past it, ranking and ambientState see the strongest by decay. */
|
|
11
13
|
export declare const CONTEXT_CANDIDATE_CAP = 2000;
|
|
12
|
-
export declare function oneCopyPerMemory(local: readonly MemoryEntry[], global: readonly MemoryEntry[], now: Date): [MemoryEntry[], MemoryEntry[]];
|
|
13
14
|
/**
|
|
14
15
|
* Assemble a context bundle: recalled memories (pinned-only / strength-sorted
|
|
15
16
|
* fallback / hybrid search) + active task snapshot + session handoff + recent
|
|
@@ -23,7 +24,7 @@ export declare function oneCopyPerMemory(local: readonly MemoryEntry[], global:
|
|
|
23
24
|
* Tenant scope: all `loadAllEntries` / snapshot / handoff / events reads use
|
|
24
25
|
* `ctx.tenantId`. Cross-tenant rows are filtered out.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
+
* @returns An empty result (`entries: []`, snapshot/handoff/events undefined)
|
|
27
28
|
* when there's nothing to surface (no memories AND no snapshot AND no handoff
|
|
28
29
|
* AND no recent events).
|
|
29
30
|
*/
|