@graphorin/memory 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +24 -7
  3. package/dist/conflict/index.js +0 -1
  4. package/dist/conflict/pipeline.js +0 -1
  5. package/dist/conflict/pipeline.js.map +1 -1
  6. package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
  7. package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
  8. package/dist/conflict/types.d.ts +1 -1
  9. package/dist/conflict/types.js.map +1 -1
  10. package/dist/consolidator/index.js +2 -0
  11. package/dist/consolidator/index.js.map +1 -1
  12. package/dist/consolidator/lock.js +7 -1
  13. package/dist/consolidator/lock.js.map +1 -1
  14. package/dist/consolidator/phases/deep.js +19 -12
  15. package/dist/consolidator/phases/deep.js.map +1 -1
  16. package/dist/consolidator/phases/induce.d.ts.map +1 -1
  17. package/dist/consolidator/phases/induce.js +2 -3
  18. package/dist/consolidator/phases/induce.js.map +1 -1
  19. package/dist/consolidator/phases/reflect.js +35 -8
  20. package/dist/consolidator/phases/reflect.js.map +1 -1
  21. package/dist/consolidator/phases/standard.js +199 -28
  22. package/dist/consolidator/phases/standard.js.map +1 -1
  23. package/dist/consolidator/reconcile.js +16 -15
  24. package/dist/consolidator/reconcile.js.map +1 -1
  25. package/dist/consolidator/runtime.d.ts.map +1 -1
  26. package/dist/consolidator/runtime.js +129 -47
  27. package/dist/consolidator/runtime.js.map +1 -1
  28. package/dist/consolidator/scheduler.d.ts +17 -3
  29. package/dist/consolidator/scheduler.d.ts.map +1 -1
  30. package/dist/consolidator/scheduler.js.map +1 -1
  31. package/dist/consolidator/types.d.ts +30 -1
  32. package/dist/consolidator/types.d.ts.map +1 -1
  33. package/dist/consolidator/types.js +10 -0
  34. package/dist/consolidator/types.js.map +1 -1
  35. package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
  36. package/dist/context-engine/compaction/clear-tool-results.js +1 -1
  37. package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
  38. package/dist/context-engine/compaction/compactor.d.ts +1 -0
  39. package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
  40. package/dist/context-engine/compaction/compactor.js +32 -8
  41. package/dist/context-engine/compaction/compactor.js.map +1 -1
  42. package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
  43. package/dist/context-engine/compaction/index.d.ts +1 -0
  44. package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
  45. package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
  46. package/dist/context-engine/compaction/templates/marker.js +34 -0
  47. package/dist/context-engine/compaction/templates/marker.js.map +1 -0
  48. package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
  49. package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
  50. package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
  51. package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
  52. package/dist/context-engine/engine.d.ts +7 -176
  53. package/dist/context-engine/engine.d.ts.map +1 -1
  54. package/dist/context-engine/engine.js +0 -2
  55. package/dist/context-engine/engine.js.map +1 -1
  56. package/dist/context-engine/index.d.ts +8 -49
  57. package/dist/context-engine/io-types.d.ts +144 -0
  58. package/dist/context-engine/io-types.d.ts.map +1 -0
  59. package/dist/context-engine/locale-packs/types.d.ts +1 -1
  60. package/dist/context-engine/locale-packs/types.js.map +1 -1
  61. package/dist/context-engine/privacy-filter.d.ts +1 -2
  62. package/dist/context-engine/privacy-filter.d.ts.map +1 -1
  63. package/dist/context-engine/privacy-filter.js +1 -2
  64. package/dist/context-engine/privacy-filter.js.map +1 -1
  65. package/dist/errors/index.d.ts +2 -2
  66. package/dist/errors/index.js +2 -2
  67. package/dist/errors/index.js.map +1 -1
  68. package/dist/facade.d.ts +43 -52
  69. package/dist/facade.d.ts.map +1 -1
  70. package/dist/facade.js +5 -3
  71. package/dist/facade.js.map +1 -1
  72. package/dist/graph/entity-resolver.js +1 -1
  73. package/dist/graph/entity-resolver.js.map +1 -1
  74. package/dist/index.d.ts +17 -15
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +5 -7
  77. package/dist/index.js.map +1 -1
  78. package/dist/internal/envelope.js +57 -0
  79. package/dist/internal/envelope.js.map +1 -0
  80. package/dist/internal/injection-heuristics.js +0 -0
  81. package/dist/internal/injection-heuristics.js.map +1 -1
  82. package/dist/internal/llm-json.js +40 -0
  83. package/dist/internal/llm-json.js.map +1 -0
  84. package/dist/internal/storage-adapter.d.ts +23 -10
  85. package/dist/internal/storage-adapter.d.ts.map +1 -1
  86. package/dist/memory-interface.d.ts +131 -0
  87. package/dist/memory-interface.d.ts.map +1 -0
  88. package/dist/package.js +6 -0
  89. package/dist/package.js.map +1 -0
  90. package/dist/search/iterative.d.ts.map +1 -1
  91. package/dist/search/iterative.js +2 -3
  92. package/dist/search/iterative.js.map +1 -1
  93. package/dist/search/query-transform.d.ts.map +1 -1
  94. package/dist/search/query-transform.js +2 -3
  95. package/dist/search/query-transform.js.map +1 -1
  96. package/dist/tiers/episodic-memory.js +3 -3
  97. package/dist/tiers/episodic-memory.js.map +1 -1
  98. package/dist/tiers/index.d.ts +2 -2
  99. package/dist/tiers/insight-memory.js +1 -1
  100. package/dist/tiers/insight-memory.js.map +1 -1
  101. package/dist/tiers/procedural-memory.d.ts +1 -1
  102. package/dist/tiers/procedural-memory.js +2 -2
  103. package/dist/tiers/procedural-memory.js.map +1 -1
  104. package/dist/tiers/semantic-memory.d.ts +58 -4
  105. package/dist/tiers/semantic-memory.d.ts.map +1 -1
  106. package/dist/tiers/semantic-memory.js +103 -20
  107. package/dist/tiers/semantic-memory.js.map +1 -1
  108. package/dist/tools/block-tools.d.ts +19 -57
  109. package/dist/tools/block-tools.d.ts.map +1 -1
  110. package/dist/tools/block-tools.js.map +1 -1
  111. package/dist/tools/fact-tools.d.ts +53 -214
  112. package/dist/tools/fact-tools.d.ts.map +1 -1
  113. package/dist/tools/fact-tools.js +3 -2
  114. package/dist/tools/fact-tools.js.map +1 -1
  115. package/dist/tools/recall-tools.d.ts +34 -175
  116. package/dist/tools/recall-tools.d.ts.map +1 -1
  117. package/dist/tools/recall-tools.js +5 -4
  118. package/dist/tools/recall-tools.js.map +1 -1
  119. package/dist/tools/runbook-tools.d.ts +15 -58
  120. package/dist/tools/runbook-tools.d.ts.map +1 -1
  121. package/dist/tools/runbook-tools.js.map +1 -1
  122. package/dist/tools/types.d.ts +1 -1
  123. package/package.json +18 -17
  124. package/src/conflict/index.ts +29 -0
  125. package/src/conflict/locale-packs/en.ts +142 -0
  126. package/src/conflict/locale-packs/index.ts +17 -0
  127. package/src/conflict/locale-packs/types.ts +138 -0
  128. package/src/conflict/pipeline.ts +371 -0
  129. package/src/conflict/stages/helpers.ts +65 -0
  130. package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
  131. package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
  132. package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
  133. package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
  134. package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
  135. package/src/conflict/types.ts +263 -0
  136. package/src/consolidator/budget.ts +312 -0
  137. package/src/consolidator/decay.ts +246 -0
  138. package/src/consolidator/dlq.ts +61 -0
  139. package/src/consolidator/errors.ts +87 -0
  140. package/src/consolidator/idempotency.ts +27 -0
  141. package/src/consolidator/index.ts +269 -0
  142. package/src/consolidator/lock.ts +141 -0
  143. package/src/consolidator/noise-filter.ts +235 -0
  144. package/src/consolidator/phases/deep.ts +308 -0
  145. package/src/consolidator/phases/induce.ts +423 -0
  146. package/src/consolidator/phases/learned-context.ts +233 -0
  147. package/src/consolidator/phases/light.ts +166 -0
  148. package/src/consolidator/phases/reflect.ts +504 -0
  149. package/src/consolidator/phases/standard.ts +1002 -0
  150. package/src/consolidator/reconcile.ts +273 -0
  151. package/src/consolidator/runtime.ts +1049 -0
  152. package/src/consolidator/scheduler.ts +182 -0
  153. package/src/consolidator/triggers.ts +134 -0
  154. package/src/consolidator/types.ts +622 -0
  155. package/src/context-engine/annotations.ts +161 -0
  156. package/src/context-engine/auto-recall.ts +92 -0
  157. package/src/context-engine/compaction/clear-tool-results.ts +232 -0
  158. package/src/context-engine/compaction/compactor.ts +403 -0
  159. package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
  160. package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
  161. package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
  162. package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
  163. package/src/context-engine/compaction/hooks/types.ts +68 -0
  164. package/src/context-engine/compaction/index.ts +60 -0
  165. package/src/context-engine/compaction/templates/marker.ts +33 -0
  166. package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
  167. package/src/context-engine/compaction/thresholds.ts +107 -0
  168. package/src/context-engine/compaction/types.ts +255 -0
  169. package/src/context-engine/engine.ts +970 -0
  170. package/src/context-engine/index.ts +118 -0
  171. package/src/context-engine/io-types.ts +162 -0
  172. package/src/context-engine/locale-packs/en.ts +107 -0
  173. package/src/context-engine/locale-packs/index.ts +19 -0
  174. package/src/context-engine/locale-packs/resolver.ts +197 -0
  175. package/src/context-engine/locale-packs/types.ts +177 -0
  176. package/src/context-engine/metadata.ts +114 -0
  177. package/src/context-engine/preambles/inbound-en.ts +19 -0
  178. package/src/context-engine/privacy-filter.ts +190 -0
  179. package/src/context-engine/templates/base-en.ts +19 -0
  180. package/src/context-engine/templates/composer.ts +91 -0
  181. package/src/context-engine/token-budget.ts +319 -0
  182. package/src/context-engine/token-counter.ts +157 -0
  183. package/src/errors/index.ts +273 -0
  184. package/src/facade.ts +750 -0
  185. package/src/graph/entity-resolver.ts +364 -0
  186. package/src/graph/index.ts +24 -0
  187. package/src/index.ts +259 -0
  188. package/src/internal/contextualize.ts +173 -0
  189. package/src/internal/embedder-binding.ts +53 -0
  190. package/src/internal/envelope.ts +70 -0
  191. package/src/internal/id.ts +23 -0
  192. package/src/internal/injection-heuristics.ts +0 -0
  193. package/src/internal/llm-json.ts +38 -0
  194. package/src/internal/spans.ts +35 -0
  195. package/src/internal/storage-adapter.ts +963 -0
  196. package/src/memory-interface.ts +158 -0
  197. package/src/migration/embedder-migration.ts +237 -0
  198. package/src/migration/index.ts +13 -0
  199. package/src/search/explain.ts +123 -0
  200. package/src/search/fit-weights.ts +109 -0
  201. package/src/search/graph-ppr.ts +38 -0
  202. package/src/search/index.ts +55 -0
  203. package/src/search/iterative.ts +658 -0
  204. package/src/search/query-transform.ts +285 -0
  205. package/src/search/rrf.ts +225 -0
  206. package/src/search/trust.ts +55 -0
  207. package/src/search/types.ts +50 -0
  208. package/src/tiers/episodic-memory.ts +463 -0
  209. package/src/tiers/index.ts +50 -0
  210. package/src/tiers/insight-memory.ts +208 -0
  211. package/src/tiers/procedural-memory.ts +482 -0
  212. package/src/tiers/semantic-memory.ts +1729 -0
  213. package/src/tiers/session-memory.ts +271 -0
  214. package/src/tiers/shared-memory.ts +62 -0
  215. package/src/tiers/working-memory.ts +368 -0
  216. package/src/tools/block-tools.ts +172 -0
  217. package/src/tools/fact-tools.ts +473 -0
  218. package/src/tools/index.ts +109 -0
  219. package/src/tools/recall-tools.ts +303 -0
  220. package/src/tools/runbook-tools.ts +107 -0
  221. package/src/tools/taint.ts +39 -0
  222. package/src/tools/types.ts +37 -0
  223. package/dist/conflict/locale-packs/index.js +0 -4
  224. package/dist/context-engine/compaction/index.js +0 -10
  225. package/dist/context-engine/index.d.ts.map +0 -1
  226. package/dist/context-engine/index.js +0 -25
  227. package/dist/context-engine/locale-packs/index.js +0 -4
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Public types for the per-locale assets the
3
+ * {@link ContextEngine} consumes when assembling the layered system
4
+ * prompt. The framework ships an English default ({@link enLocalePack});
5
+ * additional locales are supplied by application code via
6
+ * {@link defineContextLocalePack}.
7
+ *
8
+ * The framework is locale-agnostic - no language is privileged in
9
+ * core. Every consumer-facing string is reachable through this
10
+ * surface so consumers can swap them without forking the framework.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+
15
+ /**
16
+ * Trigger mode used by the auto-recall heuristic. The framework ships
17
+ * regex patterns for the bundled locale; consumers may extend or
18
+ * replace them via {@link defineContextLocalePack}.
19
+ *
20
+ * @stable
21
+ */
22
+ export interface AutoRecallTriggers {
23
+ /**
24
+ * Case-insensitive regex set evaluated against the last user
25
+ * message. A match indicates the model would benefit from the
26
+ * top-K facts being injected into Layer 6.
27
+ */
28
+ readonly factTriggers: ReadonlyArray<RegExp>;
29
+ }
30
+
31
+ /**
32
+ * Layer 1 template fragments. The `composeTemplate` function
33
+ * picks the right fragment based on `memoryBaseMode`.
34
+ *
35
+ * @stable
36
+ */
37
+ export interface BaseTemplateFragments {
38
+ /** Full educational template (~250-350 tokens). */
39
+ readonly full: string;
40
+ /** Minimal template for high-end models (~80-120 tokens). */
41
+ readonly minimal: string;
42
+ }
43
+
44
+ /**
45
+ * D4 inbound-sanitization preamble fragment. Injected after the
46
+ * cache breakpoint (Layer 5/6 territory) on steps containing
47
+ * untrusted tool output.
48
+ *
49
+ * @stable
50
+ */
51
+ export interface InboundSanitizationPreamble {
52
+ /** Verbatim text appended to the system message when fired. */
53
+ readonly text: string;
54
+ }
55
+
56
+ /**
57
+ * Structured section-template fragments used by the auto-compaction
58
+ * summarizer (RB-46). Section order is fixed; only the per-section
59
+ * header / preamble text is locale-extensible.
60
+ *
61
+ * Each entry is the human-readable header for the corresponding
62
+ * section. Section indices are 1-based to match the documented
63
+ * layout in the architecture doc.
64
+ *
65
+ * @stable
66
+ */
67
+ export interface CompactionSummaryTemplate {
68
+ /** Preamble injected at the top of the summarizer prompt. */
69
+ readonly preamble: string;
70
+ /** 12 section headers. The last two are filled by the harness. */
71
+ readonly sections: readonly [
72
+ string, // 1: Session goal and current task
73
+ string, // 2: Decisions made and rationale
74
+ string, // 3: Constraints and non-negotiables (C4)
75
+ string, // 4: Key facts established
76
+ string, // 5: Open questions and ambiguities
77
+ string, // 6: Tools used and their outcomes
78
+ string, // 7: Files / artifacts referenced
79
+ string, // 8: Persona / preferences / project rules surfaced
80
+ string, // 9: Errors encountered and resolutions (SOTA-6)
81
+ string, // 10: Next steps (SOTA-6)
82
+ string, // 11: Recent turns preserved verbatim (harness-filled)
83
+ string, // 12: Compaction metadata (harness-filled)
84
+ ];
85
+ }
86
+
87
+ /**
88
+ * A `LocalePack` collects every consumer-facing string the
89
+ * ContextEngine renders. Consumers register additional locales via
90
+ * {@link defineContextLocalePack}. The framework is locale-agnostic
91
+ * - no language is privileged in core.
92
+ *
93
+ * @stable
94
+ */
95
+ export interface ContextLocalePack {
96
+ /** Stable lowercase identifier (`'en'`, `'de'`, `'fr'`, …). */
97
+ readonly id: string;
98
+ /** Layer 1 base template fragments. */
99
+ readonly baseTemplate: BaseTemplateFragments;
100
+ /** Auto-recall trigger regex set. */
101
+ readonly autoRecallTriggers: AutoRecallTriggers;
102
+ /** Inbound-sanitization preamble (D4 - RB-43). */
103
+ readonly inboundSanitizationPreamble: InboundSanitizationPreamble;
104
+ /** Structured compaction summary template (RB-46). */
105
+ readonly compactionSummaryTemplate: CompactionSummaryTemplate;
106
+ }
107
+
108
+ /**
109
+ * Builder used by application code that wants to ship a custom
110
+ * locale pack. The builder freezes every input so the pack can be
111
+ * safely reused across multiple `Memory` instances without
112
+ * accidental mutation.
113
+ *
114
+ * Partial packs are accepted: any field omitted falls back to the
115
+ * English default at compose time, with a one-time WARN per locale
116
+ * per missing surface (the safety guarantee is preserved even when
117
+ * an operator installs a partial locale pack).
118
+ *
119
+ * @stable
120
+ */
121
+ export interface PartialContextLocalePack {
122
+ readonly id: string;
123
+ readonly baseTemplate?: Partial<BaseTemplateFragments>;
124
+ readonly autoRecallTriggers?: Partial<AutoRecallTriggers>;
125
+ readonly inboundSanitizationPreamble?: Partial<InboundSanitizationPreamble>;
126
+ readonly compactionSummaryTemplate?: Partial<CompactionSummaryTemplate>;
127
+ }
128
+
129
+ /**
130
+ * Build a {@link ContextLocalePack} from a partial input. Missing
131
+ * fields fall back to the English default at compose time.
132
+ *
133
+ * @stable
134
+ */
135
+ export function defineContextLocalePack(input: PartialContextLocalePack): PartialContextLocalePack {
136
+ if (typeof input.id !== 'string' || input.id.length === 0) {
137
+ throw new TypeError(
138
+ '[graphorin/memory] defineContextLocalePack: `id` must be a non-empty lowercase string.',
139
+ );
140
+ }
141
+ return Object.freeze({
142
+ id: input.id,
143
+ ...(input.baseTemplate !== undefined
144
+ ? { baseTemplate: Object.freeze({ ...input.baseTemplate }) }
145
+ : {}),
146
+ ...(input.autoRecallTriggers !== undefined
147
+ ? {
148
+ autoRecallTriggers: Object.freeze({
149
+ ...(input.autoRecallTriggers.factTriggers !== undefined
150
+ ? { factTriggers: Object.freeze([...input.autoRecallTriggers.factTriggers]) }
151
+ : {}),
152
+ }) as Partial<AutoRecallTriggers>,
153
+ }
154
+ : {}),
155
+ ...(input.inboundSanitizationPreamble !== undefined
156
+ ? {
157
+ inboundSanitizationPreamble: Object.freeze({ ...input.inboundSanitizationPreamble }),
158
+ }
159
+ : {}),
160
+ ...(input.compactionSummaryTemplate !== undefined
161
+ ? {
162
+ compactionSummaryTemplate: Object.freeze({
163
+ ...(input.compactionSummaryTemplate.preamble !== undefined
164
+ ? { preamble: input.compactionSummaryTemplate.preamble }
165
+ : {}),
166
+ ...(input.compactionSummaryTemplate.sections !== undefined
167
+ ? {
168
+ sections: Object.freeze([
169
+ ...input.compactionSummaryTemplate.sections,
170
+ ]) as CompactionSummaryTemplate['sections'],
171
+ }
172
+ : {}),
173
+ }) as Partial<CompactionSummaryTemplate>,
174
+ }
175
+ : {}),
176
+ });
177
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * `gatherMemoryMetadata(...)` - implements the
3
+ * {@link Memory.metadata} surface for Phase 10d. Aggregates
4
+ * per-tier counts + last-consolidation timestamps + active
5
+ * embedder id + active locale.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { MemoryMetadata, SessionScope } from '@graphorin/core';
11
+ import type { Consolidator } from '../consolidator/index.js';
12
+ import type { MemoryStoreAdapter } from '../internal/storage-adapter.js';
13
+
14
+ /**
15
+ * Per-call dependency surface. Mirrors the `Memory` facade fields
16
+ * the metadata gatherer needs without taking a hard dependency on
17
+ * the facade type itself (the facade and the gatherer are
18
+ * mutually-referenced through the engine).
19
+ *
20
+ * @stable
21
+ */
22
+ export interface MemoryMetadataDeps {
23
+ readonly store: MemoryStoreAdapter;
24
+ readonly consolidator: Consolidator;
25
+ readonly embedderId: () => string | null;
26
+ readonly localeId: string;
27
+ /** Optional free-form metadata tags surfaced through `MemoryMetadata.tags`. */
28
+ readonly tags?: ReadonlyArray<string>;
29
+ }
30
+
31
+ /**
32
+ * Build the {@link MemoryMetadata} block. Pure async - no side
33
+ * effects beyond the storage reads.
34
+ *
35
+ * @stable
36
+ */
37
+ export async function gatherMemoryMetadata(
38
+ scope: SessionScope,
39
+ deps: MemoryMetadataDeps,
40
+ ): Promise<MemoryMetadata> {
41
+ // CE-5 / MST-6: real `COUNT(*)` surfaces per tier - the old
42
+ // `search({ query: '*', topK: 1 })` probe was capped at 1 and matched zero
43
+ // rows on real SQLite (`escapeFtsQuery('*')` tokenises to nothing), so a
44
+ // production deploy told the model "Facts: 0" regardless of content. Counts
45
+ // never materialise rows; `messageCount` no longer lists up to 1000 messages.
46
+ const [blocks, rules, episodeCount, messageCount, factCount, status] = await Promise.all([
47
+ deps.store.working.list(scope),
48
+ deps.store.procedural.list(scope),
49
+ countOrZero(deps.store.episodic, scope),
50
+ countOrZero(deps.store.session, scope),
51
+ countOrZero(deps.store.semantic, scope),
52
+ deps.consolidator.status(),
53
+ ]);
54
+ // The metadata block surfaces the embedder id + active locale so
55
+ // the model can reason about the available embeddings; both are
56
+ // informational tags exposed through the optional `tags` field.
57
+ const embedderId = deps.embedderId();
58
+ const baseTags = deps.tags !== undefined ? [...deps.tags] : [];
59
+ const tags: string[] = [...baseTags, `locale:${deps.localeId}`];
60
+ if (embedderId !== null) tags.push(`embedder:${embedderId}`);
61
+
62
+ const meta: MemoryMetadata = {
63
+ factCount,
64
+ episodeCount,
65
+ messageCount,
66
+ // MST-6: "Active rules" excludes still-quarantined (e.g. induced) rules.
67
+ activeRuleCount: rules.filter((rule) => rule.status !== 'quarantined').length,
68
+ workingBlockCount: blocks.length,
69
+ ...(status.lastRunAt !== undefined ? { lastConsolidatedAt: status.lastRunAt } : {}),
70
+ tags: Object.freeze(tags),
71
+ };
72
+ return Object.freeze(meta);
73
+ }
74
+
75
+ /**
76
+ * Read a tier's `count(scope)` when the adapter exposes it (CE-5), degrading to
77
+ * `0` for adapters that do not - honest, and never the old 0/1 probe.
78
+ */
79
+ async function countOrZero(
80
+ store: { count?(scope: SessionScope): Promise<number> },
81
+ scope: SessionScope,
82
+ ): Promise<number> {
83
+ if (typeof store.count !== 'function') return 0;
84
+ try {
85
+ return await store.count(scope);
86
+ } catch {
87
+ return 0;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Render the {@link MemoryMetadata} block as the `<memory_metadata>`
93
+ * XML fragment used inside Layer 5 of the layered system prompt.
94
+ *
95
+ * @stable
96
+ */
97
+ export function renderMetadataBlock(meta: MemoryMetadata): string {
98
+ const lines = [
99
+ '<memory_metadata>',
100
+ ` Working blocks: ${meta.workingBlockCount}`,
101
+ ` Active rules: ${meta.activeRuleCount}`,
102
+ ` Indexed messages: ${meta.messageCount}`,
103
+ ` Episodes: ${meta.episodeCount}`,
104
+ ` Facts: ${meta.factCount}`,
105
+ ];
106
+ if (meta.lastConsolidatedAt !== undefined) {
107
+ lines.push(` Last consolidation: ${meta.lastConsolidatedAt}`);
108
+ }
109
+ if (meta.tags !== undefined && meta.tags.length > 0) {
110
+ lines.push(` Tags: ${meta.tags.join(', ')}`);
111
+ }
112
+ lines.push('</memory_metadata>');
113
+ return lines.join('\n');
114
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convenience re-export of the English D4 inbound-sanitization
3
+ * preamble. Lives at the documented path
4
+ * `@graphorin/memory/context-engine/preambles/inbound-en.ts` so
5
+ * Phase 12 (agent runtime) can import it directly when wiring
6
+ * the per-step preamble injection.
7
+ *
8
+ * The framework is locale-agnostic - additional preambles plug in
9
+ * through `defineContextLocalePack({ inboundSanitizationPreamble:
10
+ * { text } })`. A partial locale pack falls back to this English
11
+ * default with a one-time WARN per locale.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+
16
+ import { enLocalePack } from '../locale-packs/en.js';
17
+
18
+ /** English D4 preamble text (~80-120 tokens, cache-friendly). */
19
+ export const INBOUND_SANITIZATION_PREAMBLE_EN = enLocalePack.inboundSanitizationPreamble.text;
@@ -0,0 +1,190 @@
1
+ /**
2
+ * D2 sensitivity-tier filter. Tags every memory record with a
3
+ * decision (`'pass'` / `'drop'`) before the assembled prompt is
4
+ * compiled. The filter is *tier-based* - it trusts the
5
+ * `record.sensitivity` tag the consolidator / writer set; the
6
+ * outbound prompt-redaction middleware (D3, ADR-045) is the
7
+ * universal backstop for content the filter cannot see (raw user
8
+ * input, tool results, MCP responses, skill bodies, agent
9
+ * instructions, tool-call args).
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+
14
+ import type { LocalProviderTrust, Sensitivity } from '@graphorin/core';
15
+
16
+ /**
17
+ * Filter input: the record-level sensitivity tag + the per-tier
18
+ * trust class of the active provider.
19
+ *
20
+ * @stable
21
+ */
22
+ export interface PrivacyFilterContext {
23
+ /**
24
+ * Sensitivity tiers the active provider is allowed to receive.
25
+ * When omitted, the per-tier defaults below apply.
26
+ */
27
+ readonly providerAcceptsSensitivity?: ReadonlyArray<Sensitivity>;
28
+ /**
29
+ * Trust class of the active provider as classified by
30
+ * `@graphorin/provider/trust/classify-local-provider.ts`. Defaults
31
+ * to `'public-tls'` (the conservative cloud default).
32
+ */
33
+ readonly providerTrust?: LocalProviderTrust;
34
+ /**
35
+ * Per-user opt-in for cloud upload of `'internal'`-tier content.
36
+ * Default `false` - `'internal'` content does not leave the
37
+ * loopback boundary unless the user explicitly opts in.
38
+ */
39
+ readonly cloudUploadConsent?: boolean;
40
+ /**
41
+ * Override the default sensitivity applied to records that are
42
+ * missing a tag. Default `'internal'` per DEC-126.
43
+ */
44
+ readonly defaultSensitivity?: Sensitivity;
45
+ }
46
+
47
+ /**
48
+ * Output of {@link decide} for a single record.
49
+ *
50
+ * @stable
51
+ */
52
+ export type PrivacyDecision = 'pass' | 'drop';
53
+
54
+ /**
55
+ * Reason the decision was made. Surfaced to the metadata block so
56
+ * operators can audit per-tier drops.
57
+ *
58
+ * @stable
59
+ */
60
+ export type PrivacyDecisionReason =
61
+ | 'provider-rejects-secret'
62
+ | 'provider-rejects-internal'
63
+ | 'no-cloud-upload-consent'
64
+ | 'allowed';
65
+
66
+ /**
67
+ * Resolve the effective `acceptsSensitivity` set for a provider.
68
+ * The function is pure: callers can supply a custom matrix to
69
+ * override the defaults (used by tests + custom adapters).
70
+ *
71
+ * Defaults (DEC-149, ADR-013 ext):
72
+ *
73
+ * - `'loopback'` → `['public', 'internal', 'secret']`.
74
+ * - `'private'` → `['public', 'internal']`.
75
+ * - `'public-tls'` / `'public-cleartext'` → `['public']`.
76
+ *
77
+ * Per-provider overrides always win over the defaults - pass
78
+ * `providerAcceptsSensitivity` explicitly to override.
79
+ *
80
+ * @stable
81
+ */
82
+ export function effectiveAcceptsSensitivity(
83
+ context: PrivacyFilterContext,
84
+ ): ReadonlyArray<Sensitivity> {
85
+ if (context.providerAcceptsSensitivity !== undefined) {
86
+ return context.providerAcceptsSensitivity;
87
+ }
88
+ switch (context.providerTrust ?? 'public-tls') {
89
+ case 'loopback':
90
+ return ['public', 'internal', 'secret'];
91
+ case 'private':
92
+ return ['public', 'internal'];
93
+ case 'public-tls':
94
+ case 'public-cleartext':
95
+ return ['public'];
96
+ default:
97
+ return ['public'];
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Decide whether a single record is safe to send to the active
103
+ * provider. The decision logic mirrors the DoD spec:
104
+ *
105
+ * - `'public'` always passes.
106
+ * - `'secret'` passes only when the provider explicitly accepts
107
+ * `'secret'` AND the trust class is `'loopback'`.
108
+ * - `'internal'` passes when the provider accepts `'internal'`
109
+ * AND `cloudUploadConsent === true` (or the trust class is
110
+ * `'loopback'` / `'private'`).
111
+ *
112
+ * @stable
113
+ */
114
+ export function decide(
115
+ recordSensitivity: Sensitivity | undefined,
116
+ context: PrivacyFilterContext,
117
+ ): { readonly decision: PrivacyDecision; readonly reason: PrivacyDecisionReason } {
118
+ const sensitivity = recordSensitivity ?? context.defaultSensitivity ?? 'internal';
119
+ const accepts = effectiveAcceptsSensitivity(context);
120
+ const trust = context.providerTrust ?? 'public-tls';
121
+ if (sensitivity === 'public') {
122
+ return { decision: 'pass', reason: 'allowed' };
123
+ }
124
+ if (sensitivity === 'secret') {
125
+ if (accepts.includes('secret') && trust === 'loopback') {
126
+ return { decision: 'pass', reason: 'allowed' };
127
+ }
128
+ return { decision: 'drop', reason: 'provider-rejects-secret' };
129
+ }
130
+ // 'internal'
131
+ if (!accepts.includes('internal')) {
132
+ return { decision: 'drop', reason: 'provider-rejects-internal' };
133
+ }
134
+ if (trust === 'loopback' || trust === 'private') {
135
+ return { decision: 'pass', reason: 'allowed' };
136
+ }
137
+ if (context.cloudUploadConsent === true) {
138
+ return { decision: 'pass', reason: 'allowed' };
139
+ }
140
+ return { decision: 'drop', reason: 'no-cloud-upload-consent' };
141
+ }
142
+
143
+ /**
144
+ * Bookkeeping returned by {@link partition}. Carries both the
145
+ * surviving + dropped records and a per-reason counter for the
146
+ * audit trail.
147
+ *
148
+ * @stable
149
+ */
150
+ export interface PartitionResult<TRecord> {
151
+ readonly kept: ReadonlyArray<TRecord>;
152
+ readonly dropped: ReadonlyArray<{
153
+ readonly record: TRecord;
154
+ readonly reason: PrivacyDecisionReason;
155
+ }>;
156
+ readonly counters: Readonly<Record<PrivacyDecisionReason, number>>;
157
+ }
158
+
159
+ /**
160
+ * Partition a record list against the supplied filter context.
161
+ *
162
+ * @stable
163
+ */
164
+ export function partition<TRecord extends { readonly sensitivity?: Sensitivity }>(
165
+ records: ReadonlyArray<TRecord>,
166
+ context: PrivacyFilterContext,
167
+ ): PartitionResult<TRecord> {
168
+ const kept: TRecord[] = [];
169
+ const dropped: Array<{ readonly record: TRecord; readonly reason: PrivacyDecisionReason }> = [];
170
+ const counters: Record<PrivacyDecisionReason, number> = {
171
+ allowed: 0,
172
+ 'no-cloud-upload-consent': 0,
173
+ 'provider-rejects-internal': 0,
174
+ 'provider-rejects-secret': 0,
175
+ };
176
+ for (const record of records) {
177
+ const outcome = decide(record.sensitivity, context);
178
+ counters[outcome.reason] += 1;
179
+ if (outcome.decision === 'pass') {
180
+ kept.push(record);
181
+ } else {
182
+ dropped.push({ record, reason: outcome.reason });
183
+ }
184
+ }
185
+ return Object.freeze({
186
+ kept: Object.freeze(kept),
187
+ dropped: Object.freeze(dropped),
188
+ counters: Object.freeze(counters),
189
+ });
190
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Convenience re-export of the English Layer 1 base-template
3
+ * fragments so call sites can import the bundled defaults without
4
+ * dragging the full locale pack.
5
+ *
6
+ * The framework is locale-agnostic - no language is privileged in
7
+ * core. Application code can register additional locales via
8
+ * `defineContextLocalePack`.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ import { enLocalePack } from '../locale-packs/en.js';
14
+
15
+ /** English Layer 1 in `'full'` mode (~250-350 tokens). */
16
+ export const BASE_TEMPLATE_EN_FULL = enLocalePack.baseTemplate.full;
17
+
18
+ /** English Layer 1 in `'minimal'` mode (~80-120 tokens). */
19
+ export const BASE_TEMPLATE_EN_MINIMAL = enLocalePack.baseTemplate.minimal;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Layer 1 base-template composer. Resolves the locale pack, picks
3
+ * the right base fragment based on `memoryBaseMode`, and exposes
4
+ * the per-step inbound preamble as an additive Layer 5/6 fragment
5
+ * (so the Layer 1-4 cache prefix stays bytes-equal across the run).
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { ContextLocalePack } from '../locale-packs/index.js';
11
+
12
+ /**
13
+ * Layer 1 mode discriminator. `'full'` ships the full educational
14
+ * template (~250-350 tok); `'minimal'` ships the shortened version
15
+ * (~80-120 tok) for top-tier models.
16
+ *
17
+ * @stable
18
+ */
19
+ export type MemoryBaseMode = 'full' | 'minimal';
20
+
21
+ /**
22
+ * Compose Layer 1 from the resolved locale pack.
23
+ *
24
+ * @stable
25
+ */
26
+ export function composeLayer1(pack: ContextLocalePack, mode: MemoryBaseMode): string {
27
+ return mode === 'minimal' ? pack.baseTemplate.minimal : pack.baseTemplate.full;
28
+ }
29
+
30
+ /**
31
+ * Render the optional Layer 2 (`agent_instructions`) wrapper.
32
+ * Returns the empty string when `instructions` is empty so the
33
+ * layer is dropped from the assembled prompt.
34
+ *
35
+ * @stable
36
+ */
37
+ export function composeLayer2(instructions: string | undefined): string {
38
+ if (instructions === undefined || instructions.trim().length === 0) return '';
39
+ return `<agent_instructions>\n${instructions}\n</agent_instructions>`;
40
+ }
41
+
42
+ /**
43
+ * Render Layer 4 skills metadata cards. Each entry is a
44
+ * `<skill ... />` self-closing tag so the model sees only the
45
+ * progressive-disclosure metadata - the full SKILL.md body is
46
+ * loaded on-demand via `activate_skill(name)` per RB-04.
47
+ *
48
+ * @stable
49
+ */
50
+ export interface SkillMetadataCard {
51
+ readonly name: string;
52
+ readonly description: string;
53
+ readonly location?: string;
54
+ /** When `true`, the card is excluded from the assembled prompt. */
55
+ readonly disableModelInvocation?: boolean;
56
+ }
57
+
58
+ export function composeLayer4Skills(cards: ReadonlyArray<SkillMetadataCard>): string {
59
+ const visible = cards.filter((c) => c.disableModelInvocation !== true);
60
+ if (visible.length === 0) return '';
61
+ const lines = ['<skills_available>'];
62
+ for (const card of visible) {
63
+ const description = ` description="${escapeXmlAttr(card.description)}"`;
64
+ const location =
65
+ card.location !== undefined ? ` location="${escapeXmlAttr(card.location)}"` : '';
66
+ lines.push(` <skill name="${escapeXmlAttr(card.name)}"${description}${location} />`);
67
+ }
68
+ lines.push('</skills_available>');
69
+ return lines.join('\n');
70
+ }
71
+
72
+ /**
73
+ * Render the inbound-sanitization preamble fragment. Emitted
74
+ * AFTER the cache breakpoint so the Layer 1-4 cache prefix is
75
+ * unaffected. Caller threads the fragment into the system content
76
+ * post-Layer 5; the fragment is returned as a string so the
77
+ * caller does not need to know the cache-breakpoint policy.
78
+ *
79
+ * @stable
80
+ */
81
+ export function composeInboundPreamble(pack: ContextLocalePack): string {
82
+ return pack.inboundSanitizationPreamble.text;
83
+ }
84
+
85
+ function escapeXmlAttr(value: string): string {
86
+ return value
87
+ .replace(/&/g, '&amp;')
88
+ .replace(/</g, '&lt;')
89
+ .replace(/>/g, '&gt;')
90
+ .replace(/"/g, '&quot;');
91
+ }