@avantf/dsh-mem 0.7.0 → 0.8.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 +4 -0
- package/lib/client.js +548 -1195
- package/lib/dsh-build.json +1 -0
- package/lib/engine/graph-recall/in-memory.d.ts +93 -0
- package/lib/engine/graph-recall/index.d.ts +22 -0
- package/lib/engine/graph-recall/traverse.d.ts +119 -0
- package/lib/engine/graph-recall/types.d.ts +188 -0
- package/lib/engine/mem/config/config_files.d.ts +1 -1
- package/lib/engine/mem/config/loader.d.ts +2 -3
- package/lib/engine/mem/config/paths.d.ts +2 -13
- package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
- package/lib/engine/mem/db/dao/entities.d.ts +80 -7
- package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
- package/lib/engine/mem/db/dao/facts.d.ts +311 -56
- package/lib/engine/mem/db/dao/triples.d.ts +1 -26
- package/lib/engine/mem/db/graph.d.ts +249 -0
- package/lib/engine/mem/db/journal_notes.d.ts +35 -0
- package/lib/engine/mem/db/migrations.d.ts +9 -0
- package/lib/engine/mem/db/port.d.ts +22 -3
- package/lib/engine/mem/db/schema.d.ts +119 -0
- package/lib/engine/mem/db/tokenizer.d.ts +9 -0
- package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
- package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
- package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
- package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
- package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
- package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
- package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
- package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
- package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
- package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
- package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
- package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
- package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
- package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
- package/lib/engine/mem/defaults.d.ts +3 -4
- package/lib/engine/mem/dispatch.d.ts +7 -32
- package/lib/engine/mem/entities/extract.d.ts +44 -47
- package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
- package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
- package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
- package/lib/engine/mem/facts_io.d.ts +50 -10
- package/lib/engine/mem/index.d.ts +6 -2
- package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
- package/lib/engine/mem/interfaces/common.d.ts +11 -0
- package/lib/engine/mem/interfaces/errors.d.ts +65 -0
- package/lib/engine/mem/interfaces/index.d.ts +18 -0
- package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
- package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
- package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
- package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
- package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
- package/lib/engine/mem/interfaces/version.d.ts +34 -0
- package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
- package/lib/engine/mem/modelBootstrap.d.ts +1 -1
- package/lib/engine/mem/relations/confidence.d.ts +39 -0
- package/lib/engine/mem/relations/extract.d.ts +128 -0
- package/lib/engine/mem/relations/index.d.ts +32 -0
- package/lib/engine/mem/relations/name-quality.d.ts +78 -0
- package/lib/engine/mem/relations/normalize.d.ts +275 -0
- package/lib/engine/mem/relations/patterns.d.ts +67 -0
- package/lib/engine/mem/relations/statement.d.ts +88 -0
- package/lib/engine/mem/relations/types.d.ts +79 -0
- package/lib/engine/mem/runtime.d.ts +61 -88
- package/lib/engine/mem/storage/create.d.ts +42 -0
- package/lib/engine/mem/storage/errors.d.ts +65 -0
- package/lib/engine/mem/storage/index.d.ts +15 -0
- package/lib/engine/mem/storage/ports.d.ts +472 -0
- package/lib/engine/mem/storage/registry.d.ts +37 -0
- package/lib/engine/mem/storage/sqlite.d.ts +28 -0
- package/lib/engine/mem/store/common.d.ts +1 -1
- package/lib/engine/mem/store/entity_leg.d.ts +59 -2
- package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
- package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
- package/lib/engine/mem/store/facts_restore.d.ts +10 -2
- package/lib/engine/mem/store/floors.d.ts +4 -1
- package/lib/engine/mem/store/graph_reader.d.ts +146 -0
- package/lib/engine/mem/store/hybrid.d.ts +50 -13
- package/lib/engine/mem/store/kinds.d.ts +47 -0
- package/lib/engine/mem/store/legs.d.ts +24 -29
- package/lib/engine/mem/store/lexical.d.ts +36 -45
- package/lib/engine/mem/store/memory.d.ts +471 -83
- package/lib/engine/mem/store/recall_policy.d.ts +49 -0
- package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
- package/lib/engine/mem/store/relation_planner.d.ts +191 -0
- package/lib/engine/mem/store/secret_guard.d.ts +0 -1
- package/lib/engine/mem/store/self_query.d.ts +4 -2
- package/lib/engine/mem/store/time_filter.d.ts +121 -0
- package/lib/engine/mem/store/titles.d.ts +52 -0
- package/lib/engine/mem/store/vector_repair.d.ts +44 -54
- package/lib/engine/mem-contract/config.d.ts +44 -38
- package/lib/engine/mem-contract/text.d.ts +1 -1
- package/lib/engine/mem-contract/tools.d.ts +145 -132
- package/lib/engine/mem-contract/types.d.ts +364 -288
- package/lib/index.js +12048 -11337
- package/lib/interface-version.json +1 -1
- package/lib/types/client/index.d.ts +2 -2
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/paging.d.ts +3 -12
- package/lib/types/client/paging.d.ts.map +1 -1
- package/lib/types/client/summarize.d.ts.map +1 -1
- package/lib/types/envinit-bootstrap.d.ts +1 -1
- package/lib/types/envinit.d.ts +2 -6
- package/lib/types/envinit.d.ts.map +1 -1
- package/lib/types/hints.d.ts +28 -24
- package/lib/types/hints.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -40
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/inject.d.ts +3 -2
- package/lib/types/inject.d.ts.map +1 -1
- package/lib/types/interface_gate.d.ts +1 -1
- package/lib/types/prompt.d.ts +17 -41
- package/lib/types/prompt.d.ts.map +1 -1
- package/lib/types/provision.d.ts +3 -0
- package/lib/types/provision.d.ts.map +1 -1
- package/lib/types/remote.d.ts +14 -7
- package/lib/types/remote.d.ts.map +1 -1
- package/lib/types/tool_schema.d.ts +4 -0
- package/lib/types/tool_schema.d.ts.map +1 -1
- package/lib/types/vectorMigration.d.ts +5 -7
- package/lib/types/vectorMigration.d.ts.map +1 -1
- package/package.json +15 -14
- package/lib/engine/mem/config/domains.d.ts +0 -20
- package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
- package/lib/engine/mem/db/dao/documents.d.ts +0 -51
- package/lib/engine/mem/db/hash.d.ts +0 -2
- package/lib/engine/mem/db/knowledge.d.ts +0 -17
- package/lib/engine/mem/eval/index.d.ts +0 -4
- package/lib/engine/mem/git.d.ts +0 -73
- package/lib/engine/mem/router.d.ts +0 -51
- package/lib/engine/mem/store/doc_files.d.ts +0 -125
- package/lib/engine/mem/store/document_text.d.ts +0 -47
- package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
- package/lib/engine/mem/store/knowledge.d.ts +0 -598
- package/lib/engine/mem/store/source_picker.d.ts +0 -43
- package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
- package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
- package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
- package/lib/types/client/domains.d.ts +0 -53
- package/lib/types/client/domains.d.ts.map +0 -1
- package/lib/types/client/ingest.d.ts +0 -24
- package/lib/types/client/ingest.d.ts.map +0 -1
- package/lib/types/open.d.ts +0 -77
- package/lib/types/open.d.ts.map +0 -1
- package/lib/types/reconcile.d.ts +0 -64
- package/lib/types/reconcile.d.ts.map +0 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured relations a WRITE declares (docs/STRUCTURED_RELATIONS.md §2).
|
|
3
|
+
*
|
|
4
|
+
* The caller states `(subject, predicate, object)` — this module turns that statement into the same
|
|
5
|
+
* `RelationCandidate` shape the extraction path produces, or refuses it with a message that names the
|
|
6
|
+
* offending slot. It is deliberately a separate module from `relations/extract.ts`: nothing here
|
|
7
|
+
* GUESSES, so nothing here may silently drop. An explicit relation that cannot be written is a
|
|
8
|
+
* caller mistake (a refusal), not an extraction miss (a drop sample).
|
|
9
|
+
*
|
|
10
|
+
* The gates, in the order they run:
|
|
11
|
+
*
|
|
12
|
+
* 1. **layer.** `kind: "note"` only (§一: the graph expresses stable facts; a journal entry is an
|
|
13
|
+
* episode). The check lives HERE and runs before the write, so the CLI, the tool boundary, the
|
|
14
|
+
* Remote gateway and any embedder get the same refusal.
|
|
15
|
+
* 2. **shape.** Each slot must be non-blank, single-line and bounded, and both entity slots must
|
|
16
|
+
* pass `relations/name-quality.ts` — the same gate the extractor's edges pass. A name gate that
|
|
17
|
+
* only applied to extracted edges would let the one path a CALLER controls put a clause in the
|
|
18
|
+
* graph.
|
|
19
|
+
* 3. **self-relation** after alias resolution (§二). `老张 管理 张伟` with `老张 → 张伟` is not an
|
|
20
|
+
* edge, and for an explicit statement the caller must be told rather than silently ignored.
|
|
21
|
+
*
|
|
22
|
+
* What is deliberately NOT here: the confidence floor and the statement test
|
|
23
|
+
* (`relations/statement.ts`). Those gate a GUESS. An explicit relation carries confidence 1 and the
|
|
24
|
+
* `explicit` source, which is what `relations/confidence.ts#evaluateRelation` reads to skip them.
|
|
25
|
+
*
|
|
26
|
+
* @module store/explicit_relations
|
|
27
|
+
*/
|
|
28
|
+
import { type MemoryKind, type RelationInput } from '../../mem-contract/index.js';
|
|
29
|
+
import type { Db } from '../db/port.js';
|
|
30
|
+
import { type RelationCandidate } from '../relations/index.js';
|
|
31
|
+
/**
|
|
32
|
+
* The refusal for `journal` + `relations` (§一/§二).
|
|
33
|
+
*
|
|
34
|
+
* It names the way out, for the same reason {@link JOURNAL_NEEDS_EVENT_DATE} does: the caller has
|
|
35
|
+
* already decided the relation is worth keeping, and the only thing missing is which layer to put it
|
|
36
|
+
* in.
|
|
37
|
+
*/
|
|
38
|
+
export declare const RELATIONS_NEED_NOTE: string;
|
|
39
|
+
/**
|
|
40
|
+
* Validate + normalize the `relations` of one write, or refuse.
|
|
41
|
+
*
|
|
42
|
+
* Returns `[]` when nothing was given (an omitted or EMPTY array is "no relations", §二), so the
|
|
43
|
+
* caller can treat the result uniformly. The thrown messages are caller-facing: they name the
|
|
44
|
+
* layer, the index and the slot, because a model or an operator has to be able to fix the call.
|
|
45
|
+
*
|
|
46
|
+
* @param db - the store's connection (needed only for the read-only alias resolution of §二's
|
|
47
|
+
* self-relation rule).
|
|
48
|
+
* @param kind - the layer the revision will actually carry; the caller resolves it (for `update`
|
|
49
|
+
* that is the replaced row's kind when the request omits one).
|
|
50
|
+
* @param relations - what the caller passed, if anything.
|
|
51
|
+
*/
|
|
52
|
+
export declare function explicitRelationTriples(db: Db, kind: MemoryKind, relations: readonly RelationInput[] | undefined): RelationCandidate[];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* RESTORE IS NOT IMPORT. `import` / `merge` (see `facts_io.ts`) are CONTENT-IDENTITY UNIONS: new
|
|
6
6
|
* rows get new ids, only ACTIVE facts travel, and every value the document carries beyond
|
|
7
|
-
*
|
|
7
|
+
* kind/ttl/sources/validity is counted as dropped. Restore is the opposite promise — "put this
|
|
8
8
|
* library back exactly as it was" — which is only answerable if the target is EMPTY:
|
|
9
9
|
*
|
|
10
10
|
* - `facts` is written DIRECTLY, one transaction, keeping `fact_id`, status, timestamps,
|
|
@@ -41,6 +41,14 @@
|
|
|
41
41
|
* Zero would make every restored fact decay by the host's whole clock in one settlement; the current
|
|
42
42
|
* clock is the "no decay has happened yet" baseline, which is what a fresh restore means.
|
|
43
43
|
*
|
|
44
|
+
* V5 DECLARED RELATIONS ARE RESTORED, AS EDGES. A note's `relations` (the relations a caller stated,
|
|
45
|
+
* `source: 'explicit'`) are written into `edges` inside the same transaction as the fact. They are
|
|
46
|
+
* the one part of the graph the entity sweep cannot re-derive (that pass re-creates extracted edges
|
|
47
|
+
* and PRESERVES explicit ones), so treating them like the rest of the derived state would lose them.
|
|
48
|
+
* The field is counted in `metadata_missing` for notes only: a journal refuses relations by contract,
|
|
49
|
+
* so an absent key on a journal row states the truth instead of a loss. A journal row that DOES carry
|
|
50
|
+
* them is refused outright — such a row cannot be represented in this schema.
|
|
51
|
+
*
|
|
44
52
|
* @module store/facts_restore
|
|
45
53
|
*/
|
|
46
54
|
import type { FactsExportDocument } from '../facts_io.js';
|
|
@@ -56,7 +64,7 @@ export declare const RESTORE_VECTOR_HINT: string;
|
|
|
56
64
|
* rather than trust). A field counts as missing when the document does not carry the key at all —
|
|
57
65
|
* `null` is a value the document states ("unknown"), not an absence.
|
|
58
66
|
*/
|
|
59
|
-
export declare const RESTORE_METADATA_FIELDS: readonly ["
|
|
67
|
+
export declare const RESTORE_METADATA_FIELDS: readonly ["kind", "title", "created_at", "updated_at", "trust_score", "pinned", "pinned_at", "assert_count", "helpful_count", "last_reinforced_at", "ttl_days", "valid_from", "valid_to", "supersedes_source_id", "archive", "sources", "settle_clock", "bonus_count", "bonus_window_at", "retrieval_count", "last_retrieved_at", "mirror_source", "mirror_target", "entities_version", "conflict_checked", "relations"];
|
|
60
68
|
export type RestoreMetadataField = (typeof RESTORE_METADATA_FIELDS)[number];
|
|
61
69
|
/** Per-field count of facts whose document did not carry that field. */
|
|
62
70
|
export type RestoreMetadataMissing = Record<RestoreMetadataField, number>;
|
|
@@ -99,7 +99,10 @@ export type FloorLeg = 'semantic' | 'fts' | 'jaccard';
|
|
|
99
99
|
* that cannot produce terms is not graded at all, and one that produces a single term (every
|
|
100
100
|
* 3-char CJK query, and every 2-char one through the substring fallback) is already graded
|
|
101
101
|
* against that one term by the reachability clamp.
|
|
102
|
-
* - jaccard **0.15** (positive — the previous `0` was the ungated hole this closes).
|
|
102
|
+
* - jaccard **0.15** (positive — the previous `0` was the ungated hole this closes). This is the
|
|
103
|
+
* LOWER BOUND of the entity leg's evidence bar: the leg also applies a per-candidate anchor-rarity
|
|
104
|
+
* bar in the same metric unit (`entityEvidenceFloor`, 2026-10-21), so "relaxed" cannot re-admit a
|
|
105
|
+
* generic anchor's wide-fact tail — the structural relation is not a relevance threshold. The metric was
|
|
103
106
|
* recalibrated on 2026-10-04 (`store/entity_leg.ts`): the leg still scores a Jaccard ratio, but
|
|
104
107
|
* over the query's ANCHOR entities and with the fact's extra-entity term SATURATED at
|
|
105
108
|
* `ENTITY_UNION_CAP`. 0.15 on that unit means "shares at least one discriminative entity" for any
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory adapter for the shared `@avantf/graph-recall` port
|
|
3
|
+
* (docs/GRAPH_RECALL_LIBRARY.md §6, Phase 3b).
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. The library owns the traversal algorithms (one hop, reverse, two hops, AND,
|
|
6
|
+
* neighbours) and knows nothing about SQLite; memory owns the storage and therefore must supply a
|
|
7
|
+
* {@link GraphReader}. That is this class and nothing more: it maps `edges` + `entities` +
|
|
8
|
+
* `predicates` rows onto `GraphEdge` / `GraphNode` and owns EVERY filtering decision the library
|
|
9
|
+
* refuses to guess.
|
|
10
|
+
*
|
|
11
|
+
* THE DIVISION OF LABOUR, spelled out because it is the whole point of Phase 3:
|
|
12
|
+
*
|
|
13
|
+
* - **The reader filters.** Only `facts.status = 'active'` rows are edges of the live graph; a
|
|
14
|
+
* `kind`, `source` (`fact_sources.ref`) or `time` filter is applied inside the SQL, before any
|
|
15
|
+
* `LIMIT`, exactly like the retrieval legs do it (`store/memory.ts#SearchInput`). A superseded
|
|
16
|
+
* revision's edges are deleted by the write path, so "archived" here means a row whose status
|
|
17
|
+
* moved after the fact was written — the contradiction/supersede paths — and it must not be
|
|
18
|
+
* walkable.
|
|
19
|
+
* - **The reader normalizes names.** `resolveNode` follows `relations/normalize.ts`'s order
|
|
20
|
+
* (explicit alias, then exact name, then — last, so a name never shadows an id — the numeric
|
|
21
|
+
* `entity_id`); `resolvePredicate` follows the same alias-then-name order. Walkers call
|
|
22
|
+
* `resolvePredicate` before reading, so an alias (`负责`) and its canonical (`管理`) are one hop
|
|
23
|
+
* predicate, never two.
|
|
24
|
+
* - **Direction is the STORED one.** `outgoing` reads `subject_id = ?`, `incoming` reads
|
|
25
|
+
* `object_id = ?`, and the adapter never flips endpoints: a predicate registered as `inverse` was
|
|
26
|
+
* already stored in its canonical orientation by the write path (`db/graph.ts`), which is what
|
|
27
|
+
* lets every walker read edges left to right. The ONE exception is a SYMMETRIC relation (`合作`):
|
|
28
|
+
* it is stored once, in the order the sentence used, and a predicate-filtered read anchors on
|
|
29
|
+
* either endpoint, so both ends of the relation answer (see `read`).
|
|
30
|
+
* - **Evidence is the fact.** `GraphEdge.factId` is `edges.fact_id`, so a `RelationPath.evidence`
|
|
31
|
+
* points at the row a caller can open (`mem_admin detail`), not at an edge id only this adapter
|
|
32
|
+
* understands.
|
|
33
|
+
* - **The vocabulary is the adapter's too.** `entitiesIn` / `predicatesIn` (Phase 4) are the
|
|
34
|
+
* `RelationVocabulary` port the planner reads: which known surfaces occur where in a query. Those
|
|
35
|
+
* lookups belong here because they are the same normalization (`entity_aliases` first, then
|
|
36
|
+
* `entities.name`; alias-then-canonical for predicates) the walks use.
|
|
37
|
+
*
|
|
38
|
+
* WHAT IS DELIBERATELY ABSENT: no query planner (that is `store/relation_planner.ts`), no answer
|
|
39
|
+
* generation, no new action, and no change to `titles`. `MemoryStore` builds one of these per search
|
|
40
|
+
* for the relation stage — the class stays an adapter with no policy of its own — and it is still not
|
|
41
|
+
* exported from the engine's front door, the same status `db/graph.ts` has: reachable by module path,
|
|
42
|
+
* not a published promise.
|
|
43
|
+
*
|
|
44
|
+
* @module store/graph_reader
|
|
45
|
+
*/
|
|
46
|
+
import type { GraphEdge, GraphNode, GraphReader } from '../../graph-recall/index.js';
|
|
47
|
+
import type { MemoryKind, TimeFilter } from '../../mem-contract/index.js';
|
|
48
|
+
import type { Db } from '../db/port.js';
|
|
49
|
+
import { type SurfaceMatch } from './relation_planner.js';
|
|
50
|
+
/**
|
|
51
|
+
* Hard ceiling on one read's `LIMIT`.
|
|
52
|
+
*
|
|
53
|
+
* The library over-fetches (`limit × 5`, capped at its own `MAX_SCAN = 500`) and never calls a read
|
|
54
|
+
* without a limit, so this is the bound an omitted `limit` falls back to rather than a second policy:
|
|
55
|
+
* an unbounded `SELECT * FROM edges WHERE subject_id = ?` on a hub entity would be a table scan, and
|
|
56
|
+
* "at most `limit`" is the contract's promise, not "all of them".
|
|
57
|
+
*/
|
|
58
|
+
export declare const MAX_GRAPH_EDGE_READ = 500;
|
|
59
|
+
/**
|
|
60
|
+
* Which facts a reader may walk.
|
|
61
|
+
*
|
|
62
|
+
* Every field is ANDed with the others and with `status = 'active'`; an omitted field filters
|
|
63
|
+
* nothing. An EMPTY list is not "no restriction" — a kind/source allowlist that names nothing
|
|
64
|
+
* permits nothing, and silently widening it to the whole corpus is the failure mode a filter exists
|
|
65
|
+
* to prevent.
|
|
66
|
+
*/
|
|
67
|
+
export interface MemoryGraphFilter {
|
|
68
|
+
/** `facts.kind` values to keep (the two memory layers, see `store/kinds.ts`). */
|
|
69
|
+
kinds?: readonly MemoryKind[];
|
|
70
|
+
/**
|
|
71
|
+
* `fact_sources.ref` values to keep — the same `source` axis `mem_recall.search` exposes (P-08).
|
|
72
|
+
*
|
|
73
|
+
* Note the write-side spelling: `mem_remember source_ref: 'session:abc'` records kind `session`,
|
|
74
|
+
* ref `abc` (`dao/fact_sources.ts#classifySourceRef`), and `ref` is what this filter compares —
|
|
75
|
+
* the same value `mem_recall`'s `source=` matches.
|
|
76
|
+
*/
|
|
77
|
+
sources?: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* The caller's explicit time filter, on `facts` (`store/time_filter.ts`'s normalizer, i.e. the very
|
|
80
|
+
* same grammar and axes `mem_recall` uses: `created` / `updated` / `event`).
|
|
81
|
+
*/
|
|
82
|
+
time?: TimeFilter;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A read-only {@link GraphReader} over a memory database.
|
|
86
|
+
*
|
|
87
|
+
* Construct one per query — it holds a filter and a connection, no mutable state, so concurrent
|
|
88
|
+
* readers on one connection cannot interfere — and hand it to the `@avantf/graph-recall` operators:
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* const reader = new MemoryGraphReader(db, { kinds: ['note'] })
|
|
92
|
+
* const zhang = reader.resolveNode('张伟')
|
|
93
|
+
* if (zhang !== null) {
|
|
94
|
+
* const { paths } = twoHop(reader, zhang, '管理')
|
|
95
|
+
* for (const path of paths) console.log(path.evidence) // fact ids
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare class MemoryGraphReader implements GraphReader {
|
|
100
|
+
private readonly db;
|
|
101
|
+
/** The normalized time range, parsed ONCE in the constructor so a bad `time` fails fast. */
|
|
102
|
+
private readonly timeRange;
|
|
103
|
+
/** `undefined` = the axis is not filtered; `[]` = an allowlist that names nothing (matches nothing). */
|
|
104
|
+
private readonly kinds;
|
|
105
|
+
private readonly sources;
|
|
106
|
+
constructor(db: Db, filter?: MemoryGraphFilter);
|
|
107
|
+
resolveNode(ref: string): GraphNode | null;
|
|
108
|
+
resolvePredicate(ref: string): string | null;
|
|
109
|
+
/**
|
|
110
|
+
* The entity surfaces occurring in `text` — the planner's vocabulary port (`store/relation_planner.ts`).
|
|
111
|
+
*
|
|
112
|
+
* Only entities the graph can actually walk are reported: an entity with no `edges` row can never
|
|
113
|
+
* answer a relation question, so including it would let a plan form and then come back empty (a
|
|
114
|
+
* fuzzy fallback with extra work), and scanning the whole `entities` table on every search would
|
|
115
|
+
* pay for names the relation stage can never use. The alias rows are narrowed the same way.
|
|
116
|
+
*
|
|
117
|
+
* Filtering (kind/source/time) is deliberately NOT applied to this lookup: the vocabulary answers
|
|
118
|
+
* "is this name an entity at all", while the paths are filtered by the edge SQL. A name whose only
|
|
119
|
+
* edges are outside the caller's window still forms a plan — and then yields no path, which is the
|
|
120
|
+
* documented fallback to fuzzy.
|
|
121
|
+
*/
|
|
122
|
+
entitiesIn(text: string): SurfaceMatch[];
|
|
123
|
+
/** The predicate surfaces occurring in `text` (canonical names and aliases) — same port as above. */
|
|
124
|
+
predicatesIn(text: string): SurfaceMatch[];
|
|
125
|
+
outgoing(node: GraphNode, predicate?: string, limit?: number): GraphEdge[];
|
|
126
|
+
incoming(node: GraphNode, predicate?: string, limit?: number): GraphEdge[];
|
|
127
|
+
edgesForFact(factId: string | number): GraphEdge[];
|
|
128
|
+
/** One alias-or-canonical-name lookup; `undefined` when the predicate is not in the vocabulary. */
|
|
129
|
+
private predicateRow;
|
|
130
|
+
/**
|
|
131
|
+
* Shared read behind {@link outgoing} / {@link incoming} / {@link edgesForFact}.
|
|
132
|
+
*
|
|
133
|
+
* `anchorColumn` is the ONE difference between the three reads (`subject_id`, `object_id`,
|
|
134
|
+
* `fact_id`), which is why they cannot drift apart in their filters: active status, kind, source,
|
|
135
|
+
* time and the page bound are appended in exactly one place.
|
|
136
|
+
*/
|
|
137
|
+
private read;
|
|
138
|
+
/**
|
|
139
|
+
* Build one bounded, filtered edge read.
|
|
140
|
+
*
|
|
141
|
+
* Bind order is the SQL's own: kind list, source list, time bounds, anchor, predicate, limit.
|
|
142
|
+
* `ORDER BY e.edge_id` is insertion order — stable across calls, which is what the port asks for
|
|
143
|
+
* ("the library sorts by confidence/hops/recency itself").
|
|
144
|
+
*/
|
|
145
|
+
private edgeQuery;
|
|
146
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `MemoryStore.search` and `KnowledgeStore.search` used to carry parallel copies of this flow
|
|
5
5
|
* (~80 lines each: limit normalization → weights → over-fetch → leg cap → fuse → live filter →
|
|
6
6
|
* slice → output budget → health event). The legs themselves are legitimately store-specific —
|
|
7
|
-
* different tables, different filters (`
|
|
7
|
+
* different tables, different filters (`kind` vs `domain`/`source`), and only memory has the
|
|
8
8
|
* HRR probe — but the ORCHESTRATION is one algorithm, and two copies of it drifted:
|
|
9
9
|
*
|
|
10
10
|
* - the `NaN` limit guard existed only in memory, so a non-finite limit reached knowledge's SQL
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { type Config, type FloorProfile, type RecallResult, type RetrievalFloorDrops, type RetrievalFloors } from '../../mem-contract/index.js';
|
|
27
27
|
import { type BudgetInput, type Hit, type SemanticBackend } from '../../mem-retrieval/index.js';
|
|
28
|
+
import type { Awaitable } from '../interfaces/common.js';
|
|
28
29
|
import { type FloorLeg } from './floors.js';
|
|
29
30
|
/**
|
|
30
31
|
* A retrieval input the CALLER got wrong, as opposed to a backend that failed at runtime.
|
|
@@ -107,8 +108,8 @@ export interface HybridContext {
|
|
|
107
108
|
}
|
|
108
109
|
/** What a store supplies. Generic in `H`, the caller-facing hit shape. */
|
|
109
110
|
export interface HybridDeps<H> {
|
|
110
|
-
/** Which health-event
|
|
111
|
-
kind: 'memory'
|
|
111
|
+
/** Which health-event bucket this search records (`retrieval.by_kind`). */
|
|
112
|
+
kind: 'memory';
|
|
112
113
|
config: Config;
|
|
113
114
|
semantic: SemanticBackend;
|
|
114
115
|
/**
|
|
@@ -118,10 +119,10 @@ export interface HybridDeps<H> {
|
|
|
118
119
|
* body, before the array is returned.
|
|
119
120
|
*/
|
|
120
121
|
legs(ctx: HybridContext): Promise<readonly (HybridLeg | Promise<HybridLeg>)[]>;
|
|
121
|
-
/** Text for the fused ids: the live filter and the hit bodies. */
|
|
122
|
-
texts(ids: number[]): Map<number, string
|
|
122
|
+
/** Text for the fused ids: the live filter and the hit bodies (a repository read — DB-2). */
|
|
123
|
+
texts(ids: number[]): Awaitable<Map<number, string>>;
|
|
123
124
|
/** Build the caller-facing hits from the ranked, live, already-sliced ids. */
|
|
124
|
-
hits(ranked: readonly Hit[], texts: Map<number, string>): H[]
|
|
125
|
+
hits(ranked: readonly Hit[], texts: Map<number, string>): Awaitable<H[]>;
|
|
125
126
|
/**
|
|
126
127
|
* Called with the hits the caller ACTUALLY receives — after the output budget, not before it.
|
|
127
128
|
*
|
|
@@ -130,7 +131,7 @@ export interface HybridDeps<H> {
|
|
|
130
131
|
* dropped, i.e. rewarding facts the caller never saw; the cross-store path had already learned
|
|
131
132
|
* that lesson (R5/R21) and the single-store path had not.
|
|
132
133
|
*/
|
|
133
|
-
onReturn?(kept: readonly Budgeted<H>[]): void
|
|
134
|
+
onReturn?(kept: readonly Budgeted<H>[]): Awaitable<void>;
|
|
134
135
|
}
|
|
135
136
|
/** One search request, before resolution. */
|
|
136
137
|
export interface HybridPlan {
|
|
@@ -168,6 +169,20 @@ export interface HybridPlan {
|
|
|
168
169
|
* too (relaxing a leg that dropped nothing admits no candidate; it only misreports `floors`).
|
|
169
170
|
*/
|
|
170
171
|
relaxLegs?: readonly FloorLeg[];
|
|
172
|
+
/**
|
|
173
|
+
* Overrides `retriever.min_score` for this search (`0` = off). The UNIFIED cross-store entry
|
|
174
|
+
* (`runtime.retrieve`) passes `0` here and applies the score floor ONCE, after the merged pool has
|
|
175
|
+
* been normalized, because a per-store floor runs on a different scale in each store (DESIGN §7).
|
|
176
|
+
* A direct store search leaves this absent and keeps the configured value.
|
|
177
|
+
*/
|
|
178
|
+
minScore?: number;
|
|
179
|
+
/**
|
|
180
|
+
* READ-ONLY probe mode (`AvantfRuntime.wouldFind`, DESIGN §12): no model warm, no leg-capped
|
|
181
|
+
* counters, no retrieval event and no `onReturn` reinforcement. The legs themselves are the same
|
|
182
|
+
* ones a user query runs, because the probe's answer must be exactly "would retrieval return a
|
|
183
|
+
* hit?"; only the SIDE EFFECTS are suppressed.
|
|
184
|
+
*/
|
|
185
|
+
probe?: boolean;
|
|
171
186
|
/**
|
|
172
187
|
* Emit a health event for this search (default true). The cross-store router sets it false: it
|
|
173
188
|
* fuses both stores into ONE user-facing query and records a single `kind: 'cross'` event, so
|
|
@@ -196,6 +211,24 @@ export interface HybridResult<H> {
|
|
|
196
211
|
floors: RetrievalFloors;
|
|
197
212
|
/** How many candidates each leg dropped below its floor. */
|
|
198
213
|
dropped_by_floor: RetrievalFloorDrops;
|
|
214
|
+
/**
|
|
215
|
+
* Candidates the FUSED-score floor (`retriever.min_score`) removed after the live filter.
|
|
216
|
+
*
|
|
217
|
+
* Deliberately a scalar, not a `RetrievalFloorDrops` member: a score drop happens AFTER all legs
|
|
218
|
+
* have been fused, so there is no leg to attribute it to. Folding it into the per-leg structure
|
|
219
|
+
* would also make the retry rule and the panel's "the floors removed N" claim describe a rule that
|
|
220
|
+
* did not remove the candidate.
|
|
221
|
+
*
|
|
222
|
+
* Absent when nothing was dropped (including whenever `min_score` is off). The count covers every
|
|
223
|
+
* live candidate below the threshold, including ones the `limit` slice would have discarded anyway,
|
|
224
|
+
* because the threshold is applied before the slice. It is the CHOSEN pass's count: when
|
|
225
|
+
* {@link HybridResult.relaxed} is true it describes the relaxed pass, like `floors`.
|
|
226
|
+
*
|
|
227
|
+
* The UNIFIED entry (`runtime.retrieve`) passes `minScore: 0` and applies the floor once, after the
|
|
228
|
+
* merged-pool normalization (`router.crossQuery`), so this field is absent on that path; the
|
|
229
|
+
* router's own `dropped_by_score` is the cross-store count (DESIGN §7).
|
|
230
|
+
*/
|
|
231
|
+
dropped_by_score?: number;
|
|
199
232
|
/**
|
|
200
233
|
* The strict pass returned nothing while having dropped candidates, and ONE relaxed pass supplied
|
|
201
234
|
* these hits — `floors` are that pass's values. An explicit `floors: 'loose'` request does NOT set
|
|
@@ -233,11 +266,15 @@ export declare function hybridSearch<H extends BudgetInput>(deps: HybridDeps<H>,
|
|
|
233
266
|
/**
|
|
234
267
|
* The search-entry fields `MemoryStore.search` and `KnowledgeStore.search` hand to the orchestrator.
|
|
235
268
|
* Both stores' option types (`SearchInput` / `KnowledgeSearchOptions`) satisfy this structurally;
|
|
236
|
-
* everything else on them (`
|
|
269
|
+
* everything else on them (`kind` / `source` / `domain` / `includeHrr` / `track` / `onResult`) is
|
|
237
270
|
* leg- or domain-specific and stays at the store.
|
|
238
271
|
*/
|
|
239
272
|
export interface HybridSearchEntryFields {
|
|
240
273
|
limit?: number;
|
|
274
|
+
/** See {@link HybridPlan.minScore}. `0` = the caller applies the score floor itself. */
|
|
275
|
+
minScore?: number;
|
|
276
|
+
/** See {@link HybridPlan.probe}: read-only, no side effects. */
|
|
277
|
+
probe?: boolean;
|
|
241
278
|
/**
|
|
242
279
|
* Knowledge only: overrides the config-derived pool size. Memory's `SearchInput` has no such field
|
|
243
280
|
* and leaves the plan key absent (see {@link runHybridSearch}).
|
|
@@ -274,9 +311,9 @@ export declare function runHybridSearch<H extends BudgetInput>(deps: HybridDeps<
|
|
|
274
311
|
*/
|
|
275
312
|
export declare function hybridPlanOf(query: string, fields: HybridSearchEntryFields | undefined): HybridPlan;
|
|
276
313
|
/**
|
|
277
|
-
* Build a store's {@link HybridDeps} — the ONE construction site for the
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* field
|
|
314
|
+
* Build a store's {@link HybridDeps} — the ONE construction site for the fields every caller shares
|
|
315
|
+
* (`kind` / `config` / `semantic` / `legs` / `texts` / `hits` / `onReturn`). The per-caller half is
|
|
316
|
+
* `parts`; a store built on these legs passes its own `kind` and parts, so a new required
|
|
317
|
+
* `HybridDeps` field does not have to be remembered in more than one place.
|
|
281
318
|
*/
|
|
282
|
-
export declare function makeHybridDeps<H>(kind: 'memory'
|
|
319
|
+
export declare function makeHybridDeps<H>(kind: 'memory', config: Config, semantic: SemanticBackend, parts: Pick<HybridDeps<H>, 'legs' | 'texts' | 'hits' | 'onReturn'>): HybridDeps<H>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory LAYERS at the WRITE boundary (JOURNAL_NOTES_REDESIGN N1/N6/N7).
|
|
3
|
+
*
|
|
4
|
+
* `kind` replaced the eight-value `category` vocabulary: a fact is either a `journal` entry (what was
|
|
5
|
+
* done / talked about / thought about, anchored by an event date) or a `note` (a stable fact,
|
|
6
|
+
* preference, decision or pitfall). Three layers agree, each with a distinct job:
|
|
7
|
+
*
|
|
8
|
+
* 1. the contract's `z.enum(MEMORY_KINDS)` — the MODEL cannot even phrase an unknown value;
|
|
9
|
+
* 2. {@link kindOrThrow} — the ENGINE refuses one, so the CLI, the UI, an import and an embedder
|
|
10
|
+
* (none of which pass through the tool schema) get the same answer;
|
|
11
|
+
* 3. `facts.kind`'s CHECK constraint (the base DDL) — the DATABASE refuses one, which is what makes
|
|
12
|
+
* the rule hold for a writer this build does not control (an older process sharing the file).
|
|
13
|
+
*
|
|
14
|
+
* There is no lookup table any more (the old `categories` table is dropped by migration 13): two
|
|
15
|
+
* values do not need to be stored as data, and the schema test would have to keep them in step.
|
|
16
|
+
*
|
|
17
|
+
* @module store/kinds
|
|
18
|
+
*/
|
|
19
|
+
import { type MemoryKind } from '../../mem-contract/index.js';
|
|
20
|
+
/** The EPISODIC layer: one row per episode, and it cannot exist without its event time. */
|
|
21
|
+
export declare const JOURNAL_KIND: MemoryKind;
|
|
22
|
+
/** The SEMANTIC layer — also what an omitted `kind` means on a new row (CLI / import / base). */
|
|
23
|
+
export declare const NOTE_KIND: MemoryKind;
|
|
24
|
+
/** Whether `value` is one of the two layers (the engine's own guard). */
|
|
25
|
+
export declare function isMemoryKind(value: string): value is MemoryKind;
|
|
26
|
+
/**
|
|
27
|
+
* Validate a caller's `kind`, or refuse with the allowed set.
|
|
28
|
+
*
|
|
29
|
+
* Whitespace/case are NOT "helpfully" accepted: `kind` is an identity (the column's CHECK compares it
|
|
30
|
+
* verbatim), and silently folding `Journal` into `journal` would make the stored value depend on which
|
|
31
|
+
* layer normalized it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function kindOrThrow(value: string): MemoryKind;
|
|
34
|
+
/**
|
|
35
|
+
* Map ANY historical value onto the two layers. Used where the value is not a fresh caller choice but
|
|
36
|
+
* data written by an older build (a snapshot restore, an old export, a pre-migration database), so
|
|
37
|
+
* refusing it would refuse the whole file over a label.
|
|
38
|
+
*
|
|
39
|
+
* `worklog` (the one episodic value in the published 16-value set, and in the eight-value experiment)
|
|
40
|
+
* is `journal`; every other value is `note` — the old vocabularies were topic taxonomies, and "the
|
|
41
|
+
* topic of a fact" says nothing about whether it is an episode.
|
|
42
|
+
*/
|
|
43
|
+
export declare function mapLegacyKind(value: unknown): MemoryKind;
|
|
44
|
+
/** `true` when this layer cannot be written without an event date (N6). */
|
|
45
|
+
export declare function requiresEventDate(kind: string): boolean;
|
|
46
|
+
/** The refusal text for a `journal` write without `event_date` — one sentence, with the way out. */
|
|
47
|
+
export declare const JOURNAL_NEEDS_EVENT_DATE: string;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { type FloorLeg } from './floors.js';
|
|
42
42
|
import { type HybridContext, type HybridLeg } from './hybrid.js';
|
|
43
|
-
import {
|
|
43
|
+
import type { Awaitable } from '../interfaces/common.js';
|
|
44
44
|
/**
|
|
45
45
|
* The legs {@link LegRunner.scored} may wrap: those whose raw score has an absolute scale a floor
|
|
46
46
|
* can cut. `semantic` is excluded on purpose — it shares `applyScoreFloor` but must not carry the
|
|
@@ -94,44 +94,39 @@ export declare function makeLegRunner(ctx: Pick<HybridContext, 'legCap'>): LegRu
|
|
|
94
94
|
/** The per-store pieces of the FTS leg — see {@link ftsLeg}. `Scope` is opaque to the flow. */
|
|
95
95
|
export interface FtsLegOptions<Scope> {
|
|
96
96
|
query: string;
|
|
97
|
-
/** The per-leg row cap (the store's `legCap`), handed to whichever
|
|
97
|
+
/** The per-leg row cap (the store's `legCap`), handed to whichever backend statement runs. */
|
|
98
98
|
cap: number;
|
|
99
|
-
/** The store's scope shape (`{
|
|
99
|
+
/** The store's scope shape (`{kind, source}` / `{domain, source}`), passed straight through. */
|
|
100
100
|
scope: Scope;
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
/**
|
|
102
|
+
* The store's lexical search, handed the CALLER'S QUERY.
|
|
103
|
+
*
|
|
104
|
+
* The backend decides how to express it (a full-text `MATCH`, or the short-query substring
|
|
105
|
+
* fallback when the index cannot express the text at all) and normalizes its own rank convention
|
|
106
|
+
* so `rank` is always HIGHER IS BETTER. That is the DB-3 boundary: neither the tokenizer nor
|
|
107
|
+
* bm25's sign crosses into the store.
|
|
108
|
+
*/
|
|
109
|
+
search: (query: string, scope: Scope, limit: number) => Awaitable<readonly {
|
|
110
110
|
id: number;
|
|
111
111
|
rank: number;
|
|
112
|
-
}[]
|
|
112
|
+
}[]>;
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
115
|
* The FTS leg, shared by `MemoryStore.ftsPath` and `KnowledgeStore.ftsPath` (§4.6.1).
|
|
116
116
|
*
|
|
117
|
-
* The two were line-for-line the same flow —
|
|
118
|
-
*
|
|
119
|
-
*
|
|
117
|
+
* The two were line-for-line the same flow — build the index expression, run it, map hits to scores
|
|
118
|
+
* — differing only in the DAO and the order its scope parameters bind. What stays here is the shape
|
|
119
|
+
* both flows share: every hit becomes a score, and the leg is a `Map` in the backend's own order.
|
|
120
120
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* - **an empty term list returns the empty map** (`substringTerms` yields none for a query with no
|
|
126
|
-
* 3-char run) — the leg is empty by construction, not by a query that matched nothing.
|
|
121
|
+
* THE SHORT-QUERY FALLBACK moved DOWN into the backend in DB-3. `buildFtsQuery` returning `null`
|
|
122
|
+
* (a 2-char CJK query the trigram vocabulary cannot express) used to leave this leg empty by
|
|
123
|
+
* construction; the SQLite adapter now answers that shape with the bounded substring predicate
|
|
124
|
+
* itself, on the same "higher is better" scale, so the store has one call instead of a branch.
|
|
127
125
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* leave this leg empty by construction — the 2-char CJK shape. `substringTerms` is the one finer
|
|
131
|
-
* predicate the FTS5 trigram TABLE still answers (`LIKE '%…%'`), bounded by `cap`; the accuracy guard
|
|
132
|
-
* is the per-row term floor in `applyTermFloor`, which requires the row to CONTAIN the run.
|
|
126
|
+
* A backend that matches nothing returns an empty map — the leg is empty by construction, not by a
|
|
127
|
+
* query that matched nothing.
|
|
133
128
|
*/
|
|
134
|
-
export declare function ftsLeg<Scope>(opts: FtsLegOptions<Scope>): Map<number, number
|
|
129
|
+
export declare function ftsLeg<Scope>(opts: FtsLegOptions<Scope>): Promise<Map<number, number>>;
|
|
135
130
|
/** The per-store pieces of the semantic leg — see {@link semanticLeg}. */
|
|
136
131
|
export interface SemanticLegOptions {
|
|
137
132
|
query: string;
|
|
@@ -155,7 +150,7 @@ export interface SemanticLegOptions {
|
|
|
155
150
|
* short-circuit (no lookup runs at all). A returned set is the allow-list; the flow still maps the
|
|
156
151
|
* pool in ITS order, so ids/scores/insertion order are the vstore's.
|
|
157
152
|
*/
|
|
158
|
-
filterTopk: (ids: readonly number[]) => ReadonlySet<number> | undefined
|
|
153
|
+
filterTopk: (ids: readonly number[]) => Awaitable<ReadonlySet<number> | undefined>;
|
|
159
154
|
}
|
|
160
155
|
/**
|
|
161
156
|
* The semantic leg, shared by `MemoryStore.semanticPath` and `KnowledgeStore.semanticPath` (§4.6.2).
|