hippo-memory 1.59.0 → 1.61.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 +1 -1
- package/dist/ablation.js +9 -27
- package/dist/agent-memories/apply.js +4 -1
- package/dist/agent-memories/claude-code.js +1 -1
- package/dist/agent-memories/codex.js +1 -1
- package/dist/agent-memories/gemini.js +1 -1
- package/dist/agent-memories/legacy.js +1 -1
- package/dist/agent-memories/source.js +1 -1
- package/dist/agent-memories/sync.js +8 -3
- package/dist/ambient-store.d.ts +14 -0
- package/dist/ambient-store.js +90 -0
- package/dist/ambient.d.ts +23 -0
- package/dist/ambient.js +72 -50
- package/dist/api/assemble.d.ts +93 -0
- package/dist/api/assemble.js +152 -0
- package/dist/api/audit.d.ts +17 -0
- package/dist/api/audit.js +23 -0
- package/dist/api/auth.d.ts +79 -0
- package/dist/api/auth.js +178 -0
- package/dist/api/context-types.d.ts +105 -0
- package/dist/api/context-types.js +3 -0
- package/dist/api/context.d.ts +31 -0
- package/dist/api/context.js +705 -0
- package/dist/api/dormant.d.ts +30 -0
- package/dist/api/dormant.js +140 -0
- package/dist/api/drill-down.d.ts +84 -0
- package/dist/api/drill-down.js +123 -0
- package/dist/api/forget.d.ts +57 -0
- package/dist/api/forget.js +87 -0
- package/dist/api/goals.d.ts +18 -0
- package/dist/api/goals.js +33 -0
- package/dist/api/learn.d.ts +31 -0
- package/dist/api/learn.js +88 -0
- package/dist/api/outcome.d.ts +61 -0
- package/dist/api/outcome.js +66 -0
- package/dist/api/promote.d.ts +54 -0
- package/dist/api/promote.js +203 -0
- package/dist/api/quarantine.d.ts +24 -0
- package/dist/api/quarantine.js +121 -0
- package/dist/api/recall-types.d.ts +390 -0
- package/dist/api/recall-types.js +3 -0
- package/dist/api/recall.d.ts +36 -0
- package/dist/api/recall.js +634 -0
- package/dist/api/remember.d.ts +35 -0
- package/dist/api/remember.js +43 -0
- package/dist/api/sleep.d.ts +136 -0
- package/dist/api/sleep.js +271 -0
- package/dist/api/tokens.d.ts +26 -0
- package/dist/api/tokens.js +60 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/api/types.js +38 -0
- package/dist/api.d.ts +20 -1260
- package/dist/api.js +25 -2711
- package/dist/audit.d.ts +3 -0
- package/dist/audit.js +7 -3
- package/dist/auth.d.ts +45 -4
- package/dist/auth.js +125 -48
- package/dist/autolearn.js +7 -4
- package/dist/capture/command.d.ts +33 -0
- package/dist/capture/command.js +264 -0
- package/dist/capture/compact.d.ts +44 -0
- package/dist/capture/compact.js +354 -0
- package/dist/capture/extract.d.ts +21 -0
- package/dist/capture/extract.js +464 -0
- package/dist/capture/transcript.d.ts +40 -0
- package/dist/capture/transcript.js +193 -0
- package/dist/capture-error.js +2 -1
- package/dist/churn-git.js +4 -2
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.js +159 -0
- package/dist/cli/auth.d.ts +2 -0
- package/dist/cli/auth.js +171 -0
- package/dist/cli/briefs.d.ts +4 -0
- package/dist/cli/briefs.js +435 -0
- package/dist/cli/card.d.ts +3 -0
- package/dist/cli/card.js +333 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.js +366 -0
- package/dist/cli/continuity.d.ts +6 -0
- package/dist/cli/continuity.js +445 -0
- package/dist/cli/curate.d.ts +19 -0
- package/dist/cli/curate.js +555 -0
- package/dist/cli/dag.d.ts +5 -0
- package/dist/cli/dag.js +177 -0
- package/dist/cli/decisions.d.ts +4 -0
- package/dist/cli/decisions.js +528 -0
- package/dist/cli/eval.d.ts +5 -0
- package/dist/cli/eval.js +213 -0
- package/dist/cli/explain.d.ts +4 -0
- package/dist/cli/explain.js +150 -0
- package/dist/cli/goals.d.ts +2 -0
- package/dist/cli/goals.js +196 -0
- package/dist/cli/hook-blocks.d.ts +18 -0
- package/dist/cli/hook-blocks.js +233 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +305 -0
- package/dist/cli/maintenance.d.ts +4 -0
- package/dist/cli/maintenance.js +179 -0
- package/dist/cli/playbooks.d.ts +4 -0
- package/dist/cli/playbooks.js +556 -0
- package/dist/cli/projects.js +1 -1
- package/dist/cli/recall.d.ts +7 -0
- package/dist/cli/recall.js +597 -0
- package/dist/cli/remember.d.ts +5 -0
- package/dist/cli/remember.js +442 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +40 -0
- package/dist/cli/session-hooks.d.ts +29 -0
- package/dist/cli/session-hooks.js +637 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +376 -0
- package/dist/cli/shared.d.ts +15 -19
- package/dist/cli/shared.js +50 -341
- package/dist/cli/slack.d.ts +2 -0
- package/dist/cli/slack.js +171 -0
- package/dist/cli/status.d.ts +18 -0
- package/dist/cli/status.js +400 -0
- package/dist/cli/transfer.d.ts +10 -0
- package/dist/cli/transfer.js +438 -0
- package/dist/cli/usage.d.ts +85 -0
- package/dist/cli/usage.js +741 -0
- package/dist/cli.d.ts +319 -13
- package/dist/cli.js +436 -8580
- package/dist/client.js +15 -8
- package/dist/compaction-record.js +12 -9
- package/dist/config.js +12 -11
- package/dist/connectors/github/backfill.js +94 -87
- package/dist/connectors/github/cli-impl.js +5 -3
- package/dist/connectors/github/dlq.js +67 -54
- package/dist/connectors/github/ingest.js +34 -35
- package/dist/connectors/github/tenant-routing.js +3 -2
- package/dist/connectors/github/webhook.js +135 -216
- package/dist/connectors/slack/dlq.js +49 -61
- package/dist/connectors/slack/ingest.js +55 -48
- package/dist/connectors/slack/tenant-routing.js +4 -3
- package/dist/connectors/slack/webhook.js +72 -74
- package/dist/consolidate/conflicts.d.ts +10 -0
- package/dist/consolidate/conflicts.js +178 -0
- package/dist/consolidate/decay.d.ts +12 -0
- package/dist/consolidate/decay.js +145 -0
- package/dist/consolidate/llm-passes.d.ts +3 -0
- package/dist/consolidate/llm-passes.js +141 -0
- package/dist/consolidate/merge.d.ts +7 -0
- package/dist/consolidate/merge.js +251 -0
- package/dist/consolidate/physics-pass.d.ts +3 -0
- package/dist/consolidate/physics-pass.js +60 -0
- package/dist/consolidate/run.d.ts +69 -0
- package/dist/consolidate/run.js +76 -0
- package/dist/consolidate/sleep.d.ts +18 -0
- package/dist/consolidate/sleep.js +209 -0
- package/dist/consolidate/traces.d.ts +4 -0
- package/dist/consolidate/traces.js +178 -0
- package/dist/context-auto.js +7 -11
- package/dist/context-render.d.ts +1 -1
- package/dist/context-render.js +1 -1
- package/dist/customer-notes.d.ts +3 -0
- package/dist/customer-notes.js +6 -4
- package/dist/dag.js +13 -12
- package/dist/dashboard-actions.d.ts +20 -0
- package/dist/dashboard-actions.js +88 -0
- package/dist/dashboard-params.d.ts +45 -0
- package/dist/dashboard-params.js +127 -0
- package/dist/dashboard-queries.d.ts +15 -0
- package/dist/dashboard-queries.js +355 -0
- package/dist/dashboard-snapshot.d.ts +138 -0
- package/dist/dashboard-snapshot.js +308 -0
- package/dist/dashboard-types.d.ts +163 -0
- package/dist/dashboard-types.js +3 -0
- package/dist/dashboard.d.ts +6 -7
- package/dist/dashboard.js +231 -203
- package/dist/db/busy.d.ts +5 -0
- package/dist/db/busy.js +24 -0
- package/dist/db/continuity.d.ts +5 -0
- package/dist/db/continuity.js +145 -0
- package/dist/db/meta.d.ts +8 -0
- package/dist/db/meta.js +35 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +138 -0
- package/dist/db/migrations/index.d.ts +5 -0
- package/dist/db/migrations/index.js +109 -0
- package/dist/db/migrations/types.d.ts +14 -0
- package/dist/db/migrations/types.js +2 -0
- package/dist/db/migrations/v01.d.ts +3 -0
- package/dist/db/migrations/v01.js +38 -0
- package/dist/db/migrations/v02.d.ts +3 -0
- package/dist/db/migrations/v02.js +21 -0
- package/dist/db/migrations/v03.d.ts +3 -0
- package/dist/db/migrations/v03.js +22 -0
- package/dist/db/migrations/v04.d.ts +3 -0
- package/dist/db/migrations/v04.js +28 -0
- package/dist/db/migrations/v05.d.ts +3 -0
- package/dist/db/migrations/v05.js +21 -0
- package/dist/db/migrations/v06.d.ts +3 -0
- package/dist/db/migrations/v06.js +25 -0
- package/dist/db/migrations/v07.d.ts +3 -0
- package/dist/db/migrations/v07.js +13 -0
- package/dist/db/migrations/v08.d.ts +3 -0
- package/dist/db/migrations/v08.js +8 -0
- package/dist/db/migrations/v09.d.ts +3 -0
- package/dist/db/migrations/v09.js +13 -0
- package/dist/db/migrations/v10.d.ts +3 -0
- package/dist/db/migrations/v10.js +17 -0
- package/dist/db/migrations/v11.d.ts +3 -0
- package/dist/db/migrations/v11.js +15 -0
- package/dist/db/migrations/v12.d.ts +3 -0
- package/dist/db/migrations/v12.js +11 -0
- package/dist/db/migrations/v13.d.ts +3 -0
- package/dist/db/migrations/v13.js +15 -0
- package/dist/db/migrations/v14.d.ts +3 -0
- package/dist/db/migrations/v14.js +66 -0
- package/dist/db/migrations/v15.d.ts +3 -0
- package/dist/db/migrations/v15.js +42 -0
- package/dist/db/migrations/v16.d.ts +3 -0
- package/dist/db/migrations/v16.js +61 -0
- package/dist/db/migrations/v17.d.ts +3 -0
- package/dist/db/migrations/v17.js +46 -0
- package/dist/db/migrations/v18.d.ts +3 -0
- package/dist/db/migrations/v18.js +60 -0
- package/dist/db/migrations/v19.d.ts +3 -0
- package/dist/db/migrations/v19.js +28 -0
- package/dist/db/migrations/v20.d.ts +3 -0
- package/dist/db/migrations/v20.js +42 -0
- package/dist/db/migrations/v21.d.ts +3 -0
- package/dist/db/migrations/v21.js +16 -0
- package/dist/db/migrations/v22.d.ts +3 -0
- package/dist/db/migrations/v22.js +82 -0
- package/dist/db/migrations/v23.d.ts +3 -0
- package/dist/db/migrations/v23.js +48 -0
- package/dist/db/migrations/v24.d.ts +3 -0
- package/dist/db/migrations/v24.js +72 -0
- package/dist/db/migrations/v25.d.ts +3 -0
- package/dist/db/migrations/v25.js +46 -0
- package/dist/db/migrations/v26.d.ts +3 -0
- package/dist/db/migrations/v26.js +23 -0
- package/dist/db/migrations/v27.d.ts +3 -0
- package/dist/db/migrations/v27.js +57 -0
- package/dist/db/migrations/v28.d.ts +3 -0
- package/dist/db/migrations/v28.js +38 -0
- package/dist/db/migrations/v29.d.ts +3 -0
- package/dist/db/migrations/v29.js +78 -0
- package/dist/db/migrations/v30.d.ts +3 -0
- package/dist/db/migrations/v30.js +92 -0
- package/dist/db/migrations/v31.d.ts +3 -0
- package/dist/db/migrations/v31.js +74 -0
- package/dist/db/migrations/v32.d.ts +3 -0
- package/dist/db/migrations/v32.js +102 -0
- package/dist/db/migrations/v33.d.ts +3 -0
- package/dist/db/migrations/v33.js +104 -0
- package/dist/db/migrations/v34.d.ts +3 -0
- package/dist/db/migrations/v34.js +93 -0
- package/dist/db/migrations/v35.d.ts +3 -0
- package/dist/db/migrations/v35.js +98 -0
- package/dist/db/migrations/v36.d.ts +3 -0
- package/dist/db/migrations/v36.js +98 -0
- package/dist/db/migrations/v37.d.ts +3 -0
- package/dist/db/migrations/v37.js +219 -0
- package/dist/db/migrations/v38.d.ts +3 -0
- package/dist/db/migrations/v38.js +277 -0
- package/dist/db/migrations/v39.d.ts +3 -0
- package/dist/db/migrations/v39.js +59 -0
- package/dist/db/migrations/v40.d.ts +3 -0
- package/dist/db/migrations/v40.js +74 -0
- package/dist/db/migrations/v41.d.ts +3 -0
- package/dist/db/migrations/v41.js +43 -0
- package/dist/db/migrations/v42.d.ts +3 -0
- package/dist/db/migrations/v42.js +41 -0
- package/dist/db/migrations/v43.d.ts +3 -0
- package/dist/db/migrations/v43.js +67 -0
- package/dist/db/migrations/v44.d.ts +3 -0
- package/dist/db/migrations/v44.js +28 -0
- package/dist/db/migrations/v45.d.ts +3 -0
- package/dist/db/migrations/v45.js +30 -0
- package/dist/db/migrations/v46.d.ts +3 -0
- package/dist/db/migrations/v46.js +25 -0
- package/dist/db/migrations/v47.d.ts +3 -0
- package/dist/db/migrations/v47.js +17 -0
- package/dist/db/migrations/v48.d.ts +3 -0
- package/dist/db/migrations/v48.js +10 -0
- package/dist/db/migrations/v49.d.ts +3 -0
- package/dist/db/migrations/v49.js +31 -0
- package/dist/db/migrations/v50.d.ts +3 -0
- package/dist/db/migrations/v50.js +67 -0
- package/dist/db/migrations/v51.d.ts +3 -0
- package/dist/db/migrations/v51.js +14 -0
- package/dist/db/migrations/v52.d.ts +3 -0
- package/dist/db/migrations/v52.js +7 -0
- package/dist/db/open.d.ts +23 -0
- package/dist/db/open.js +146 -0
- package/dist/db/sqlite.d.ts +21 -0
- package/dist/db/sqlite.js +8 -0
- package/dist/db/tables.d.ts +7 -0
- package/dist/db/tables.js +38 -0
- package/dist/db.d.ts +6 -46
- package/dist/db.js +5 -3036
- package/dist/decisions.d.ts +4 -1
- package/dist/decisions.js +9 -7
- package/dist/dedupe.js +3 -2
- package/dist/delivery-recorder.js +5 -1
- package/dist/doctor.js +4 -3
- package/dist/dormant.js +1 -0
- package/dist/embedding-provider.d.ts +1 -1
- package/dist/embedding-provider.js +7 -5
- package/dist/embeddings.d.ts +9 -52
- package/dist/embeddings.js +47 -298
- package/dist/env.d.ts +75 -0
- package/dist/env.js +119 -0
- package/dist/eval-suite.js +1 -1
- package/dist/eval.js +2 -2
- package/dist/extract.js +5 -4
- package/dist/gated-write.js +3 -1
- package/dist/goals.d.ts +3 -1
- package/dist/goals.js +18 -0
- package/dist/graph/read.d.ts +73 -0
- package/dist/graph/read.js +325 -0
- package/dist/graph/rows.d.ts +45 -0
- package/dist/graph/rows.js +51 -0
- package/dist/graph/types.d.ts +83 -0
- package/dist/graph/types.js +11 -0
- package/dist/graph/write.d.ts +93 -0
- package/dist/{graph.js → graph/write.js} +8 -391
- package/dist/graph-extract.js +2 -1
- package/dist/graph-recall.d.ts +1 -1
- package/dist/graph-recall.js +2 -2
- package/dist/graph-stream.js +1 -1
- package/dist/graph-view.d.ts +1 -1
- package/dist/graph-view.js +1 -1
- package/dist/half-life-migration.d.ts +1 -1
- package/dist/half-life-migration.js +2 -1
- package/dist/handoff.js +3 -0
- package/dist/hooks/codex-session.d.ts +8 -0
- package/dist/hooks/codex-session.js +76 -0
- package/dist/hooks/codex-wrapper.d.ts +55 -0
- package/dist/hooks/codex-wrapper.js +288 -0
- package/dist/hooks/json-hooks.d.ts +63 -0
- package/dist/hooks/json-hooks.js +356 -0
- package/dist/hooks/opencode.d.ts +50 -0
- package/dist/hooks/opencode.js +202 -0
- package/dist/hooks/shared.d.ts +54 -0
- package/dist/hooks/shared.js +77 -0
- package/dist/http-retry.d.ts +2 -0
- package/dist/http-retry.js +4 -3
- package/dist/http-util.d.ts +3 -0
- package/dist/http-util.js +10 -0
- package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
- package/dist/importers/core.js +141 -0
- package/dist/importers/markdown-parse.d.ts +41 -0
- package/dist/importers/markdown-parse.js +132 -0
- package/dist/importers/markdown.d.ts +3 -0
- package/dist/importers/markdown.js +92 -0
- package/dist/importers/sources.d.ts +7 -0
- package/dist/importers/sources.js +229 -0
- package/dist/importers/vault.d.ts +11 -0
- package/dist/importers/vault.js +352 -0
- package/dist/incidents.d.ts +3 -0
- package/dist/incidents.js +8 -5
- package/dist/index.d.ts +25 -6
- package/dist/index.js +23 -6
- package/dist/invalidation.js +2 -1
- package/dist/judgment.js +7 -3
- package/dist/keyset.d.ts +13 -0
- package/dist/keyset.js +8 -0
- package/dist/local-embedding.d.ts +13 -0
- package/dist/local-embedding.js +165 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +19 -1
- package/dist/mcp/admin-tools.d.ts +8 -0
- package/dist/mcp/admin-tools.js +116 -0
- package/dist/mcp/format.d.ts +27 -0
- package/dist/mcp/format.js +135 -0
- package/dist/mcp/memory-tools.d.ts +5 -0
- package/dist/mcp/memory-tools.js +83 -0
- package/dist/mcp/protocol.d.ts +83 -0
- package/dist/mcp/protocol.js +55 -0
- package/dist/mcp/recall-tools.d.ts +6 -0
- package/dist/mcp/recall-tools.js +320 -0
- package/dist/mcp/request.d.ts +10 -0
- package/dist/mcp/request.js +163 -0
- package/dist/mcp/server.d.ts +4 -70
- package/dist/mcp/server.js +8 -1172
- package/dist/mcp/session-state.d.ts +11 -0
- package/dist/mcp/session-state.js +28 -0
- package/dist/mcp/stdio.d.ts +8 -0
- package/dist/mcp/stdio.js +79 -0
- package/dist/mcp/tools.d.ts +11 -0
- package/dist/mcp/tools.js +247 -0
- package/dist/memory.d.ts +8 -3
- package/dist/memory.js +27 -1
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +2 -1
- package/dist/owner-validation.js +2 -1
- package/dist/physics-state.js +10 -7
- package/dist/policies.d.ts +3 -0
- package/dist/policies.js +8 -6
- package/dist/postinstall.js +3 -2
- package/dist/predictions/planning-fallacy.d.ts +100 -0
- package/dist/predictions/planning-fallacy.js +190 -0
- package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
- package/dist/predictions/store.js +434 -0
- package/dist/processes.d.ts +3 -0
- package/dist/processes.js +8 -5
- package/dist/project-briefs.d.ts +3 -0
- package/dist/project-briefs.js +7 -5
- package/dist/project-identity.d.ts +1 -1
- package/dist/project-identity.js +12 -6
- package/dist/project-merge.js +3 -1
- package/dist/quarantine.d.ts +2 -1
- package/dist/quarantine.js +8 -5
- package/dist/raw-archive-mirror-cleanup.js +2 -1
- package/dist/raw-archive.js +1 -1
- package/dist/recall-history.js +3 -2
- package/dist/recall-pipeline.d.ts +2 -2
- package/dist/recall-pipeline.js +16 -8
- package/dist/recall-scope.js +1 -1
- package/dist/recall-trace.d.ts +3 -3
- package/dist/recall-trace.js +10 -14
- package/dist/refine-llm.js +20 -11
- package/dist/reject-flow.js +6 -1
- package/dist/rerankers/clef.d.ts +2 -2
- package/dist/rerankers/clef.js +72 -30
- package/dist/rerankers/cross-encoder.js +6 -4
- package/dist/rerankers/jev.d.ts +4 -2
- package/dist/rerankers/jev.js +24 -19
- package/dist/rerankers/llm.d.ts +4 -2
- package/dist/rerankers/llm.js +61 -41
- package/dist/rerankers/types.d.ts +1 -1
- package/dist/salience.js +1 -1
- package/dist/same-text.d.ts +2 -0
- package/dist/same-text.js +4 -0
- package/dist/scheduler.d.ts +1 -0
- package/dist/scheduler.js +26 -3
- package/dist/scope.js +4 -3
- package/dist/search/as-of.d.ts +10 -0
- package/dist/search/as-of.js +22 -0
- package/dist/search/bm25-search.d.ts +14 -0
- package/dist/search/bm25-search.js +43 -0
- package/dist/search/bm25.d.ts +15 -0
- package/dist/search/bm25.js +54 -0
- package/dist/search/boosts.d.ts +54 -0
- package/dist/search/boosts.js +94 -0
- package/dist/search/breakdown.d.ts +7 -0
- package/dist/search/breakdown.js +20 -0
- package/dist/search/explain.d.ts +25 -0
- package/dist/search/explain.js +31 -0
- package/dist/search/finalize.d.ts +8 -0
- package/dist/search/finalize.js +52 -0
- package/dist/search/fusion.d.ts +27 -0
- package/dist/search/fusion.js +42 -0
- package/dist/search/hybrid-score.d.ts +20 -0
- package/dist/search/hybrid-score.js +73 -0
- package/dist/search/hybrid.d.ts +46 -0
- package/dist/search/hybrid.js +64 -0
- package/dist/search/physics-search.d.ts +29 -0
- package/dist/search/physics-search.js +162 -0
- package/dist/search/rerank.d.ts +10 -0
- package/dist/search/rerank.js +72 -0
- package/dist/search/temporal.d.ts +15 -0
- package/dist/search/temporal.js +45 -0
- package/dist/search/types.d.ts +91 -0
- package/dist/search/types.js +2 -0
- package/dist/search/vector.d.ts +30 -0
- package/dist/search/vector.js +71 -0
- package/dist/secret-detect.d.ts +2 -0
- package/dist/secret-detect.js +3 -1
- package/dist/server/auth.d.ts +52 -0
- package/dist/server/auth.js +221 -0
- package/dist/server/client-ip.d.ts +25 -0
- package/dist/server/client-ip.js +92 -0
- package/dist/server/cursor.d.ts +23 -0
- package/dist/server/cursor.js +58 -0
- package/dist/server/lifecycle.d.ts +7 -0
- package/dist/server/lifecycle.js +29 -0
- package/dist/server/mcp-http.d.ts +5 -0
- package/dist/server/mcp-http.js +199 -0
- package/dist/server/request.d.ts +33 -0
- package/dist/server/request.js +103 -0
- package/dist/server/routes/admin.d.ts +9 -0
- package/dist/server/routes/admin.js +158 -0
- package/dist/server/routes/customer-notes.d.ts +7 -0
- package/dist/server/routes/customer-notes.js +112 -0
- package/dist/server/routes/decisions.d.ts +7 -0
- package/dist/server/routes/decisions.js +133 -0
- package/dist/server/routes/incidents.d.ts +7 -0
- package/dist/server/routes/incidents.js +126 -0
- package/dist/server/routes/memories.d.ts +10 -0
- package/dist/server/routes/memories.js +179 -0
- package/dist/server/routes/policies.d.ts +8 -0
- package/dist/server/routes/policies.js +151 -0
- package/dist/server/routes/predictions.d.ts +7 -0
- package/dist/server/routes/predictions.js +164 -0
- package/dist/server/routes/processes.d.ts +7 -0
- package/dist/server/routes/processes.js +161 -0
- package/dist/server/routes/project-briefs.d.ts +8 -0
- package/dist/server/routes/project-briefs.js +136 -0
- package/dist/server/routes/recall.d.ts +8 -0
- package/dist/server/routes/recall.js +340 -0
- package/dist/server/routes/skills.d.ts +8 -0
- package/dist/server/routes/skills.js +150 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/types.js +2 -0
- package/dist/server/validation.d.ts +14 -0
- package/dist/server/validation.js +91 -0
- package/dist/server.d.ts +7 -61
- package/dist/server.js +92 -2368
- package/dist/session-digest.d.ts +1 -1
- package/dist/session-digest.js +9 -2
- package/dist/shared.d.ts +3 -1
- package/dist/shared.js +46 -40
- package/dist/skills.d.ts +3 -0
- package/dist/skills.js +7 -5
- package/dist/stdin.js +3 -2
- package/dist/store/audit-event.d.ts +19 -0
- package/dist/store/audit-event.js +33 -0
- package/dist/store/candidates.d.ts +42 -0
- package/dist/store/candidates.js +152 -0
- package/dist/store/conflicts.d.ts +44 -0
- package/dist/store/conflicts.js +444 -0
- package/dist/store/delete-and-batch.d.ts +63 -0
- package/dist/store/delete-and-batch.js +310 -0
- package/dist/store/entry-reads.d.ts +92 -0
- package/dist/store/entry-reads.js +255 -0
- package/dist/store/entry-row.d.ts +67 -0
- package/dist/store/entry-row.js +208 -0
- package/dist/store/entry-writes.d.ts +46 -0
- package/dist/store/entry-writes.js +148 -0
- package/dist/store/handoffs.d.ts +26 -0
- package/dist/store/handoffs.js +184 -0
- package/dist/store/index-and-stats.d.ts +52 -0
- package/dist/store/index-and-stats.js +214 -0
- package/dist/store/markdown.d.ts +10 -0
- package/dist/store/markdown.js +108 -0
- package/dist/store/mirrors.d.ts +49 -0
- package/dist/store/mirrors.js +312 -0
- package/dist/store/open.d.ts +15 -0
- package/dist/store/open.js +223 -0
- package/dist/store/rows.d.ts +178 -0
- package/dist/store/rows.js +158 -0
- package/dist/store/search-rows.d.ts +86 -0
- package/dist/store/search-rows.js +254 -0
- package/dist/store/sessions.d.ts +83 -0
- package/dist/store/sessions.js +272 -0
- package/dist/store/summaries.d.ts +94 -0
- package/dist/store/summaries.js +377 -0
- package/dist/store/tenant-lookup.d.ts +12 -0
- package/dist/store/tenant-lookup.js +16 -0
- package/dist/store-cards.js +2 -1
- package/dist/summary-dirty.d.ts +4 -0
- package/dist/summary-dirty.js +32 -0
- package/dist/support-bundle.js +4 -3
- package/dist/tenant.js +2 -2
- package/dist/token-ledger.js +1 -0
- package/dist/tokenize.d.ts +2 -0
- package/dist/tokenize.js +16 -0
- package/dist/transcript-tail.d.ts +7 -0
- package/dist/transcript-tail.js +48 -0
- package/dist/vector-store.d.ts +27 -0
- package/dist/vector-store.js +210 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/working-memory.js +1 -1
- package/dist/yaml.js +36 -11
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist-ui/assets/index-DPN7cP19.js +33 -0
- package/dist-ui/assets/index-dFloKRVr.css +1 -0
- package/dist-ui/index.html +3 -25
- 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/capture.d.ts +0 -155
- package/dist/capture.js +0 -1293
- package/dist/consolidate.d.ts +0 -58
- package/dist/consolidate.js +0 -1123
- package/dist/graph.d.ts +0 -245
- package/dist/hooks.d.ts +0 -208
- package/dist/hooks.js +0 -1073
- package/dist/importers.js +0 -897
- package/dist/predictions.js +0 -620
- package/dist/search.d.ts +0 -320
- package/dist/search.js +0 -968
- package/dist/store.d.ts +0 -744
- package/dist/store.js +0 -3372
- package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
- package/dist-ui/assets/index-BhT8RvO6.js +0 -61
- package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
- package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
package/dist/search.js
DELETED
|
@@ -1,968 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BM25 search + optional embedding hybrid search for Hippo.
|
|
3
|
-
* Zero external dependencies when embeddings are not available.
|
|
4
|
-
*/
|
|
5
|
-
import { estimateTokens } from './token-ledger.js';
|
|
6
|
-
import { calculateStrength, CHURN_STALE_TAG } from './memory.js';
|
|
7
|
-
import { tokenize } from './tokenize.js';
|
|
8
|
-
import { isOutcomeFastAblated, isRecallBoostAblated, isRecencyAblated, evalRecencyScaleDays, evalNow } from './ablation.js';
|
|
9
|
-
import { extractPathTags, pathBoostMultiplier } from './path-context.js';
|
|
10
|
-
import { detectScope, scopeMatch } from './scope.js';
|
|
11
|
-
import { cosineSimilarity, embeddingModelRequiresReindex, loadEmbeddingIndex, } from './embeddings.js';
|
|
12
|
-
import { resolveEmbeddingProvider } from './embedding-provider.js';
|
|
13
|
-
import { physicsScore as computePhysicsScores, computeMass } from './physics.js';
|
|
14
|
-
import { DEFAULT_PHYSICS_CONFIG } from './physics-config.js';
|
|
15
|
-
import { loadPhysicsState } from './physics-state.js';
|
|
16
|
-
import { openHippoDb, closeHippoDb } from './db.js';
|
|
17
|
-
import { rrfFuse } from './rrf.js';
|
|
18
|
-
import { graphRankStream, selectGraphSeeds, DEFAULT_GRAPH_SEED_COUNT } from './graph-stream.js';
|
|
19
|
-
import { compareEntryIdentity, compareScoredResults } from './compare.js';
|
|
20
|
-
import { log } from './log.js';
|
|
21
|
-
import { redactSecretsStrict } from './secret-detect.js';
|
|
22
|
-
export const CHURN_STALE_RANK_MULTIPLIER = 0.5; // SHORTCUT: untuned; FE3 measures before any default.
|
|
23
|
-
// Search runs on every hook prompt, so one line per reason per process says why vectors went unused without flooding stderr.
|
|
24
|
-
function warnBm25Fallback(reason, detail) {
|
|
25
|
-
log.once(`search.bm25-fallback.${reason}`, 'warn', `hybrid search fell back to BM25 only: ${redactSecretsStrict(detail)}`);
|
|
26
|
-
}
|
|
27
|
-
export function churnStaleFactor(entry) {
|
|
28
|
-
return entry.tags.includes(CHURN_STALE_TAG) ? CHURN_STALE_RANK_MULTIPLIER : 1.0;
|
|
29
|
-
}
|
|
30
|
-
const BM25_K1 = 1.5;
|
|
31
|
-
const BM25_B = 0.75;
|
|
32
|
-
export function buildCorpus(texts) {
|
|
33
|
-
const docs = texts.map(tokenize);
|
|
34
|
-
const N = docs.length;
|
|
35
|
-
const df = new Map();
|
|
36
|
-
let totalLen = 0;
|
|
37
|
-
for (const doc of docs) {
|
|
38
|
-
totalLen += doc.length;
|
|
39
|
-
const seen = new Set();
|
|
40
|
-
for (const term of doc) {
|
|
41
|
-
if (!seen.has(term)) {
|
|
42
|
-
df.set(term, (df.get(term) ?? 0) + 1);
|
|
43
|
-
seen.add(term);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const avgLen = N > 0 ? totalLen / N : 1;
|
|
48
|
-
return { docs, avgLen, df, N };
|
|
49
|
-
}
|
|
50
|
-
function bm25Score(corpus, docIdx, queryTerms) {
|
|
51
|
-
const doc = corpus.docs[docIdx];
|
|
52
|
-
const docLen = doc.length;
|
|
53
|
-
let score = 0;
|
|
54
|
-
// Term frequency map for this doc
|
|
55
|
-
const tf = new Map();
|
|
56
|
-
for (const t of doc)
|
|
57
|
-
tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
58
|
-
for (const term of queryTerms) {
|
|
59
|
-
const f = tf.get(term) ?? 0;
|
|
60
|
-
if (f === 0)
|
|
61
|
-
continue;
|
|
62
|
-
const df = corpus.df.get(term) ?? 0;
|
|
63
|
-
const idf = Math.log((corpus.N - df + 0.5) / (df + 0.5) + 1);
|
|
64
|
-
const numerator = f * (BM25_K1 + 1);
|
|
65
|
-
const denominator = f + BM25_K1 * (1 - BM25_B + BM25_B * (docLen / corpus.avgLen));
|
|
66
|
-
score += idf * (numerator / denominator);
|
|
67
|
-
}
|
|
68
|
-
return score;
|
|
69
|
-
}
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Token budget estimation
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Rough token estimate (characters / 4). Defined once in token-ledger.ts and
|
|
74
|
-
// re-exported here, where callers have always imported it from.
|
|
75
|
-
export { estimateTokens };
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
// Recency boost
|
|
78
|
-
// ---------------------------------------------------------------------------
|
|
79
|
-
function recencyBoost(entry, now) {
|
|
80
|
-
if (isRecencyAblated())
|
|
81
|
-
return 1; // EVAL-ONLY ablation (see ablation.ts)
|
|
82
|
-
const created = new Date(entry.created);
|
|
83
|
-
const ageDays = (now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24);
|
|
84
|
-
// Exponential decay: memories < 1 day get boost ~1.0, older get less
|
|
85
|
-
return Math.exp(-ageDays / (evalRecencyScaleDays() ?? 30));
|
|
86
|
-
}
|
|
87
|
-
/** Retrieval-time outcome nudge in [0.85, 1.15]; the E1 bm25-outcome baseline ranks with it too. */
|
|
88
|
-
export function outcomeMultiplier(entry) {
|
|
89
|
-
const pos = entry.outcome_positive ?? 0;
|
|
90
|
-
const neg = entry.outcome_negative ?? 0;
|
|
91
|
-
// EVAL-ONLY ablation (see ablation.ts): the fast outcome channel.
|
|
92
|
-
if (isOutcomeFastAblated() || (pos === 0 && neg === 0))
|
|
93
|
-
return 1.0;
|
|
94
|
-
return Math.max(0.85, Math.min(1.15, 1 + 0.15 * Math.tanh((pos - neg) / 2)));
|
|
95
|
-
}
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Temporal-aware scoring
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
const TEMPORAL_RECENT_CUES = new Set(['recently', 'latest', 'last', 'newest', 'current', 'today']);
|
|
100
|
-
const TEMPORAL_OLDEST_CUES = new Set(['first', 'earliest', 'oldest', 'initially', 'originally']);
|
|
101
|
-
export function detectTemporalDirection(query) {
|
|
102
|
-
const words = query.toLowerCase().split(/\s+/);
|
|
103
|
-
for (const w of words) {
|
|
104
|
-
if (TEMPORAL_RECENT_CUES.has(w))
|
|
105
|
-
return 'recent';
|
|
106
|
-
if (TEMPORAL_OLDEST_CUES.has(w))
|
|
107
|
-
return 'oldest';
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
export function computeTemporalRange(entries) {
|
|
112
|
-
let minTime = Infinity;
|
|
113
|
-
let maxTime = -Infinity;
|
|
114
|
-
for (const e of entries) {
|
|
115
|
-
const t = new Date(e.created).getTime();
|
|
116
|
-
if (t < minTime)
|
|
117
|
-
minTime = t;
|
|
118
|
-
if (t > maxTime)
|
|
119
|
-
maxTime = t;
|
|
120
|
-
}
|
|
121
|
-
return { minTime, maxTime };
|
|
122
|
-
}
|
|
123
|
-
export function temporalBoost(entry, direction, range) {
|
|
124
|
-
if (!direction)
|
|
125
|
-
return 1.0;
|
|
126
|
-
const span = range.maxTime - range.minTime;
|
|
127
|
-
if (span === 0)
|
|
128
|
-
return 1.0;
|
|
129
|
-
const entryTime = new Date(entry.created).getTime();
|
|
130
|
-
const normalized = (entryTime - range.minTime) / span;
|
|
131
|
-
if (direction === 'recent') {
|
|
132
|
-
return 0.8 + 0.4 * normalized;
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
return 0.8 + 0.4 * (1 - normalized);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// Skip-and-continue, with the first minResults kept whatever they cost; one loop so every engine spends alike.
|
|
139
|
-
export function fitBudget(ordered, budget, minResults, cost) {
|
|
140
|
-
const results = [];
|
|
141
|
-
let used = 0;
|
|
142
|
-
for (const r of ordered) {
|
|
143
|
-
const tokens = cost ? cost(r) : r.tokens;
|
|
144
|
-
if (results.length >= minResults && used + tokens > budget)
|
|
145
|
-
continue;
|
|
146
|
-
used += tokens;
|
|
147
|
-
results.push(r);
|
|
148
|
-
}
|
|
149
|
-
return results;
|
|
150
|
-
}
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// v0.30 / E4 — DAG L2 summary scoring helpers
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
const DEFAULT_SUMMARY_DEBOOST = 0.85;
|
|
155
|
-
const DEFAULT_FRESHNESS_BOOST = 1.05;
|
|
156
|
-
const FRESHNESS_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
157
|
-
/**
|
|
158
|
-
* v0.30 / E4-E5 — single source of truth for "is this a DAG summary".
|
|
159
|
-
* E4 originally checked dag_level === 2; E5 widens to L2 + L3 since L3
|
|
160
|
-
* entity profiles also get the same deboost factor. Differentiated
|
|
161
|
-
* deboost (e.g. 0.7 for L3) is flagged as follow-up.
|
|
162
|
-
*
|
|
163
|
-
* Existing drill-down at search.ts:506-529/923-946 uses tag check
|
|
164
|
-
* ('dag-summary'); structural truth is dag_level === 2 || === 3. New
|
|
165
|
-
* E4/E5 code uses this helper; existing drill-down NOT modified.
|
|
166
|
-
*/
|
|
167
|
-
export function isDagSummary(entry) {
|
|
168
|
-
return entry.dag_level === 2 || entry.dag_level === 3;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* v0.30 / E4 — resolve summaryDeboost: per-call > env > 0.85 default.
|
|
172
|
-
* Invalid values (≤0, >1, NaN) at any level fall back to 0.85.
|
|
173
|
-
*/
|
|
174
|
-
function resolveSummaryDeboost(perCall) {
|
|
175
|
-
if (perCall !== undefined && Number.isFinite(perCall) && perCall > 0 && perCall <= 1) {
|
|
176
|
-
return perCall;
|
|
177
|
-
}
|
|
178
|
-
const raw = process.env.HIPPO_SUMMARY_DEBOOST;
|
|
179
|
-
if (raw !== undefined) {
|
|
180
|
-
const parsed = parseFloat(raw);
|
|
181
|
-
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1) {
|
|
182
|
-
return parsed;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return DEFAULT_SUMMARY_DEBOOST;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* v0.30 / E4 — micro-boost for L2 summaries rebuilt within window.
|
|
189
|
-
* Returns 1.05 only when isDagSummary AND last_rebuilt_at parses to a
|
|
190
|
-
* finite timestamp within FRESHNESS_WINDOW_MS of `now`. Handles null +
|
|
191
|
-
* garbage string + future-dated via the Number.isFinite + ageMs >= 0 gates.
|
|
192
|
-
*/
|
|
193
|
-
function summaryFreshnessMultiplier(entry, now) {
|
|
194
|
-
if (!isDagSummary(entry) || !entry.last_rebuilt_at)
|
|
195
|
-
return 1.0;
|
|
196
|
-
const rebuiltMs = new Date(entry.last_rebuilt_at).getTime();
|
|
197
|
-
if (!Number.isFinite(rebuiltMs))
|
|
198
|
-
return 1.0;
|
|
199
|
-
const ageMs = now.getTime() - rebuiltMs;
|
|
200
|
-
return ageMs >= 0 && ageMs <= FRESHNESS_WINDOW_MS ? DEFAULT_FRESHNESS_BOOST : 1.0;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Hybrid search: BM25 + cosine similarity (when embeddings are available).
|
|
204
|
-
* score = 0.4 * bm25_norm + 0.6 * cosine_sim (with embeddings)
|
|
205
|
-
* score = bm25_norm * strength * recency (BM25-only fallback)
|
|
206
|
-
*
|
|
207
|
-
* embeddingWeight: weight for the cosine similarity component (0.0 to 1.0).
|
|
208
|
-
*/
|
|
209
|
-
export async function hybridSearch(query, entries, options = {}) {
|
|
210
|
-
const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
211
|
-
const budget = options.budget ?? 4000;
|
|
212
|
-
const minResults = options.minResults ?? 1;
|
|
213
|
-
const embeddingWeight = options.embeddingWeight ?? 0.6;
|
|
214
|
-
const bm25Weight = 1 - embeddingWeight;
|
|
215
|
-
const scoringMode = options.scoring ?? 'blend';
|
|
216
|
-
const explain = options.explain ?? false;
|
|
217
|
-
const mmrEnabled = options.mmr ?? true;
|
|
218
|
-
const mmrLambda = options.mmrLambda ?? 0.7;
|
|
219
|
-
// v0.30 / E4 — DAG L2 summary scoring controls
|
|
220
|
-
const summaryDeboost = resolveSummaryDeboost(options.summaryDeboost);
|
|
221
|
-
const summaryFreshness = options.summaryFreshness ?? true;
|
|
222
|
-
// Bi-temporal filtering
|
|
223
|
-
if (options.asOf) {
|
|
224
|
-
const asOfDate = new Date(options.asOf);
|
|
225
|
-
const successorValidFrom = new Map();
|
|
226
|
-
for (const e of entries) {
|
|
227
|
-
if (e.superseded_by) {
|
|
228
|
-
const successor = entries.find(s => s.id === e.superseded_by);
|
|
229
|
-
if (successor)
|
|
230
|
-
successorValidFrom.set(e.id, successor.valid_from);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
entries = entries.filter(e => {
|
|
234
|
-
if (new Date(e.valid_from) > asOfDate)
|
|
235
|
-
return false;
|
|
236
|
-
if (!e.superseded_by)
|
|
237
|
-
return true;
|
|
238
|
-
const succVf = successorValidFrom.get(e.id);
|
|
239
|
-
return succVf ? new Date(succVf) > asOfDate : true;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
else if (!options.includeSuperseded) {
|
|
243
|
-
entries = entries.filter(e => !e.superseded_by);
|
|
244
|
-
}
|
|
245
|
-
if (entries.length === 0)
|
|
246
|
-
return [];
|
|
247
|
-
const queryTerms = tokenize(query);
|
|
248
|
-
if (queryTerms.length === 0)
|
|
249
|
-
return [];
|
|
250
|
-
// Build BM25 corpus (or reuse one the caller already built).
|
|
251
|
-
const corpus = options.preparedCorpus
|
|
252
|
-
?? buildCorpus(entries.map((e) => `${e.content} ${e.tags.join(' ')}`));
|
|
253
|
-
// Score all entries with BM25
|
|
254
|
-
const bm25Scores = entries.map((_, i) => bm25Score(corpus, i, queryTerms));
|
|
255
|
-
const maxBm25 = bm25Scores.reduce((a, b) => Math.max(a, b), 1e-9);
|
|
256
|
-
// Try to get embedding scores if available
|
|
257
|
-
let useEmbeddings = false;
|
|
258
|
-
let embeddingIndex = {};
|
|
259
|
-
let queryVector = [];
|
|
260
|
-
if (options.hippoRoot) {
|
|
261
|
-
try {
|
|
262
|
-
const provider = resolveEmbeddingProvider(options.hippoRoot);
|
|
263
|
-
const idx = provider.isAvailable() ? loadEmbeddingIndex(options.hippoRoot) : {};
|
|
264
|
-
if (provider.isAvailable() && !embeddingModelRequiresReindex(options.hippoRoot, provider.id, idx)) {
|
|
265
|
-
// Only spend a (possibly paid, off-box) query embedding when at least one
|
|
266
|
-
// of THIS search's entries has a cached vector to compare against. An
|
|
267
|
-
// index of only orphaned / out-of-scope vectors yields a meaningless
|
|
268
|
-
// dense ranking, so stay BM25-only in that case.
|
|
269
|
-
if (entries.some((e) => (idx[e.id]?.length ?? 0) > 0)) {
|
|
270
|
-
const [vec] = await provider.embed([query], 'query');
|
|
271
|
-
queryVector = vec ?? [];
|
|
272
|
-
if (queryVector.length > 0) {
|
|
273
|
-
embeddingIndex = idx;
|
|
274
|
-
useEmbeddings = true;
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
warnBm25Fallback('empty-query-vector', 'the embedding provider returned no vector for the query');
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
else if (provider.isAvailable()) {
|
|
282
|
-
warnBm25Fallback('reindex', "the embedding index was built by another model or is being rebuilt; run 'hippo embed'");
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch (err) {
|
|
286
|
-
warnBm25Fallback('error', err instanceof Error ? err.message : String(err));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
// Compute cosine similarities for RRF ranking (need all before scoring)
|
|
290
|
-
const cosineScores = new Array(entries.length).fill(0);
|
|
291
|
-
const hadCachedVecs = new Array(entries.length).fill(false);
|
|
292
|
-
if (useEmbeddings) {
|
|
293
|
-
for (let i = 0; i < entries.length; i++) {
|
|
294
|
-
const cached = embeddingIndex[entries[i].id];
|
|
295
|
-
hadCachedVecs[i] = Boolean(cached && queryVector.length > 0);
|
|
296
|
-
cosineScores[i] = hadCachedVecs[i]
|
|
297
|
-
? Math.max(0, cosineSimilarity(queryVector, cached))
|
|
298
|
-
: 0;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// RRF fusion: shared with the F9 LongMemEval benchmark via src/rrf.ts.
|
|
302
|
-
// `absentRank: entries.length + 1` preserves the pre-extraction convention
|
|
303
|
-
// (rrfFuse's default would otherwise use the max ranked-list length, which
|
|
304
|
-
// matches in the current filter path but differs if filtering ever changes).
|
|
305
|
-
let rrfScores = null;
|
|
306
|
-
if (useEmbeddings && scoringMode === 'rrf') {
|
|
307
|
-
const eligible = entries
|
|
308
|
-
.map((_, i) => i)
|
|
309
|
-
.filter((i) => bm25Scores[i] > 0 || cosineScores[i] > 0);
|
|
310
|
-
// score desc -> compareEntryIdentity tail (deterministic across fresh
|
|
311
|
-
// ingests): these two rankings feed selectGraphSeeds + RRF rank
|
|
312
|
-
// assignment, so an unbroken tie here would leak into seed selection.
|
|
313
|
-
const bm25Ranked = [...eligible].sort((a, b) => {
|
|
314
|
-
const d = bm25Scores[b] - bm25Scores[a];
|
|
315
|
-
return d !== 0 ? d : compareEntryIdentity(entries[a], entries[b]);
|
|
316
|
-
});
|
|
317
|
-
const cosineRanked = [...eligible].sort((a, b) => {
|
|
318
|
-
const d = cosineScores[b] - cosineScores[a];
|
|
319
|
-
return d !== 0 ? d : compareEntryIdentity(entries[a], entries[b]);
|
|
320
|
-
});
|
|
321
|
-
// L1 graph-retrieval stream (opt-in): a 3rd RRF input ranking in-pool candidates by
|
|
322
|
-
// graph proximity to the strong lexical seeds. When absent / weight<=0 / the graph
|
|
323
|
-
// yields nothing in-pool, `graphRanked` is empty and the fusion below is the
|
|
324
|
-
// byte-identical 2-list (BM25 + dense) path. An all-absent 3rd list would NOT be a
|
|
325
|
-
// no-op: it adds a uniform constant to the RRF base, which the per-entry multipliers
|
|
326
|
-
// below turn non-uniform — so we SKIP it rather than fuse an empty list.
|
|
327
|
-
// Gate the graph stream on the candidate set actually having cached document vectors
|
|
328
|
-
// (codex P2): when the transformers package is installed but this store has not been
|
|
329
|
-
// embedded yet, `useEmbeddings` is true (only the QUERY vector was produced) while every
|
|
330
|
-
// `cosineScore` is 0, so `cosineRanked` collapses to entry order. Without this gate the
|
|
331
|
-
// stream would select seeds from that meaningless dense ranking; instead it stays inert
|
|
332
|
-
// (degrades to the 2-list path) until `hippo embed` has run.
|
|
333
|
-
const hasDocVectors = hadCachedVecs.some(Boolean);
|
|
334
|
-
const gs = options.graphStream;
|
|
335
|
-
let graphRanked = [];
|
|
336
|
-
if (gs && gs.weight > 0 && options.hippoRoot && hasDocVectors) {
|
|
337
|
-
const seedCount = Math.min(gs.seedCount ?? DEFAULT_GRAPH_SEED_COUNT, eligible.length);
|
|
338
|
-
const seeds = selectGraphSeeds(bm25Ranked, cosineRanked, seedCount);
|
|
339
|
-
graphRanked = graphRankStream(entries, seeds, {
|
|
340
|
-
hippoRoot: options.hippoRoot,
|
|
341
|
-
tenantId: gs.tenantId,
|
|
342
|
-
globalRoot: gs.globalRoot,
|
|
343
|
-
hops: gs.hops,
|
|
344
|
-
decay: gs.decay,
|
|
345
|
-
maxNeighbors: gs.maxNeighbors,
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
rrfScores = graphRanked.length > 0
|
|
349
|
-
? rrfFuse([bm25Ranked, cosineRanked, graphRanked], [bm25Weight, embeddingWeight, gs.weight], { absentRank: entries.length + 1 })
|
|
350
|
-
: rrfFuse([bm25Ranked, cosineRanked], [bm25Weight, embeddingWeight], { absentRank: entries.length + 1 });
|
|
351
|
-
}
|
|
352
|
-
// Score each entry
|
|
353
|
-
const scored = [];
|
|
354
|
-
const currentPathTags = extractPathTags(process.cwd());
|
|
355
|
-
const activeScope = options.scope !== undefined ? options.scope : detectScope();
|
|
356
|
-
const queryTermSet = new Set(queryTerms);
|
|
357
|
-
const temporalDirAsync = detectTemporalDirection(query);
|
|
358
|
-
const temporalRangeAsync = temporalDirAsync ? computeTemporalRange(entries) : { minTime: 0, maxTime: 0 };
|
|
359
|
-
for (let i = 0; i < entries.length; i++) {
|
|
360
|
-
const rawBm25 = bm25Scores[i];
|
|
361
|
-
const cosineScore = cosineScores[i];
|
|
362
|
-
const hadCachedVec = hadCachedVecs[i];
|
|
363
|
-
if (!useEmbeddings && rawBm25 <= 0)
|
|
364
|
-
continue;
|
|
365
|
-
const normBm25 = rawBm25 / maxBm25;
|
|
366
|
-
const strength = calculateStrength(entries[i], now);
|
|
367
|
-
const recency = recencyBoost(entries[i], now);
|
|
368
|
-
const strengthMultiplier = 0.5 + 0.5 * strength;
|
|
369
|
-
const recencyMultiplier = 0.8 + 0.2 * recency;
|
|
370
|
-
let compositeScore;
|
|
371
|
-
let base;
|
|
372
|
-
let modeLabel;
|
|
373
|
-
if (useEmbeddings) {
|
|
374
|
-
if (rrfScores) {
|
|
375
|
-
base = rrfScores.get(i) ?? 0;
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
base = bm25Weight * normBm25 + embeddingWeight * cosineScore;
|
|
379
|
-
}
|
|
380
|
-
compositeScore = base * strengthMultiplier * recencyMultiplier;
|
|
381
|
-
modeLabel = hadCachedVec ? 'hybrid' : 'hybrid-no-vec';
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
base = queryTerms.length > 0 ? rawBm25 / queryTerms.length : rawBm25;
|
|
385
|
-
compositeScore = base * strengthMultiplier * recencyMultiplier;
|
|
386
|
-
modeLabel = 'bm25-only';
|
|
387
|
-
}
|
|
388
|
-
// Decision-tagged memories get a 1.2x recall boost
|
|
389
|
-
const decisionBoost = entries[i].tags.includes('decision') ? 1.2 : 1.0;
|
|
390
|
-
compositeScore *= decisionBoost;
|
|
391
|
-
const churnStaleMultiplier = churnStaleFactor(entries[i]);
|
|
392
|
-
compositeScore *= churnStaleMultiplier;
|
|
393
|
-
// Path-based boost: memories tagged with matching path segments get up to 1.3x
|
|
394
|
-
const pathBoost = pathBoostMultiplier(entries[i].tags, currentPathTags);
|
|
395
|
-
compositeScore *= pathBoost;
|
|
396
|
-
// Retrieval-time outcome personalization: nudge up/down from user feedback.
|
|
397
|
-
// Distinct from reward-factor-via-strength (slow); this is immediate.
|
|
398
|
-
const outcomeBoost = outcomeMultiplier(entries[i]);
|
|
399
|
-
compositeScore *= outcomeBoost;
|
|
400
|
-
// Scope boost: memories tagged with the active scope get 1.5x; mismatching scopes get 0.5x
|
|
401
|
-
const scopeSignal = scopeMatch(entries[i].tags, activeScope);
|
|
402
|
-
const scopeBoost = scopeSignal === 1 ? 1.5 : scopeSignal === -1 ? 0.5 : 1.0;
|
|
403
|
-
compositeScore *= scopeBoost;
|
|
404
|
-
const extractionBoost = entries[i].tags.includes('extracted') ? 1.3 : 1.0;
|
|
405
|
-
compositeScore *= extractionBoost;
|
|
406
|
-
compositeScore *= temporalBoost(entries[i], temporalDirAsync, temporalRangeAsync);
|
|
407
|
-
// v0.30 / E4 — DAG L2 summary deboost + freshness micro-boost.
|
|
408
|
-
// Applied last so it composes with all other multipliers.
|
|
409
|
-
let summaryDeboostMultiplier = 1.0;
|
|
410
|
-
let freshnessMultiplier = 1.0;
|
|
411
|
-
if (isDagSummary(entries[i])) {
|
|
412
|
-
summaryDeboostMultiplier = summaryDeboost;
|
|
413
|
-
if (summaryFreshness) {
|
|
414
|
-
freshnessMultiplier = summaryFreshnessMultiplier(entries[i], now);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
compositeScore *= summaryDeboostMultiplier * freshnessMultiplier;
|
|
418
|
-
if (compositeScore <= 0)
|
|
419
|
-
continue;
|
|
420
|
-
const tokens = estimateTokens(entries[i].content);
|
|
421
|
-
const result = {
|
|
422
|
-
entry: entries[i],
|
|
423
|
-
score: compositeScore,
|
|
424
|
-
bm25: rawBm25,
|
|
425
|
-
cosine: cosineScore,
|
|
426
|
-
tokens,
|
|
427
|
-
};
|
|
428
|
-
if (explain) {
|
|
429
|
-
const docTerms = new Set(tokenize(`${entries[i].content} ${entries[i].tags.join(' ')}`));
|
|
430
|
-
const matchedTerms = [];
|
|
431
|
-
for (const t of queryTermSet)
|
|
432
|
-
if (docTerms.has(t))
|
|
433
|
-
matchedTerms.push(t);
|
|
434
|
-
const ageDays = Math.max(0, Math.floor((now.getTime() - new Date(entries[i].created).getTime()) / 86_400_000));
|
|
435
|
-
result.breakdown = {
|
|
436
|
-
mode: modeLabel,
|
|
437
|
-
normBm25,
|
|
438
|
-
bm25Weight: useEmbeddings ? bm25Weight : 1,
|
|
439
|
-
embeddingWeight: useEmbeddings ? embeddingWeight : 0,
|
|
440
|
-
cosine: cosineScore,
|
|
441
|
-
base,
|
|
442
|
-
strengthMultiplier,
|
|
443
|
-
recencyMultiplier,
|
|
444
|
-
decisionBoost,
|
|
445
|
-
pathBoost,
|
|
446
|
-
scopeBoost,
|
|
447
|
-
sourceBump: 1,
|
|
448
|
-
outcomeBoost,
|
|
449
|
-
churnStaleMultiplier,
|
|
450
|
-
matchedTerms,
|
|
451
|
-
final: compositeScore,
|
|
452
|
-
ageDays,
|
|
453
|
-
};
|
|
454
|
-
// v0.30 / E4 — DAG metadata
|
|
455
|
-
if (entries[i].dag_level !== undefined) {
|
|
456
|
-
result.breakdown.dagLevel = entries[i].dag_level;
|
|
457
|
-
}
|
|
458
|
-
if (entries[i].descendant_count !== undefined) {
|
|
459
|
-
result.breakdown.descendantCount = entries[i].descendant_count;
|
|
460
|
-
}
|
|
461
|
-
if (isDagSummary(entries[i])) {
|
|
462
|
-
result.breakdown.lastRebuiltAt = entries[i].last_rebuilt_at ?? null;
|
|
463
|
-
result.breakdown.rebuildCount = entries[i].rebuild_count ?? 0;
|
|
464
|
-
result.breakdown.summaryDeboost = summaryDeboostMultiplier;
|
|
465
|
-
result.breakdown.summaryFreshnessBoost = freshnessMultiplier;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
scored.push(result);
|
|
469
|
-
}
|
|
470
|
-
// Sort by composite score descending, deterministic tiebreak on ties.
|
|
471
|
-
scored.sort(compareScoredResults);
|
|
472
|
-
// Deduplicate: when an extracted fact and its source both appear,
|
|
473
|
-
// keep only the higher-scoring one (typically the fact).
|
|
474
|
-
const seenExtractedFrom = new Set();
|
|
475
|
-
const deduped = [];
|
|
476
|
-
for (const result of scored) {
|
|
477
|
-
const entry = result.entry;
|
|
478
|
-
if (entry.extracted_from) {
|
|
479
|
-
seenExtractedFrom.add(entry.extracted_from);
|
|
480
|
-
const sourceIdx = deduped.findIndex((d) => d.entry.id === entry.extracted_from);
|
|
481
|
-
if (sourceIdx >= 0)
|
|
482
|
-
deduped.splice(sourceIdx, 1);
|
|
483
|
-
deduped.push(result);
|
|
484
|
-
}
|
|
485
|
-
else if (seenExtractedFrom.has(entry.id)) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
deduped.push(result);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
const scoredDeduped = deduped;
|
|
493
|
-
// DAG drill-down: when a summary node matches, inject its children
|
|
494
|
-
const summaryIdsAsync = scoredDeduped
|
|
495
|
-
.filter((r) => r.entry.tags.includes('dag-summary'))
|
|
496
|
-
.map((r) => r.entry.id);
|
|
497
|
-
if (summaryIdsAsync.length > 0) {
|
|
498
|
-
const childEntriesAsync = entries.filter((e) => e.dag_parent_id && summaryIdsAsync.includes(e.dag_parent_id));
|
|
499
|
-
for (const child of childEntriesAsync) {
|
|
500
|
-
if (!scoredDeduped.some((r) => r.entry.id === child.id)) {
|
|
501
|
-
const parentResult = scoredDeduped.find((r) => r.entry.id === child.dag_parent_id);
|
|
502
|
-
const childScore = parentResult ? parentResult.score * 0.9 * churnStaleFactor(child) : 0;
|
|
503
|
-
scoredDeduped.push({
|
|
504
|
-
entry: child,
|
|
505
|
-
score: childScore,
|
|
506
|
-
bm25: 0,
|
|
507
|
-
cosine: 0,
|
|
508
|
-
tokens: estimateTokens(child.content),
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
scoredDeduped.sort(compareScoredResults);
|
|
513
|
-
}
|
|
514
|
-
// MMR re-ranking: de-cluster near-duplicates by trading relevance for
|
|
515
|
-
// diversity. Only applies when embeddings are loaded (doc-to-doc similarity
|
|
516
|
-
// is via cosine of cached vectors); otherwise we return the pure-relevance
|
|
517
|
-
// ordering unchanged.
|
|
518
|
-
//
|
|
519
|
-
// MMR is O(K^2) in cosine similarity ops, which on large corpora (1000+
|
|
520
|
-
// candidates) dominates query time. Cap the re-ranking window to the top
|
|
521
|
-
// relevance-scored candidates — anything below top-K was never going to
|
|
522
|
-
// surface anyway after budget filtering.
|
|
523
|
-
const MMR_CANDIDATE_CAP = 100;
|
|
524
|
-
const applyMmr = mmrEnabled && useEmbeddings && scoredDeduped.length > 1 && mmrLambda < 1;
|
|
525
|
-
let ordered;
|
|
526
|
-
if (applyMmr) {
|
|
527
|
-
const head = scoredDeduped.slice(0, MMR_CANDIDATE_CAP);
|
|
528
|
-
const tail = scoredDeduped.slice(MMR_CANDIDATE_CAP);
|
|
529
|
-
ordered = [...mmrRerank(head, embeddingIndex, mmrLambda, explain), ...tail];
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
ordered = scoredDeduped;
|
|
533
|
-
}
|
|
534
|
-
// Reranker pass: see src/rerankers/types.ts and
|
|
535
|
-
// docs/plans/2026-05-10-f6-reranker-hardening.md.
|
|
536
|
-
if (options.reranker) {
|
|
537
|
-
const topK = options.rerankerOptions?.topK ?? 50;
|
|
538
|
-
const head = ordered.slice(0, topK);
|
|
539
|
-
const tail = ordered.slice(topK);
|
|
540
|
-
const rerankInputWithRank = head.map((r, i) => ({ ...r, preRerankRank: i + 1 }));
|
|
541
|
-
const reranked = await options.reranker(query, rerankInputWithRank, { ...options.rerankerOptions, topK });
|
|
542
|
-
const withPostRank = reranked.map((r, i) => ({ ...r, postRerankRank: i + 1 }));
|
|
543
|
-
ordered = [...withPostRank, ...tail];
|
|
544
|
-
}
|
|
545
|
-
return fitBudget(ordered, budget, minResults, options.cost);
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* MMR (Maximal Marginal Relevance) re-ranking.
|
|
549
|
-
*
|
|
550
|
-
* Iteratively picks the candidate that maximises
|
|
551
|
-
* lambda * relevance - (1 - lambda) * max(cos(cand, picked))
|
|
552
|
-
*
|
|
553
|
-
* Inputs must already be sorted by relevance descending. When `explain` is
|
|
554
|
-
* true, attaches `preMmrRank` / `postMmrRank` to each result's breakdown.
|
|
555
|
-
* Exported for unit tests; production callers go through hybridSearch.
|
|
556
|
-
*
|
|
557
|
-
* Determinism note (T2): the picking loop below uses strict `mmr > bestMmr`
|
|
558
|
-
* (first-wins on ties), so it is already deterministic GIVEN a
|
|
559
|
-
* deterministic `scored` input order — no comparator change needed here;
|
|
560
|
-
* the tiebreak lives upstream, in how `scored` was sorted before this runs.
|
|
561
|
-
*/
|
|
562
|
-
export function mmrRerank(scored, embeddingIndex, lambda, explain) {
|
|
563
|
-
if (scored.length === 0)
|
|
564
|
-
return scored;
|
|
565
|
-
const maxScore = scored[0].score || 1;
|
|
566
|
-
const normScore = scored.map((r) => r.score / maxScore);
|
|
567
|
-
const vectors = scored.map((r) => embeddingIndex[r.entry.id] ?? null);
|
|
568
|
-
const picked = [];
|
|
569
|
-
const remaining = new Set(scored.map((_, i) => i));
|
|
570
|
-
while (remaining.size > 0) {
|
|
571
|
-
let bestIdx = -1;
|
|
572
|
-
let bestMmr = -Infinity;
|
|
573
|
-
for (const i of remaining) {
|
|
574
|
-
const rel = normScore[i];
|
|
575
|
-
let maxSim = 0;
|
|
576
|
-
const vi = vectors[i];
|
|
577
|
-
if (vi) {
|
|
578
|
-
for (const p of picked) {
|
|
579
|
-
const vp = embeddingIndex[p.entry.id];
|
|
580
|
-
if (!vp || vp.length !== vi.length)
|
|
581
|
-
continue;
|
|
582
|
-
const sim = Math.max(0, cosineSimilarity(vi, vp));
|
|
583
|
-
if (sim > maxSim)
|
|
584
|
-
maxSim = sim;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const mmr = lambda * rel - (1 - lambda) * maxSim;
|
|
588
|
-
if (mmr > bestMmr) {
|
|
589
|
-
bestMmr = mmr;
|
|
590
|
-
bestIdx = i;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (bestIdx === -1)
|
|
594
|
-
break;
|
|
595
|
-
remaining.delete(bestIdx);
|
|
596
|
-
picked.push(scored[bestIdx]);
|
|
597
|
-
}
|
|
598
|
-
if (explain) {
|
|
599
|
-
const preRank = new Map();
|
|
600
|
-
scored.forEach((r, i) => preRank.set(r.entry.id, i + 1));
|
|
601
|
-
picked.forEach((r, i) => {
|
|
602
|
-
if (r.breakdown) {
|
|
603
|
-
r.breakdown.preMmrRank = preRank.get(r.entry.id);
|
|
604
|
-
r.breakdown.postMmrRank = i + 1;
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
return picked;
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Physics-based search: scores memories using gravitational force, momentum,
|
|
612
|
-
* and cluster amplification. Falls back to classic hybrid for memories
|
|
613
|
-
* without physics state.
|
|
614
|
-
*/
|
|
615
|
-
export async function physicsSearch(query, entries, options = {}) {
|
|
616
|
-
const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
617
|
-
const budget = options.budget ?? 4000;
|
|
618
|
-
const minResults = options.minResults ?? 1;
|
|
619
|
-
const config = options.physicsConfig ?? DEFAULT_PHYSICS_CONFIG;
|
|
620
|
-
const explain = options.explain ?? false;
|
|
621
|
-
// v0.30 / E4 — DAG L2 summary scoring controls (same as hybridSearch)
|
|
622
|
-
const summaryDeboost = resolveSummaryDeboost(options.summaryDeboost);
|
|
623
|
-
const summaryFreshness = options.summaryFreshness ?? true;
|
|
624
|
-
if (entries.length === 0 || !options.hippoRoot)
|
|
625
|
-
return [];
|
|
626
|
-
// Get query embedding (use pre-computed if provided)
|
|
627
|
-
let queryVector = options.queryEmbedding ?? [];
|
|
628
|
-
if (queryVector.length === 0) {
|
|
629
|
-
// Both resolveEmbeddingProvider (invalid config) and provider.embed (API
|
|
630
|
-
// network/auth/5xx) can throw; fall back to hybrid/BM25 rather than reject
|
|
631
|
-
// recall, matching the surrounding fallback contract. NOTE: physics search
|
|
632
|
-
// scores against its own cached particle-state vectors (loaded below), NOT
|
|
633
|
-
// embeddings.json, so we do not gate the query embedding on the index here
|
|
634
|
-
// (doing so would skip valid physics when the embedding index is pruned).
|
|
635
|
-
try {
|
|
636
|
-
const provider = resolveEmbeddingProvider(options.hippoRoot);
|
|
637
|
-
if (!provider.isAvailable() || embeddingModelRequiresReindex(options.hippoRoot, provider.id)) {
|
|
638
|
-
return hybridSearch(query, entries, options);
|
|
639
|
-
}
|
|
640
|
-
const [vec] = await provider.embed([query], 'query');
|
|
641
|
-
queryVector = vec ?? [];
|
|
642
|
-
}
|
|
643
|
-
catch {
|
|
644
|
-
return hybridSearch(query, entries, options);
|
|
645
|
-
}
|
|
646
|
-
if (queryVector.length === 0) {
|
|
647
|
-
return hybridSearch(query, entries, options);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
// Load physics state
|
|
651
|
-
let physicsMap;
|
|
652
|
-
try {
|
|
653
|
-
const db = openHippoDb(options.hippoRoot);
|
|
654
|
-
try {
|
|
655
|
-
physicsMap = loadPhysicsState(db);
|
|
656
|
-
}
|
|
657
|
-
finally {
|
|
658
|
-
closeHippoDb(db);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
catch {
|
|
662
|
-
return hybridSearch(query, entries, options);
|
|
663
|
-
}
|
|
664
|
-
// Split entries into physics-enabled and classic
|
|
665
|
-
const physicsEntries = [];
|
|
666
|
-
const physicsParticles = [];
|
|
667
|
-
const classicEntries = [];
|
|
668
|
-
for (const entry of entries) {
|
|
669
|
-
const particle = physicsMap.get(entry.id);
|
|
670
|
-
if (particle
|
|
671
|
-
&& particle.position.length > 0
|
|
672
|
-
&& particle.position.length === queryVector.length
|
|
673
|
-
&& particle.velocity.length === queryVector.length) {
|
|
674
|
-
physicsEntries.push(entry);
|
|
675
|
-
// EVAL-ONLY ablation (see ablation.ts): persisted masses embed recall
|
|
676
|
-
// history TWICE - the outer retrieval-count multiplier AND the frozen
|
|
677
|
-
// strength inside it (computed at sleep time from clock-reset
|
|
678
|
-
// last_retrieved + the read-side boost). Stripping the outer multiplier
|
|
679
|
-
// alone is not enough (codex round-7 P2); under the flag, recompute the
|
|
680
|
-
// mass LIVE from the current entry under ablated strength rules
|
|
681
|
-
// (created-anchored decay, no boost - both applied inside
|
|
682
|
-
// calculateStrength/computeMass by the same flag). The frozen-at-sleep
|
|
683
|
-
// semantics is unrecoverable for the ablated arm by definition.
|
|
684
|
-
physicsParticles.push(isRecallBoostAblated()
|
|
685
|
-
? { ...particle, mass: computeMass(calculateStrength(entry, now), entry.retrieval_count) }
|
|
686
|
-
: particle);
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
classicEntries.push(entry);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
// Score physics-enabled memories
|
|
693
|
-
const physicsResults = [];
|
|
694
|
-
if (physicsParticles.length > 0) {
|
|
695
|
-
const entryMap = new Map(physicsEntries.map(e => [e.id, e]));
|
|
696
|
-
// Content tie key: the physics baseScore tie order selects the
|
|
697
|
-
// cluster_top_k amplification set, so it must be cross-ingest-stable
|
|
698
|
-
// (codex review). memoryId fallback covers particles whose entry was
|
|
699
|
-
// filtered from physicsEntries.
|
|
700
|
-
const scored = computePhysicsScores(physicsParticles, queryVector, config, (id) => entryMap.get(id)?.content ?? id);
|
|
701
|
-
for (const s of scored) {
|
|
702
|
-
if (s.finalScore <= 0)
|
|
703
|
-
continue;
|
|
704
|
-
const entry = entryMap.get(s.memoryId);
|
|
705
|
-
if (!entry)
|
|
706
|
-
continue;
|
|
707
|
-
// v0.30 / E4 — apply DAG L2 summary deboost + freshness in physics path.
|
|
708
|
-
let summaryDeboostMultiplier = 1.0;
|
|
709
|
-
let freshnessMultiplier = 1.0;
|
|
710
|
-
if (isDagSummary(entry)) {
|
|
711
|
-
summaryDeboostMultiplier = summaryDeboost;
|
|
712
|
-
if (summaryFreshness) {
|
|
713
|
-
freshnessMultiplier = summaryFreshnessMultiplier(entry, now);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
const churnStaleMultiplier = churnStaleFactor(entry);
|
|
717
|
-
const finalScore = s.finalScore * summaryDeboostMultiplier * freshnessMultiplier * churnStaleMultiplier;
|
|
718
|
-
if (finalScore <= 0)
|
|
719
|
-
continue;
|
|
720
|
-
const result = {
|
|
721
|
-
entry,
|
|
722
|
-
score: finalScore,
|
|
723
|
-
bm25: 0,
|
|
724
|
-
cosine: s.baseScore,
|
|
725
|
-
tokens: estimateTokens(entry.content),
|
|
726
|
-
};
|
|
727
|
-
if (explain) {
|
|
728
|
-
const ageDays = Math.max(0, Math.floor((now.getTime() - new Date(entry.created).getTime()) / 86_400_000));
|
|
729
|
-
result.breakdown = {
|
|
730
|
-
mode: 'physics',
|
|
731
|
-
normBm25: 0,
|
|
732
|
-
bm25Weight: 0,
|
|
733
|
-
embeddingWeight: 1,
|
|
734
|
-
cosine: s.baseScore,
|
|
735
|
-
base: s.baseScore,
|
|
736
|
-
strengthMultiplier: 1,
|
|
737
|
-
recencyMultiplier: 1,
|
|
738
|
-
decisionBoost: 1,
|
|
739
|
-
pathBoost: 1,
|
|
740
|
-
scopeBoost: 1,
|
|
741
|
-
sourceBump: 1,
|
|
742
|
-
outcomeBoost: 1,
|
|
743
|
-
churnStaleMultiplier,
|
|
744
|
-
matchedTerms: [],
|
|
745
|
-
final: finalScore,
|
|
746
|
-
ageDays,
|
|
747
|
-
};
|
|
748
|
-
// v0.30 / E4 — DAG metadata
|
|
749
|
-
if (entry.dag_level !== undefined) {
|
|
750
|
-
result.breakdown.dagLevel = entry.dag_level;
|
|
751
|
-
}
|
|
752
|
-
if (entry.descendant_count !== undefined) {
|
|
753
|
-
result.breakdown.descendantCount = entry.descendant_count;
|
|
754
|
-
}
|
|
755
|
-
if (isDagSummary(entry)) {
|
|
756
|
-
result.breakdown.lastRebuiltAt = entry.last_rebuilt_at ?? null;
|
|
757
|
-
result.breakdown.rebuildCount = entry.rebuild_count ?? 0;
|
|
758
|
-
result.breakdown.summaryDeboost = summaryDeboostMultiplier;
|
|
759
|
-
result.breakdown.summaryFreshnessBoost = freshnessMultiplier;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
physicsResults.push(result);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
// Score classic memories (no physics state)
|
|
766
|
-
const classicResults = classicEntries.length > 0
|
|
767
|
-
? await hybridSearch(query, classicEntries, { ...options, budget: Infinity, explain })
|
|
768
|
-
: [];
|
|
769
|
-
// Normalize both pools to [0, 1] and merge
|
|
770
|
-
const merged = mergeScorePools(physicsResults, classicResults);
|
|
771
|
-
// Sort and apply budget, deterministic tiebreak on ties.
|
|
772
|
-
merged.sort(compareScoredResults);
|
|
773
|
-
return fitBudget(merged, budget, minResults, options.cost);
|
|
774
|
-
}
|
|
775
|
-
/** Normalize two score pools to [0,1] and combine. */
|
|
776
|
-
function mergeScorePools(poolA, poolB) {
|
|
777
|
-
const unpenalised = (r) => r.score / churnStaleFactor(r.entry);
|
|
778
|
-
const maxA = poolA.reduce((m, r) => Math.max(m, unpenalised(r)), 1e-9);
|
|
779
|
-
const maxB = poolB.reduce((m, r) => Math.max(m, unpenalised(r)), 1e-9);
|
|
780
|
-
const merged = [];
|
|
781
|
-
for (const r of poolA) {
|
|
782
|
-
merged.push({ ...r, score: r.score / maxA });
|
|
783
|
-
}
|
|
784
|
-
for (const r of poolB) {
|
|
785
|
-
merged.push({ ...r, score: r.score / maxB });
|
|
786
|
-
}
|
|
787
|
-
return merged;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Search entries using BM25 + strength + recency composite score.
|
|
791
|
-
* When embeddings are available and hippoRoot is provided, uses hybrid scoring.
|
|
792
|
-
* Returns results sorted by score, capped at token budget.
|
|
793
|
-
*
|
|
794
|
-
* Also updates retrieval metadata on returned entries (side effect: caller
|
|
795
|
-
* must persist the updated entries).
|
|
796
|
-
*/
|
|
797
|
-
export function search(query, entries, options = {}) {
|
|
798
|
-
// Synchronous path: BM25 only (no async hybrid)
|
|
799
|
-
const now = options.now ?? evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
800
|
-
const budget = options.budget ?? 4000;
|
|
801
|
-
const minResults = options.minResults ?? 1;
|
|
802
|
-
// Bi-temporal filtering
|
|
803
|
-
if (options.asOf) {
|
|
804
|
-
const asOfDate = new Date(options.asOf);
|
|
805
|
-
const successorValidFrom = new Map();
|
|
806
|
-
for (const e of entries) {
|
|
807
|
-
if (e.superseded_by) {
|
|
808
|
-
const successor = entries.find(s => s.id === e.superseded_by);
|
|
809
|
-
if (successor)
|
|
810
|
-
successorValidFrom.set(e.id, successor.valid_from);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
entries = entries.filter(e => {
|
|
814
|
-
if (new Date(e.valid_from) > asOfDate)
|
|
815
|
-
return false;
|
|
816
|
-
if (!e.superseded_by)
|
|
817
|
-
return true;
|
|
818
|
-
const succVf = successorValidFrom.get(e.id);
|
|
819
|
-
return succVf ? new Date(succVf) > asOfDate : true;
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
else if (!options.includeSuperseded) {
|
|
823
|
-
entries = entries.filter(e => !e.superseded_by);
|
|
824
|
-
}
|
|
825
|
-
if (entries.length === 0)
|
|
826
|
-
return [];
|
|
827
|
-
const queryTerms = tokenize(query);
|
|
828
|
-
if (queryTerms.length === 0)
|
|
829
|
-
return [];
|
|
830
|
-
// Build corpus from all entries (content + tags joined)
|
|
831
|
-
const texts = entries.map((e) => `${e.content} ${e.tags.join(' ')}`);
|
|
832
|
-
const corpus = buildCorpus(texts);
|
|
833
|
-
// Score each entry
|
|
834
|
-
const scored = [];
|
|
835
|
-
const currentPathTagsSync = extractPathTags(process.cwd());
|
|
836
|
-
const activeScopeSync = detectScope();
|
|
837
|
-
const temporalDir = detectTemporalDirection(query);
|
|
838
|
-
const temporalRangeSync = temporalDir ? computeTemporalRange(entries) : { minTime: 0, maxTime: 0 };
|
|
839
|
-
for (let i = 0; i < entries.length; i++) {
|
|
840
|
-
const bm25 = bm25Score(corpus, i, queryTerms);
|
|
841
|
-
if (bm25 <= 0)
|
|
842
|
-
continue;
|
|
843
|
-
const strength = calculateStrength(entries[i], now);
|
|
844
|
-
const recency = recencyBoost(entries[i], now);
|
|
845
|
-
// Composite: BM25 relevance * strength * recency
|
|
846
|
-
// Normalise BM25 against query term count to keep scale consistent
|
|
847
|
-
const normBm25 = queryTerms.length > 0 ? bm25 / queryTerms.length : bm25;
|
|
848
|
-
let composite = normBm25 * (0.5 + 0.5 * strength) * (0.8 + 0.2 * recency);
|
|
849
|
-
// Decision-tagged memories get a 1.2x recall boost
|
|
850
|
-
const decisionBoost = entries[i].tags.includes('decision') ? 1.2 : 1.0;
|
|
851
|
-
composite *= decisionBoost;
|
|
852
|
-
composite *= churnStaleFactor(entries[i]);
|
|
853
|
-
// Path-based boost: memories tagged with matching path segments get up to 1.3x
|
|
854
|
-
composite *= pathBoostMultiplier(entries[i].tags, currentPathTagsSync);
|
|
855
|
-
// Scope boost (sync path)
|
|
856
|
-
const scopeSignalSync = scopeMatch(entries[i].tags, activeScopeSync);
|
|
857
|
-
const scopeBoostSync = scopeSignalSync === 1 ? 1.5 : scopeSignalSync === -1 ? 0.5 : 1.0;
|
|
858
|
-
composite *= scopeBoostSync;
|
|
859
|
-
const extractionBoostSync = entries[i].tags.includes('extracted') ? 1.3 : 1.0;
|
|
860
|
-
composite *= extractionBoostSync;
|
|
861
|
-
composite *= temporalBoost(entries[i], temporalDir, temporalRangeSync);
|
|
862
|
-
const tokens = estimateTokens(entries[i].content);
|
|
863
|
-
scored.push({ entry: entries[i], score: composite, bm25, cosine: 0, tokens });
|
|
864
|
-
}
|
|
865
|
-
// Sort by composite score descending, deterministic tiebreak on ties.
|
|
866
|
-
scored.sort(compareScoredResults);
|
|
867
|
-
const seenExtractedFromSync = new Set();
|
|
868
|
-
const dedupedSync = [];
|
|
869
|
-
for (const result of scored) {
|
|
870
|
-
const entry = result.entry;
|
|
871
|
-
if (entry.extracted_from) {
|
|
872
|
-
seenExtractedFromSync.add(entry.extracted_from);
|
|
873
|
-
const sourceIdx = dedupedSync.findIndex((d) => d.entry.id === entry.extracted_from);
|
|
874
|
-
if (sourceIdx >= 0)
|
|
875
|
-
dedupedSync.splice(sourceIdx, 1);
|
|
876
|
-
dedupedSync.push(result);
|
|
877
|
-
}
|
|
878
|
-
else if (seenExtractedFromSync.has(entry.id)) {
|
|
879
|
-
continue;
|
|
880
|
-
}
|
|
881
|
-
else {
|
|
882
|
-
dedupedSync.push(result);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
// DAG drill-down: when a summary node matches, inject its children
|
|
886
|
-
const summaryIdsSync = dedupedSync
|
|
887
|
-
.filter((r) => r.entry.tags.includes('dag-summary'))
|
|
888
|
-
.map((r) => r.entry.id);
|
|
889
|
-
if (summaryIdsSync.length > 0) {
|
|
890
|
-
const childEntries = entries.filter((e) => e.dag_parent_id && summaryIdsSync.includes(e.dag_parent_id));
|
|
891
|
-
for (const child of childEntries) {
|
|
892
|
-
if (!dedupedSync.some((r) => r.entry.id === child.id)) {
|
|
893
|
-
const parentResult = dedupedSync.find((r) => r.entry.id === child.dag_parent_id);
|
|
894
|
-
const childScore = parentResult ? parentResult.score * 0.9 * churnStaleFactor(child) : 0;
|
|
895
|
-
dedupedSync.push({
|
|
896
|
-
entry: child,
|
|
897
|
-
score: childScore,
|
|
898
|
-
bm25: 0,
|
|
899
|
-
cosine: 0,
|
|
900
|
-
tokens: estimateTokens(child.content),
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
dedupedSync.sort(compareScoredResults);
|
|
905
|
-
}
|
|
906
|
-
return fitBudget(dedupedSync, budget, minResults, options.cost);
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Explain why a search result matched a query.
|
|
910
|
-
* Computes which query terms overlapped with the document and whether
|
|
911
|
-
* BM25 and/or embedding similarity contributed to the composite score.
|
|
912
|
-
*/
|
|
913
|
-
export function explainMatch(query, result) {
|
|
914
|
-
const queryTerms = new Set(tokenize(query));
|
|
915
|
-
const docTerms = new Set(tokenize(`${result.entry.content} ${result.entry.tags.join(' ')}`));
|
|
916
|
-
const matchedTerms = [];
|
|
917
|
-
for (const term of queryTerms) {
|
|
918
|
-
if (docTerms.has(term)) {
|
|
919
|
-
matchedTerms.push(term);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
const hasBm25 = result.bm25 > 0;
|
|
923
|
-
const hasEmbedding = result.cosine > 0;
|
|
924
|
-
const parts = [];
|
|
925
|
-
if (hasBm25) {
|
|
926
|
-
parts.push(`BM25: matched terms [${matchedTerms.join(', ')}]`);
|
|
927
|
-
}
|
|
928
|
-
if (hasEmbedding) {
|
|
929
|
-
parts.push(`embedding similarity: ${result.cosine.toFixed(3)}`);
|
|
930
|
-
}
|
|
931
|
-
if (parts.length === 0) {
|
|
932
|
-
parts.push('no direct term or embedding match');
|
|
933
|
-
}
|
|
934
|
-
return {
|
|
935
|
-
reason: parts.join('; '),
|
|
936
|
-
matchedTerms,
|
|
937
|
-
hasBm25,
|
|
938
|
-
hasEmbedding,
|
|
939
|
-
cosineSimilarity: result.cosine,
|
|
940
|
-
envelope: {
|
|
941
|
-
kind: result.entry.kind ?? 'distilled',
|
|
942
|
-
scope: result.entry.scope ?? null,
|
|
943
|
-
owner: result.entry.owner ?? null,
|
|
944
|
-
artifact_ref: result.entry.artifact_ref ?? null,
|
|
945
|
-
session_id: result.entry.source_session_id ?? null,
|
|
946
|
-
confidence: result.entry.confidence ?? 'observed',
|
|
947
|
-
},
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Compute text overlap ratio between two strings (Jaccard on token sets).
|
|
952
|
-
*/
|
|
953
|
-
export function textOverlap(a, b) {
|
|
954
|
-
const setA = new Set(tokenize(a));
|
|
955
|
-
const setB = new Set(tokenize(b));
|
|
956
|
-
if (setA.size === 0 && setB.size === 0)
|
|
957
|
-
return 1;
|
|
958
|
-
if (setA.size === 0 || setB.size === 0)
|
|
959
|
-
return 0;
|
|
960
|
-
let intersection = 0;
|
|
961
|
-
for (const t of setA) {
|
|
962
|
-
if (setB.has(t))
|
|
963
|
-
intersection++;
|
|
964
|
-
}
|
|
965
|
-
const union = setA.size + setB.size - intersection;
|
|
966
|
-
return intersection / union;
|
|
967
|
-
}
|
|
968
|
-
//# sourceMappingURL=search.js.map
|