@fgv/ts-agent-memory 5.1.0-37 → 5.1.0-39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/.rush/temp/{6dfc331ddf74dc2db0d4483d0c96ea971f351c83.tar.log → b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log} +116 -2
  2. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +4 -4
  3. package/.rush/temp/operation/build/all.log +4 -4
  4. package/.rush/temp/operation/build/log-chunks.jsonl +4 -4
  5. package/.rush/temp/operation/build/state.json +1 -1
  6. package/.rush/temp/shrinkwrap-deps.json +222 -221
  7. package/config/jest.config.json +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/packlets/ingest/cycleGuard.js +111 -0
  11. package/dist/packlets/ingest/cycleGuard.js.map +1 -0
  12. package/dist/packlets/ingest/hostStages.js +6 -0
  13. package/dist/packlets/ingest/hostStages.js.map +1 -0
  14. package/dist/packlets/ingest/index.js +9 -0
  15. package/dist/packlets/ingest/index.js.map +1 -0
  16. package/dist/packlets/ingest/model.js +6 -0
  17. package/dist/packlets/ingest/model.js.map +1 -0
  18. package/dist/packlets/ingest/orchestrator.js +438 -0
  19. package/dist/packlets/ingest/orchestrator.js.map +1 -0
  20. package/dist/packlets/retrieve/index.js +1 -0
  21. package/dist/packlets/retrieve/index.js.map +1 -1
  22. package/dist/packlets/retrieve/temporalRetrievers.js +172 -0
  23. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -0
  24. package/dist/packlets/store/fileTreeMemoryStore.js +323 -41
  25. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  26. package/dist/packlets/tools/index.js +6 -0
  27. package/dist/packlets/tools/index.js.map +1 -0
  28. package/dist/packlets/tools/memoryTools.js +337 -0
  29. package/dist/packlets/tools/memoryTools.js.map +1 -0
  30. package/dist/packlets/types/identityCodec.js +115 -0
  31. package/dist/packlets/types/identityCodec.js.map +1 -1
  32. package/dist/packlets/types/index.js +1 -0
  33. package/dist/packlets/types/index.js.map +1 -1
  34. package/dist/packlets/types/temporal.js +85 -0
  35. package/dist/packlets/types/temporal.js.map +1 -0
  36. package/dist/packlets/types/writePolicy.js +96 -0
  37. package/dist/packlets/types/writePolicy.js.map +1 -1
  38. package/dist/test/unit/converters/antagonistRoundTrip.test.js +95 -0
  39. package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
  40. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +273 -0
  41. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
  42. package/dist/test/unit/ingest/cycleGuard.test.js +54 -0
  43. package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -0
  44. package/dist/test/unit/ingest/orchestrator.test.js +913 -0
  45. package/dist/test/unit/ingest/orchestrator.test.js.map +1 -0
  46. package/dist/test/unit/retrieve/temporalRetrievers.test.js +182 -0
  47. package/dist/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
  48. package/dist/test/unit/store/antagonistTemporalBoundary.test.js +120 -0
  49. package/dist/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
  50. package/dist/test/unit/store/fileTreeMemoryStore.test.js +74 -7
  51. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  52. package/dist/test/unit/store/temporalStore.test.js +398 -0
  53. package/dist/test/unit/store/temporalStore.test.js.map +1 -0
  54. package/dist/test/unit/tools/memoryTools.test.js +572 -0
  55. package/dist/test/unit/tools/memoryTools.test.js.map +1 -0
  56. package/dist/test/unit/types/temporalCodec.test.js +203 -0
  57. package/dist/test/unit/types/temporalCodec.test.js.map +1 -0
  58. package/dist/test/unit/types/temporalPolicy.test.js +62 -0
  59. package/dist/test/unit/types/temporalPolicy.test.js.map +1 -0
  60. package/dist/ts-agent-memory.d.ts +1042 -10
  61. package/dist/tsdoc-metadata.json +1 -1
  62. package/etc/ts-agent-memory.api.md +284 -0
  63. package/lib/index.d.ts +2 -0
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js +2 -0
  66. package/lib/index.js.map +1 -1
  67. package/lib/packlets/ingest/cycleGuard.d.ts +41 -0
  68. package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -0
  69. package/lib/packlets/ingest/cycleGuard.js +115 -0
  70. package/lib/packlets/ingest/cycleGuard.js.map +1 -0
  71. package/lib/packlets/ingest/hostStages.d.ts +88 -0
  72. package/lib/packlets/ingest/hostStages.d.ts.map +1 -0
  73. package/lib/packlets/ingest/hostStages.js +7 -0
  74. package/lib/packlets/ingest/hostStages.js.map +1 -0
  75. package/lib/packlets/ingest/index.d.ts +5 -0
  76. package/lib/packlets/ingest/index.d.ts.map +1 -0
  77. package/lib/packlets/ingest/index.js +25 -0
  78. package/lib/packlets/ingest/index.js.map +1 -0
  79. package/lib/packlets/ingest/model.d.ts +177 -0
  80. package/lib/packlets/ingest/model.d.ts.map +1 -0
  81. package/lib/packlets/ingest/model.js +7 -0
  82. package/lib/packlets/ingest/model.js.map +1 -0
  83. package/lib/packlets/ingest/orchestrator.d.ts +206 -0
  84. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -0
  85. package/lib/packlets/ingest/orchestrator.js +442 -0
  86. package/lib/packlets/ingest/orchestrator.js.map +1 -0
  87. package/lib/packlets/retrieve/index.d.ts +1 -0
  88. package/lib/packlets/retrieve/index.d.ts.map +1 -1
  89. package/lib/packlets/retrieve/index.js +1 -0
  90. package/lib/packlets/retrieve/index.js.map +1 -1
  91. package/lib/packlets/retrieve/temporalRetrievers.d.ts +78 -0
  92. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -0
  93. package/lib/packlets/retrieve/temporalRetrievers.js +178 -0
  94. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -0
  95. package/lib/packlets/store/fileTreeMemoryStore.d.ts +118 -9
  96. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  97. package/lib/packlets/store/fileTreeMemoryStore.js +322 -40
  98. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  99. package/lib/packlets/tools/index.d.ts +2 -0
  100. package/lib/packlets/tools/index.d.ts.map +1 -0
  101. package/lib/packlets/tools/index.js +22 -0
  102. package/lib/packlets/tools/index.js.map +1 -0
  103. package/lib/packlets/tools/memoryTools.d.ts +139 -0
  104. package/lib/packlets/tools/memoryTools.d.ts.map +1 -0
  105. package/lib/packlets/tools/memoryTools.js +341 -0
  106. package/lib/packlets/tools/memoryTools.js.map +1 -0
  107. package/lib/packlets/types/identityCodec.d.ts +86 -0
  108. package/lib/packlets/types/identityCodec.d.ts.map +1 -1
  109. package/lib/packlets/types/identityCodec.js +118 -1
  110. package/lib/packlets/types/identityCodec.js.map +1 -1
  111. package/lib/packlets/types/index.d.ts +1 -0
  112. package/lib/packlets/types/index.d.ts.map +1 -1
  113. package/lib/packlets/types/index.js +1 -0
  114. package/lib/packlets/types/index.js.map +1 -1
  115. package/lib/packlets/types/temporal.d.ts +40 -0
  116. package/lib/packlets/types/temporal.d.ts.map +1 -0
  117. package/lib/packlets/types/temporal.js +92 -0
  118. package/lib/packlets/types/temporal.js.map +1 -0
  119. package/lib/packlets/types/writePolicy.d.ts +49 -0
  120. package/lib/packlets/types/writePolicy.d.ts.map +1 -1
  121. package/lib/packlets/types/writePolicy.js +98 -1
  122. package/lib/packlets/types/writePolicy.js.map +1 -1
  123. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts +10 -0
  124. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts.map +1 -0
  125. package/lib/test/unit/converters/antagonistRoundTrip.test.js +97 -0
  126. package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
  127. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts +9 -0
  128. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts.map +1 -0
  129. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +275 -0
  130. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
  131. package/lib/test/unit/ingest/cycleGuard.test.d.ts +2 -0
  132. package/lib/test/unit/ingest/cycleGuard.test.d.ts.map +1 -0
  133. package/lib/test/unit/ingest/cycleGuard.test.js +56 -0
  134. package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -0
  135. package/lib/test/unit/ingest/orchestrator.test.d.ts +2 -0
  136. package/lib/test/unit/ingest/orchestrator.test.d.ts.map +1 -0
  137. package/lib/test/unit/ingest/orchestrator.test.js +915 -0
  138. package/lib/test/unit/ingest/orchestrator.test.js.map +1 -0
  139. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts +2 -0
  140. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts.map +1 -0
  141. package/lib/test/unit/retrieve/temporalRetrievers.test.js +184 -0
  142. package/lib/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
  143. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts +9 -0
  144. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts.map +1 -0
  145. package/lib/test/unit/store/antagonistTemporalBoundary.test.js +122 -0
  146. package/lib/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
  147. package/lib/test/unit/store/fileTreeMemoryStore.test.js +74 -7
  148. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  149. package/lib/test/unit/store/temporalStore.test.d.ts +2 -0
  150. package/lib/test/unit/store/temporalStore.test.d.ts.map +1 -0
  151. package/lib/test/unit/store/temporalStore.test.js +400 -0
  152. package/lib/test/unit/store/temporalStore.test.js.map +1 -0
  153. package/lib/test/unit/tools/memoryTools.test.d.ts +2 -0
  154. package/lib/test/unit/tools/memoryTools.test.d.ts.map +1 -0
  155. package/lib/test/unit/tools/memoryTools.test.js +574 -0
  156. package/lib/test/unit/tools/memoryTools.test.js.map +1 -0
  157. package/lib/test/unit/types/temporalCodec.test.d.ts +2 -0
  158. package/lib/test/unit/types/temporalCodec.test.d.ts.map +1 -0
  159. package/lib/test/unit/types/temporalCodec.test.js +205 -0
  160. package/lib/test/unit/types/temporalCodec.test.js.map +1 -0
  161. package/lib/test/unit/types/temporalPolicy.test.d.ts +2 -0
  162. package/lib/test/unit/types/temporalPolicy.test.d.ts.map +1 -0
  163. package/lib/test/unit/types/temporalPolicy.test.js +64 -0
  164. package/lib/test/unit/types/temporalPolicy.test.js.map +1 -0
  165. package/package.json +7 -7
  166. package/rush-logs/ts-agent-memory.build.cache.log +1 -1
  167. package/rush-logs/ts-agent-memory.build.log +4 -4
  168. package/src/index.ts +2 -0
  169. package/src/packlets/ingest/cycleGuard.ts +142 -0
  170. package/src/packlets/ingest/hostStages.ts +111 -0
  171. package/src/packlets/ingest/index.ts +9 -0
  172. package/src/packlets/ingest/model.ts +184 -0
  173. package/src/packlets/ingest/orchestrator.ts +797 -0
  174. package/src/packlets/retrieve/index.ts +1 -0
  175. package/src/packlets/retrieve/temporalRetrievers.ts +210 -0
  176. package/src/packlets/store/fileTreeMemoryStore.ts +460 -66
  177. package/src/packlets/tools/index.ts +6 -0
  178. package/src/packlets/tools/memoryTools.ts +579 -0
  179. package/src/packlets/types/identityCodec.ts +184 -0
  180. package/src/packlets/types/index.ts +1 -0
  181. package/src/packlets/types/temporal.ts +96 -0
  182. package/src/packlets/types/writePolicy.ts +127 -0
  183. package/src/test/unit/converters/antagonistRoundTrip.test.ts +110 -0
  184. package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +362 -0
  185. package/src/test/unit/ingest/cycleGuard.test.ts +68 -0
  186. package/src/test/unit/ingest/orchestrator.test.ts +1158 -0
  187. package/src/test/unit/retrieve/temporalRetrievers.test.ts +226 -0
  188. package/src/test/unit/store/antagonistTemporalBoundary.test.ts +158 -0
  189. package/src/test/unit/store/fileTreeMemoryStore.test.ts +98 -7
  190. package/src/test/unit/store/temporalStore.test.ts +469 -0
  191. package/src/test/unit/tools/memoryTools.test.ts +771 -0
  192. package/src/test/unit/types/temporalCodec.test.ts +259 -0
  193. package/src/test/unit/types/temporalPolicy.test.ts +96 -0
  194. package/temp/build/lint/_eslint-5eVG3S6w.json +85 -9
  195. package/temp/build/typescript/ts_8nwakTlr.json +1 -1
  196. package/temp/ts-agent-memory.api.json +11984 -6314
  197. package/temp/ts-agent-memory.api.md +284 -0
@@ -0,0 +1,142 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Hash, Result, fail, mapResults, succeed } from '@fgv/ts-utils';
7
+ import { LinkType, MemoryId } from '../types';
8
+
9
+ /**
10
+ * A directed edge in the link graph the cycle guard reasons over: `source` links
11
+ * to `target` under relation `type`.
12
+ * @public
13
+ */
14
+ export interface ICycleGuardEdge {
15
+ readonly source: MemoryId;
16
+ readonly target: MemoryId;
17
+ readonly type: LinkType;
18
+ }
19
+
20
+ /**
21
+ * Build the design's `buildCycleKey` — a deterministic, canonical (RFC-8785,
22
+ * via `Crc32Normalizer` from `@fgv/ts-utils`) identity for a directed edge. Used
23
+ * to de-duplicate proposed edges so a repeated `(source, target, type)` proposal
24
+ * contributes a single graph edge (and never spuriously "re-closes" a cycle).
25
+ * @public
26
+ */
27
+ export function buildCycleKey(edge: ICycleGuardEdge): Result<string> {
28
+ return new Hash.Crc32Normalizer().computeHash({
29
+ source: edge.source,
30
+ target: edge.target,
31
+ type: edge.type
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Write-time cycle guard. Given the graph's EXISTING directed edges plus a set of
37
+ * PROPOSED edges, verify that adding the proposed edges keeps the union graph a
38
+ * DAG. Fails loudly, naming the first proposed edge that would close a directed
39
+ * cycle.
40
+ *
41
+ * @remarks
42
+ * Distinct from the read-time BFS in `LinkTraversalRetriever` (which tolerates
43
+ * cycles via a visited-set): this is a WRITE-time admission check. Cycle
44
+ * detection is DFS reachability — a proposed `source -> target` edge closes a
45
+ * cycle iff `target` already reaches `source` in the graph built so far. Proposed
46
+ * edges are folded in one at a time (in order), so an intra-batch cycle
47
+ * (`A->B`, `B->A` both proposed) is caught as well as a batch-vs-existing cycle.
48
+ *
49
+ * See the design note §4: this enforces GLOBAL directed-acyclicity over all link
50
+ * types (conservative — a mutual associative pair is rejected). Callers that need
51
+ * mutual links disable the guard.
52
+ * @public
53
+ */
54
+ export function assertNoCycles(
55
+ existing: ReadonlyArray<ICycleGuardEdge>,
56
+ proposed: ReadonlyArray<ICycleGuardEdge>
57
+ ): Result<true> {
58
+ // Canonically key every edge up front (the only fallible step); the graph
59
+ // building + cycle detection below is then pure.
60
+ return keyEdges(existing).onSuccess((existingKeyed) =>
61
+ keyEdges(proposed).onSuccess((proposedKeyed) => {
62
+ // Adjacency map, de-duplicated by canonical edge key so a repeated edge is
63
+ // one arc. Seed with the existing edges, then fold in each proposed edge,
64
+ // checking reachability BEFORE inserting it.
65
+ const adjacency: Map<string, Set<string>> = new Map<string, Set<string>>();
66
+ const seenKeys: Set<string> = new Set<string>();
67
+ for (const keyed of existingKeyed) {
68
+ addEdge(adjacency, seenKeys, keyed);
69
+ }
70
+ for (const keyed of proposedKeyed) {
71
+ const edge: ICycleGuardEdge = keyed.edge;
72
+ // A self-loop is the degenerate one-node cycle.
73
+ if (edge.source === edge.target) {
74
+ return fail(
75
+ `ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' is a self-loop`
76
+ );
77
+ }
78
+ // Reachability: does `target` already reach `source`? If so, adding
79
+ // `source -> target` closes a directed cycle.
80
+ if (reaches(adjacency, edge.target, edge.source)) {
81
+ return fail(
82
+ `ingest cycle guard: edge '${edge.source}' -${edge.type}-> '${edge.target}' would create a cycle`
83
+ );
84
+ }
85
+ addEdge(adjacency, seenKeys, keyed);
86
+ }
87
+ return succeed(true);
88
+ })
89
+ );
90
+ }
91
+
92
+ /** An edge paired with its canonical cycle key. */
93
+ interface IKeyedEdge {
94
+ readonly edge: ICycleGuardEdge;
95
+ readonly key: string;
96
+ }
97
+
98
+ /** Canonically key a set of edges (the guard's only fallible step). */
99
+ function keyEdges(edges: ReadonlyArray<ICycleGuardEdge>): Result<ReadonlyArray<IKeyedEdge>> {
100
+ return mapResults(edges.map((edge) => buildCycleKey(edge).onSuccess((key) => succeed({ edge, key }))));
101
+ }
102
+
103
+ /** Add an edge to the adjacency map, de-duplicated by its canonical key. */
104
+ function addEdge(adjacency: Map<string, Set<string>>, seenKeys: Set<string>, keyed: IKeyedEdge): void {
105
+ if (seenKeys.has(keyed.key)) {
106
+ return;
107
+ }
108
+ seenKeys.add(keyed.key);
109
+ const source: string = keyed.edge.source;
110
+ const target: string = keyed.edge.target;
111
+ const targets: Set<string> | undefined = adjacency.get(source);
112
+ if (targets === undefined) {
113
+ adjacency.set(source, new Set<string>([target]));
114
+ } else {
115
+ targets.add(target);
116
+ }
117
+ }
118
+
119
+ /** DFS reachability: is `to` reachable from `from` over `adjacency`? */
120
+ function reaches(adjacency: ReadonlyMap<string, Set<string>>, from: string, to: string): boolean {
121
+ const visited: Set<string> = new Set<string>();
122
+ const stack: string[] = [from];
123
+ while (stack.length > 0) {
124
+ const node: string = stack.pop() as string;
125
+ if (node === to) {
126
+ return true;
127
+ }
128
+ if (visited.has(node)) {
129
+ continue;
130
+ }
131
+ visited.add(node);
132
+ const neighbors: Set<string> | undefined = adjacency.get(node);
133
+ if (neighbors !== undefined) {
134
+ for (const neighbor of neighbors) {
135
+ if (!visited.has(neighbor)) {
136
+ stack.push(neighbor);
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return false;
142
+ }
@@ -0,0 +1,111 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { Result } from '@fgv/ts-utils';
7
+ import { MemoryId } from '../types';
8
+ import {
9
+ ICandidateEdge,
10
+ ICandidateRecord,
11
+ IEntityResolutionCandidate,
12
+ IIngestItem,
13
+ IMemoryClassification,
14
+ ResolutionVerdict
15
+ } from './model';
16
+
17
+ /**
18
+ * Stage 2 — the host's classifier. Decides what {@link IIngestItem} maps to
19
+ * which memory {@link Kind} (and optional tags / confidence). LOCKED as a
20
+ * separate staged interface (OQ-10): the host plugs its existing classifier
21
+ * machinery in here rather than surrendering to an opaque ingestor.
22
+ * @public
23
+ */
24
+ export interface IMemoryClassifier {
25
+ /**
26
+ * Classify one item. A `fail` aborts the item's ingest loudly (fgv never
27
+ * guesses a kind).
28
+ */
29
+ classify(item: IIngestItem): Promise<Result<IMemoryClassification>>;
30
+ }
31
+
32
+ /**
33
+ * Stage 3 — the host's fact extractor. Turns a classified item into zero or more
34
+ * {@link ICandidateRecord}s. Each candidate's body is validated against the
35
+ * kind's registered Converter by fgv before it can reach the store (the typed
36
+ * validation boundary — no unchecked host data is persisted).
37
+ * @public
38
+ */
39
+ export interface IFactExtractor {
40
+ /**
41
+ * Extract candidate records from a classified item. An empty array is a valid
42
+ * result (the item yielded nothing memorable).
43
+ */
44
+ extract(
45
+ item: IIngestItem,
46
+ classification: IMemoryClassification
47
+ ): Promise<Result<ReadonlyArray<ICandidateRecord>>>;
48
+ }
49
+
50
+ /**
51
+ * Stage 4 (optional) — the host's entity resolver (OQ-13, LOCKED OPTIONAL). When
52
+ * supplied, fgv surfaces near-duplicate {@link IEntityResolutionCandidate}s (from
53
+ * layer-2 similarity search) and the resolver returns a
54
+ * {@link ResolutionVerdict}. When ABSENT, stage-4 dedup falls back to
55
+ * exact-`{ kind, body }`-hash only — the deterministic-identity host path.
56
+ * @public
57
+ */
58
+ export interface IEntityResolver {
59
+ /**
60
+ * Decide whether `candidate` is new, a duplicate of / supersedes / merges into
61
+ * one of the surfaced `similar` records. `similar` is non-empty and ordered by
62
+ * descending score when the resolver is invoked (fgv only calls it when
63
+ * layer-2 surfaces at least one over-threshold neighbor).
64
+ */
65
+ resolve(
66
+ candidate: ICandidateRecord,
67
+ similar: ReadonlyArray<IEntityResolutionCandidate>
68
+ ): Promise<Result<ResolutionVerdict>>;
69
+ }
70
+
71
+ /**
72
+ * The context fgv hands the host's relation extractor (stage 5): the source item
73
+ * plus the candidates fgv is about to write, each paired with its resolved
74
+ * reference id (the codec `idStem`). The extractor proposes attributed edges over
75
+ * these ids and existing store records.
76
+ * @public
77
+ */
78
+ export interface IRelationContext {
79
+ /** The item being ingested. */
80
+ readonly item: IIngestItem;
81
+ /** The candidates fgv is about to write, each with its resolved reference id. */
82
+ readonly candidates: ReadonlyArray<IRelationCandidate>;
83
+ }
84
+
85
+ /**
86
+ * A candidate paired with its resolved reference id, handed to the relation
87
+ * extractor so it can source edges from it.
88
+ * @public
89
+ */
90
+ export interface IRelationCandidate {
91
+ /** The candidate about to be written. */
92
+ readonly candidate: ICandidateRecord;
93
+ /** Its resolved reference id (codec `idStem` — the stable entity reference). */
94
+ readonly id: MemoryId;
95
+ }
96
+
97
+ /**
98
+ * Stage 5 — the host's relation extractor. Proposes attributed edges among the
99
+ * candidates and existing records. fgv owns the validation, the write-time cycle
100
+ * guard, and the actual persistence of the edges (the host brings only the
101
+ * relationship judgment).
102
+ * @public
103
+ */
104
+ export interface IRelationExtractor {
105
+ /**
106
+ * Propose the edges to attach for this ingest. An empty array is valid (no
107
+ * relations). Every proposed {@link ICandidateEdge.source | source} must be one
108
+ * of the context's candidate reference ids.
109
+ */
110
+ relate(context: IRelationContext): Promise<Result<ReadonlyArray<ICandidateEdge>>>;
111
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export * from './model';
7
+ export * from './hostStages';
8
+ export * from './cycleGuard';
9
+ export * from './orchestrator';
@@ -0,0 +1,184 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { IEdge, IMemoryEnvelope, IMemoryRecord, Kind, MemoryId, Tag } from '../types';
7
+
8
+ /**
9
+ * A single unit of raw source material handed to the ingest pipeline. The host
10
+ * owns the shape of {@link IIngestItem.content | content} — fgv never interprets
11
+ * it; it flows opaquely into the host's classifier and extractor (stages 2-3).
12
+ *
13
+ * Single-item ingest is FIRST-CLASS: the orchestrator's primary entry point
14
+ * takes one `IIngestItem` (per-turn streaming), and the batch entry point is a
15
+ * convenience loop over it.
16
+ * @public
17
+ */
18
+ export interface IIngestItem {
19
+ /**
20
+ * Host-owned identity for this source item. Opaque to fgv; used only in
21
+ * diagnostics and echoed back on the {@link IIngestItemResult}.
22
+ */
23
+ readonly id: string;
24
+ /**
25
+ * The opaque source payload the host's classifier / extractor understand
26
+ * (e.g. a raw turn, a document, a tool-call transcript). Never interpreted by
27
+ * fgv.
28
+ */
29
+ readonly content: unknown;
30
+ /**
31
+ * Optional back-link to the memory record this item was derived from (e.g. the
32
+ * MTM turn an extracted fact came from). When present, fgv stamps it as
33
+ * {@link IProvenance.derivedFrom | provenance.derivedFrom} on every record
34
+ * ingested from this item (stage 6) — the cross-kind provenance spine.
35
+ */
36
+ readonly sourceId?: MemoryId;
37
+ /** Optional opaque metadata carried alongside the item; never interpreted by fgv. */
38
+ readonly metadata?: Record<string, unknown>;
39
+ }
40
+
41
+ /**
42
+ * The host classifier's verdict for an {@link IIngestItem} (stage 2). Guides the
43
+ * host's own extractor (stage 3) and supplies the default `kind` / `tags` /
44
+ * `confidence` fgv stamps when the extractor does not override them.
45
+ *
46
+ * Extensible: the `[key: string]: unknown` arm lets the host attach an opaque
47
+ * classification payload without changing this interface.
48
+ * @public
49
+ */
50
+ export interface IMemoryClassification {
51
+ /** The record kind the item classifies as. */
52
+ readonly kind: Kind;
53
+ /** Optional tags the classifier assigns. */
54
+ readonly tags?: ReadonlyArray<Tag>;
55
+ /** Optional classifier confidence in `[0, 1]`; flows to `provenance.confidence`. */
56
+ readonly confidence?: number;
57
+ /** Opaque, host-owned extension payload — never interpreted by fgv. */
58
+ readonly [key: string]: unknown;
59
+ }
60
+
61
+ /**
62
+ * The store-owned envelope fields fgv derives or stamps: `id` (from the codec),
63
+ * and the transaction-time metadata (`seq` / `contentHash` / `created` /
64
+ * `updated`). A {@link ICandidateRecord} supplies everything EXCEPT these.
65
+ * @public
66
+ */
67
+ export type StoreStampedEnvelopeField = 'id' | 'seq' | 'contentHash' | 'created' | 'updated';
68
+
69
+ /**
70
+ * A host-extracted candidate record (stage 3 output). The host supplies the
71
+ * full envelope MINUS the {@link StoreStampedEnvelopeField | store-stamped
72
+ * fields} (fgv derives `id` from the codec; the store stamps the rest) plus the
73
+ * typed body. Every candidate body is validated against the kind's registered
74
+ * Converter before it can reach the store (the typed validation boundary).
75
+ * @public
76
+ */
77
+ export interface ICandidateRecord {
78
+ /** The host-supplied envelope, minus the store-stamped fields. */
79
+ readonly envelope: Omit<IMemoryEnvelope, StoreStampedEnvelopeField>;
80
+ /** The per-kind body (a markdown string in v1), validated on ingest. */
81
+ readonly body: unknown;
82
+ }
83
+
84
+ /**
85
+ * A near-duplicate candidate surfaced to the {@link IEntityResolver} by stage-4
86
+ * layer-2 similarity search: an existing record whose embedding is within the
87
+ * similarity threshold of the incoming candidate.
88
+ * @public
89
+ */
90
+ export interface IEntityResolutionCandidate {
91
+ /** The existing record's id. */
92
+ readonly id: MemoryId;
93
+ /** The existing record. */
94
+ readonly record: IMemoryRecord<unknown>;
95
+ /** The backend similarity score (higher = more similar). */
96
+ readonly score: number;
97
+ }
98
+
99
+ /**
100
+ * The four dedup verdicts a {@link IEntityResolver} (or fgv's exact-match layer)
101
+ * returns for a candidate. See the design note §3 for the verdict → write
102
+ * disposition mapping.
103
+ * @public
104
+ */
105
+ export type ResolutionVerdict =
106
+ | { readonly verdict: 'new' }
107
+ | { readonly verdict: 'duplicate-of'; readonly target: MemoryId }
108
+ | { readonly verdict: 'supersede'; readonly target: MemoryId }
109
+ | { readonly verdict: 'merge-into'; readonly target: MemoryId };
110
+
111
+ /**
112
+ * How a candidate was ultimately written (or not) after resolution.
113
+ *
114
+ * - `written` — persisted as a fresh record (verdict `new`), or as a superseding
115
+ * record (verdict `supersede`).
116
+ * - `deduped` — not written; an existing record satisfied it (verdict
117
+ * `duplicate-of`, incl. every layer-1 exact `{ kind, body }` match).
118
+ * - `merged` — merged into an existing target entity (verdict `merge-into`).
119
+ * @public
120
+ */
121
+ export type IngestDisposition = 'written' | 'deduped' | 'merged';
122
+
123
+ /**
124
+ * A stage-5 attributed edge proposal: the {@link ICandidateEdge.edge | edge} to
125
+ * add, sourced from {@link ICandidateEdge.source | source}. The source MUST be a
126
+ * candidate being written in this ingest (edges land on the source record's
127
+ * `envelope.links`); the edge's `target` must resolve to a sibling candidate or
128
+ * an existing store record.
129
+ * @public
130
+ */
131
+ export interface ICandidateEdge {
132
+ /** The reference id (codec `idStem`) of the candidate the edge originates from. */
133
+ readonly source: MemoryId;
134
+ /** The attributed edge (type / target / optional confidence / provenance). */
135
+ readonly edge: IEdge;
136
+ }
137
+
138
+ /**
139
+ * Per-candidate outcome of an ingest run.
140
+ * @public
141
+ */
142
+ export interface IIngestedRecordResult {
143
+ /** The candidate the outcome is for. */
144
+ readonly candidate: ICandidateRecord;
145
+ /** The resolution verdict fgv reached (or the resolver returned). */
146
+ readonly resolution: ResolutionVerdict;
147
+ /** What the write ultimately did. */
148
+ readonly disposition: IngestDisposition;
149
+ /**
150
+ * The stored record's id: the newly-written id (`written` / `merged`), or the
151
+ * existing target's id (`deduped`).
152
+ */
153
+ readonly id: MemoryId;
154
+ /** The persisted record, when a write happened (`written` / `merged`). */
155
+ readonly record?: IMemoryRecord<unknown>;
156
+ /** The stage-5 edges attached to this candidate before the write. */
157
+ readonly edges: ReadonlyArray<ICandidateEdge>;
158
+ /**
159
+ * Informational diagnostic, set to `'temporal-versioned'` when the
160
+ * contradicts→temporal interlock is recognized: a `contradicts` edge was
161
+ * attached to a candidate of a temporal kind.
162
+ *
163
+ * @remarks
164
+ * A temporal kind ALWAYS writes through the store's versioned put path (that is
165
+ * the codec's `isVersioned` behavior — the prior version is invalidated and a
166
+ * new version written on every write, contradicts edge or not). This flag does
167
+ * NOT cause that routing; it is a diagnostic marker that the contradicts-driven
168
+ * scenario occurred, so callers can distinguish a contradiction-superseding
169
+ * version from an ordinary revision.
170
+ */
171
+ readonly interlock?: 'temporal-versioned';
172
+ }
173
+
174
+ /**
175
+ * The result of ingesting a single {@link IIngestItem}: the item plus the
176
+ * per-candidate outcomes (one item can yield zero or many candidate records).
177
+ * @public
178
+ */
179
+ export interface IIngestItemResult {
180
+ /** The item that was ingested. */
181
+ readonly item: IIngestItem;
182
+ /** The per-candidate outcomes, in extraction order. */
183
+ readonly records: ReadonlyArray<IIngestedRecordResult>;
184
+ }